🏹

Azure Function で Timerトリガーを試してみた(Windows10、Python)

2021/10/24に公開

Azure Function で Python の自作ライブラリを使うのと、Timerトリガーをローカルサーバーで動かすのに苦労したのでまとめます。

コードはこちら

Windows10、Pythonを前提としています。

デプロイについては書きませんが、環境設定含め下記サイトがとても詳しいです。

https://blog.beachside.dev/entry/2020/08/13/224000
https://blog.beachside.dev/entry/2020/09/09/190000

以下はメモ書き程度に備忘録として記載します。

環境設定

VS Code の使用を前提としているようです。

Azure Functions Core Tools に必要な Node.js をインストールし、VS Code に Azure Function の拡張機能をインストールします。

npm で コマンドラインで Azure Functions Core Tools V3 をインストールします。

npm install --global azure-functions-core-tools@3 --unsafe-perm true --save-dev

https://docs.microsoft.com/ja-jp/azure/developer/javascript/tutorial/vscode-function-app-http-trigger/tutorial-vscode-serverless-node-install?tabs=bash

これにより コマンドラインで func コマンドが使えるようになります。

なお、func コマンドが認識されない場合は、環境変数 Path で 下記 npm フォルダを設定してみましょう。

C:\Users\ユーザーネーム\AppData\Roaming\npm

func コマンドの詳細については下記参照。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-core-tools-reference?tabs=v2#func-new

実行ファイルの作成

コマンドでPython用のAzureFunctionのプロジェクトを作成します。

必要ファイルが自動で作成されます。

なお、VSCode の Azure タブから GUI操作で作成しても同じです。

func init 任意のプロジェクトフォルダ --python

プロジェクトフォルダに移動します。

cd 任意のプロジェクトフォルダ名

venv で仮想環境を作成します。

python -m venv .venv
.venv\scripts\activate

以降で出てくる Function をローカルで実行する func start は仮想環境下で実行する必要があるようです。

このコマンドは仮想環境で実行する必要があります。

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-run-local?tabs=v3%2Cwindows%2Cpython%2Cportal%2Cbash%2Ckeda

必要に応じて pipのアップグレードと requirement.txt からライブラリのインストールをするとよいでしょう。

python -m pip install  --upgrade pip
pip install -r requirements.txt

ローカルで実行する Function 作成を作成します。

HTTPトリガーの場合は temlate 名を指定して作成できるようです。

func new --name ファンクションのフォルダ名 --template "HTTP trigger"

コマンドライン上で一覧から選択してトリガーを選択することも可能です。

func new --name ファンクションのフォルダ名

下記の func コマンドで作成可能な template のリストを出力することもできます。

func template list

Timer トリガーをローカルで実行すると、下記のエラーが出ました。

The listener for function 'Functions.HelloTimer' was unable to start. Azure.Core: Retry failed after 6 tries. Retry settings can be adjusted in ClientOptions.Retry.

Azure のエミュレーターが機能してないのが原因のようです。

https://stackoverflow.com/questions/59026572/listener-for-function-was-unable-to-start-azure-function-app-timetrigger

Azurite を使うとエミュレーターの構築が便利でした。

https://qiita.com/uzresk/items/538c0f997faa736a75aa

Azurite を使うには、 npm で Azurite をインストールしてやります。

npm install -g azurite

C: 直下に azurite というフォルダを作成し、VS Code とは別のコマンドラインで下記コマンドを入力します。

azurite --silent --location c:\azurite --debug c:\azurite\debug.log

https://docs.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=visual-studio-code

下記ログが出力され、Azure のエミュレーターが起動されます。

Azurite Blob service is starting at http://127.0.0.1:10000
Azurite Blob service is successfully listening at http://127.0.0.1:10000
Azurite Queue service is starting at http://127.0.0.1:10001
Azurite Queue service is successfully listening at http://127.0.0.1:10001
Azurite Table service is starting at http://127.0.0.1:10002
Azurite Table service is successfully listening at http://127.0.0.1:10002

VS Code のコマンドラインからTimerトリガーを実行します。

func start

すると、init.py ファイルのコメントを追記した部分が実行されます。

def main(mytimer: func.TimerRequest) -> None:
    utc_timestamp = datetime.datetime.utcnow().replace(
        tzinfo=datetime.timezone.utc).isoformat()

    if mytimer.past_due:
        logging.info('The timer is past due!') ## ココが初期実行

    logging.info('Python timer trigger function ran at %s', utc_timestamp) ## ココが定期実行

ログは以下のとおり。

[2021-10-24T10:28:47.349Z] Worker process started and initialized.
[2021-10-24T10:28:47.461Z] The timer is past due!
[2021-10-24T10:28:47.464Z] Python timer trigger function ran at 2021-10-24T10:28:47.459041+00:00
[2021-10-24T10:28:47.481Z] Executed 'Functions.HelloTimer' (Succeeded, Id=8e31eaf8-7ad7-49df-a756-bae82aa01543, Duration=396ms)
[2021-10-24T10:28:51.970Z] Host lock lease acquired by instance ID '00000000000000000000000088A4879D'.

Timerトリガーの実行時間を変更するには、function.json 内の schedule を修整すればよいです。

function.json
"schedule": "0 */1 * * * *"

それぞれの数字は下記の意味を表します。

{second} {minute} {hour} {day} {month} {day-of-week}  

また、下記の記述を使って条件を設定することが可能です。

Type トリガーのタイミング
特定の値 0 5 * * * * 1時間ごとに1回、1日の毎時5分
すべての値 (*) 0 * 5 * * * 5時から始まる1時間で1分ごと
範囲 (-演算子) 5-7 * * * * * 1分間に3回-各日、各時間、毎分5秒から7秒
値のセット (,演算子) 5,8,10 * * * * * 1分間に3回-各日、各時間、毎分5秒、8秒、10秒
間隔値 (/演算子) 0 */5 * * * * 1時間に12回-各日、毎時5分0秒

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-timer?tabs=python#configuration

自作ライブラリの使用

プロジェクトフォルダ直下に自作ライブラリを格納するフォルダを作成し、その中に 実行したい.pyファイルと、空の init.py ファイルを格納すれば、Functionフォルダ内の init.py ファイルで自作ライブラリを import してトリガー実行することができます。

フォルダ構成
|----- .venv/
|----- .vscode/
|----- functionフォルダ(HelloTimer)/
|   |----- __init__.py   # 実行ファイル
|   |----- function.json # トリガー情報(実行時間)が記載されたファイル
|
|----- 自作ライブラリを格納するフォルダ/
|   |----- __init__.py   # 中身は空っぽで問題ない
|   |----- 自作ライブラリの.pyファイル(display_time.py)
|
|----- .gitignore
|----- getting_started.md
|----- host.json
|----- local.settings.json
|----- requirements.txt

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-python?tabs=azurecli-linux%2Capplication-level

試しに現在時刻を出力するだけの簡単な自作ライブラリを実行してみます。

display_time>display_time.py
import datetime

class display_time_now:
  def time_now(self):
    time = datetime.datetime.now()
    return time
HelloTimer>__init__.py
import datetime
import logging
import azure.functions as func
from display_time import display_time # 追加した自作ライブラリ

nowtime = display_time.display_time_now()     # 追加
time = nowtime.time_now()                     # 追加
time_str = time.strftime('%Y/%m/%d %H:%M:%S') # 追加

def main(mytimer: func.TimerRequest) -> None:
    utc_timestamp = datetime.datetime.utcnow().replace(
        tzinfo=datetime.timezone.utc).isoformat()

    if mytimer.past_due:
        logging.info('The timer is past due!')

    logging.info('Python timer trigger function ran at %s', utc_timestamp)
    print(f"現在時刻 {time_str}")             # 追加

以下のようにログが出力されます。

出力されるログ
[2021-10-24T11:07:00.028Z] Python timer trigger function ran at 2021-10-24T11:07:00.027065+00:00
[2021-10-24T11:07:00.028Z] 現在時刻 2021/10/24 20:05:29

以上になります、最後までお読みいただきありがとうございました。

Discussion