最近、Azure Functionsのお勉強をチマチマと始めました。色々と分からないことが多かったのでお勉強メモをまとめて記します。
どこまで続くかわからないお勉強メモ。今日は5回目です。今回はRubyを使ったTimer Triggerの実装です。過去のものは以下を参照。
- Azure Functionsのお勉強メモ(1)TypeScriptでチュートリアルを実施する - miyohide's blog
- Azure Functionsのお勉強メモ(2)Spring Cloud FunctionでHTTP Triggerを実装する - miyohide's blog
- Azure Functionsのお勉強メモ(3)Spring Cloud FunctionでTimer Triggerを実装する - miyohide's blog
- Azure Functionsのお勉強メモ(4)RubyでHTTP Triggerを実装する - miyohide's blog
概要
今回の実装においてはカスタムハンドラーというものを使います。
前回の記事で実装したRubyを使ったHTTP Triggerの実装は、上記ドキュメント内にある「HTTPのみの関数」の機能を利用したものになります。
仕組みとしてはトリガーが実行されると、Azure Functionsの基盤がHTTP POSTリクエストを飛ばすようです。実際の処理はHTTP POSTリクエストに対応するメソッドで実装すれば良さそうです。
では実装してみます。
実装
実装は、前回のコンテナを使用したものを拡張します。
Timer Triggerの設定ファイルを作成する
以下のコマンドを実行してTimer Triggerの設定ファイルを作成します。
func new --name TimerExample --template "Timer trigger"
TimerExample
というディレクトリができ、その中にfunction.json
が作成されます。schedule
の部分を適当なものに編集しておきます(ここでは10秒おきに実行する)。
{ "bindings": [ { "name": "myTimer", "type": "timerTrigger", "direction": "in", "schedule": "*/10 * * * * *" } ] }
アプリケーションの実装
Timer Triggerを実行するHTTP POSTリクエストを実装します。こんな感じに実装しました。
post '/TimerExample' do logger.info "----- Timer Schedule Start" header_data = request.env body_data = JSON.parse(request.body.read) content_type :json data = { "Outputs" => { "res" => { "body" => "abc" } }, "Logs" => [header_data.to_s, body_data.to_s], "ReturnValue" => "hogehoge" } logger.info "----- Timer Schedule End" data.to_json end
単純にログを出力して、適当なデータを返すものです。戻り値の中にあるLogs
に指定したものは実行時のログに出力されるようです。これは後ほどの実行ログをみて確認してみます。
開発環境の準備
Azure FunctionsにおいてはTimer Triggerを実行する場合、Azure Storageを用意する必要があります。開発環境においてはエミュレーターを利用すればOKです。
今回、アプリ自身もコンテナ化しているので、docker-compose.yml
を準備しておきます。以下のものを作成しました。
version: '3' services: storage: image: mcr.microsoft.com/azure-storage/azurite ports: - "10000:10000" - "10001:10001" app: build: . ports: - "8080:80" depends_on: - storage environment: - AzureWebJobsStorage
環境変数の設定には、Docker Composeの機能を利用して.env
ファイルを用意します。この機能の詳細は以下のドキュメントを参照してください。
設定値は以下のようにしました。
AzureWebJobsStorage="UseDevelopmentStorage=true;DevelopmentStorageProxyUri=http://storage"
DevelopmentStorageProxyUri
に設定するURLはDocker Composeのサービス名を指定しておきます。
Dockerfileも修正し、環境変数AzureWebJobsStorage
を見るようにします。一番最後の行にENV AzureWebJobsStorage=$AzureWebJobsStorage
を追加しました。
FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice ENV AzureWebJobsScriptRoot=/home/site/wwwroot \ AzureFunctionsJobHost__Logging__Console__IsEnabled=true RUN apt update && \ apt install -y \ git \ autoconf \ bison \ build-essential \ libssl-dev \ libyaml-dev \ libreadline6-dev \ zlib1g-dev \ libncurses5-dev \ libffi-dev \ libgdbm6 \ libgdbm-dev \ libdb-dev \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth=1 https://github.com/rbenv/ruby-build && PREFIX=/usr/local ./ruby-build/install.sh && rm -rf ruby-build RUN ruby-build 2.7.2 /usr/local WORKDIR /home/site/wwwroot COPY Gemfile /home/site/wwwroot/ RUN bundler install COPY . /home/site/wwwroot/ ENV AzureWebJobsStorage=$AzureWebJobsStorage
実行
docker-compose up --build
で実行します。以下のようなログが出力されます。
Starting OpenBSD Secure Shell server: sshd. info: Host.Triggers.Warmup[0] Initializing Warmup Extension. (省略) info: Host.Triggers.Timer[5] The next 5 occurrences of the 'TimerExample' schedule (Cron: '0,10,20,30,40,50 * * * * *') will be: 04/11/2021 08:20:50+00:00 (04/11/2021 08:20:50Z) 04/11/2021 08:21:00+00:00 (04/11/2021 08:21:00Z) 04/11/2021 08:21:10+00:00 (04/11/2021 08:21:10Z) 04/11/2021 08:21:20+00:00 (04/11/2021 08:21:20Z) 04/11/2021 08:21:30+00:00 (04/11/2021 08:21:30Z) info: Host.Startup[413] Host started (237ms) info: Host.Startup[0] Job host started fail: Host.Function.Console[0] == Sinatra (v2.1.0) has taken the stage on 36205 for production with backup from Puma info: Host.Function.Console[0] Puma starting in single mode... info: Host.Function.Console[0] * Puma version: 5.2.2 (ruby 2.7.2-p137) ("Fettisdagsbulle") (省略)
The next 5 occurrences of the ...
という行でTimer Triggerの実行スケジュールが出力されています。時間がくると、以下のようにログが出力されます。
info: Function.TimerExample[1] Executing 'Functions.TimerExample' (Reason='Timer fired at 2021-04-11T08:20:50.0124322+00:00', Id=76abc40f-b7ff-4a2e-ae7d-fe64ff413f66) fail: Host.Function.Console[0] I, [2021-04-11T08:20:50.066834 #45] INFO -- : ----- Timer Schedule Start fail: Host.Function.Console[0] I, [2021-04-11T08:20:50.069203 #45] INFO -- : ----- Timer Schedule End fail: Host.Function.Console[0] 127.0.0.1 - - [11/Apr/2021:08:20:50 +0000] "POST /TimerExample HTTP/1.1" 200 4103 0.0029 info: Function.TimerExample.User[0] {"rack.version"=>[1, 6], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>true, "rack.multiprocess"=>false, "rack.run_once"=>false, "SCRIPT_NAME"=>"", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"puma 5.2.2 Fettisdagsbulle", "GATEWAY_INTERFACE"=>"CGI/1.2", "REQUEST_METHOD"=>"POST", "REQUEST_PATH"=>"/TimerExample", "REQUEST_URI"=>"/TimerExample", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"127.0.0.1:36205", "HTTP_X_AZURE_FUNCTIONS_HOSTVERSION"=>"3.0.15417.0", "HTTP_X_AZURE_FUNCTIONS_INVOCATIONID"=>"76abc40f-b7ff-4a2e-ae7d-fe64ff413f66", "HTTP_USER_AGENT"=>"Azure-Functions-Host/3.0.15417.0", "HTTP_TRANSFER_ENCODING"=>"chunked", "CONTENT_TYPE"=>"application/json; charset=utf-8", "puma.request_body_wait"=>1, "CONTENT_LENGTH"=>"234", "SERVER_NAME"=>"127.0.0.1", "SERVER_PORT"=>"36205", info: (省略) Function.TimerExample.User[0] {"Data"=>{"myTimer"=>{"Schedule"=>{"AdjustForDST"=>true}, "ScheduleStatus"=>nil, "IsPastDue"=>false}}, "Metadata"=>{"sys"=>{"MethodName"=>"TimerExample", "UtcNow"=>"2021-04-11T08:20:50.0482506Z", "RandGuid"=>"3dcd6dd9-263c-4b8d-803c-3e663a09f224"}}} info: Function.TimerExample[2] Executed 'Functions.TimerExample' (Succeeded, Id=76abc40f-b7ff-4a2e-ae7d-fe64ff413f66, Duration=73ms) (省略)
アプリ内の戻り値でLogs
にリクエストヘッダーと本文を指定しているため、その内容が表示されています。今回はサンプル実装ですが、ここにある情報を使うことでいろいろな実装ができそうです。
Sinatraで出力しているログがfail
扱いになっているのはrack.errors
がSTDERRに指定されているためかと思われます。
ソース
ここまでのソースです。