以下のQuickStartに従い、PythonでDurable Functionsを作ってみる。最近はDev Containerに興味があるので、こちらを使ってみる。
環境設定
AzureでFunctionsを作成する
これはAzure上で従量課金プランでAzure Functionsのリソースを作成するだけ。特に何も特別なこともしていないのでスキップ。
durablefunという名前で作りました。
Dev Containerの準備
PythonでAzure Functionsを開発するためのDev Containerを立ち上げる。しばやんさんのイメージを使う。
Pythonを選択。
Azure Functionsの最新サポートバージョンは3.11なのでそれにする。
初回は10分ほどかかった。こんな風になりました。
feature、拡張機能は以下のようなものが入っていた。
"features": {
"ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-azurefunctions",
"ms-azuretools.vscode-docker",
"ms-python.python"
]
}
},
Python環境
Durable Functionsの開発のために必要なものを入れる。
azure-functions
azure-functions-durable
vscode ➜ /workspaces/DurableFunction $ python -m venv venv
vscode ➜ /workspaces/DurableFunction $ venv/bin/activate
以下の記事に従って、venvとvscodeをcacheするために、mountsを追加。
{
"name": "Azure Functions (Python 3)",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"forwardPorts": [
7071,
10000,
10001,
10002
],
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"features": {
"ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-azuretools.vscode-azurefunctions",
"ms-azuretools.vscode-docker",
"ms-python.python"
]
}
},
"mounts": [
"source=vscode-extensions,target=/vscode/.vscode-server/extensions,type=volume",
{
"type": "volume",
"source": "myprj-cache-vscode",
"target": "/home/vscode/.cache"
},
{
"type": "volume",
"source": "myprj-venv",
"target": "${containerWorkspaceFolder}/.venv"
}
]
}
devcontainer.jsonを更新したので、Rebuilld Dev Containerをして、次は10秒程度で起動するようになりました。
開発
拡張機能でCreate new Azure Functionsを選択。
HTTPトリガーで作成。
Azureにサインイン
import azure.functions as func
import azure.durable_functions as df
myApp = df.DFApp(http_auth_level=func.AuthLevel.FUNCTION)
# An HTTP-triggered function with a Durable Functions client binding
@myApp.route(route="orchestrators/{functionName}")
@myApp.durable_client_input(client_name="client")
async def http_start(req: func.HttpRequest, client):
function_name = req.route_params.get('functionName')
instance_id = await client.start_new(function_name)
response = client.create_check_status_response(req, instance_id)
return response
# Orchestrator
@myApp.orchestration_trigger(context_name="context")
def hello_orchestrator(context):
result1 = yield context.call_activity("hello", "Seattle")
result2 = yield context.call_activity("hello", "Tokyo")
result3 = yield context.call_activity("hello", "London")
return [result1, result2, result3]
# Activity
@myApp.activity_trigger(input_name="city")
def hello(city: str):
return f"Hello {city}"
デバッグ
そのままローカル上でデバッグしようとしたところ、以下のエラーになった。Storage Accountに接続できていないらしい。
[2025-01-25T02:58:09.754Z] A host error has occurred during startup operation '5690128b-8db5-4f7b-bc11-69c78fb57d31'.
[2025-01-25T02:58:09.774Z] Microsoft.Azure.WebJobs.Extensions.DurableTask: Unable to resolve the Azure Storage connection named 'Storage'.
Value cannot be null. (Parameter 'provider')
[2025-01-25T02:58:09.808Z] Host startup operation has been canceled
[2025-01-25T02:58:09.836Z] Exception encountered while listening to EventStream
[2025-01-25T02:58:09.836Z] System.Private.CoreLib: The request stream was aborted. The HTTP/2 connection faulted.
[2025-01-25T02:58:09.889Z] The Azure Storage connection string is either empty or invalid. Unable to record diagnostic events, so the diagnostic logging service is being stopped.
[2025-01-25T02:58:09.889Z] Azure.Data.Tables: Value cannot be null. (Parameter 'connectionString').
[2025-01-25T02:58:09.890Z] Unable to get table reference. Aborting write operation.
が、しばやんさんが作ってくれたDev Containerのイメージでは、Azuriteの設定はしてくれている。
services:
app:
build:
context: .
dockerfile: Dockerfile
platform: linux/amd64
volumes:
- ../..:/workspaces:cached
command: sleep infinity
network_mode: service:azurite
azurite:
image: mcr.microsoft.com/azure-storage/azurite
restart: unless-stopped
調べてみたところ、Azure FunctionsをデバッグするときはVSCodeの設定もしてやる必要がある。localsettings.jsonで設定の追加が必要らしい。
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "python",
"AzureWebJobsDashboard": "UseDevelopmentStorage=true"
}
}
正常にデバッグされ、エンドポイントが表示された。
Functions:
http_start: http://localhost:7071/api/orchestrators/{functionName}
hello: activityTrigger
hello_orchestrator: orchestrationTrigger
{
"id": "583f5b37ee2a4c4b829e728a6741c220",
"statusQueryGetUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220?taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"sendEventPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/raiseEvent/{eventName}?taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"terminatePostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/terminate?reason={text}&taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"rewindPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/rewind?reason={text}&taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"purgeHistoryDeleteUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220?taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"restartPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/restart?taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"suspendPostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/suspend?reason={text}&taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA==",
"resumePostUri": "http://localhost:7071/runtime/webhooks/durabletask/instances/583f5b37ee2a4c4b829e728a6741c220/resume?reason={text}&taskHub=TestHubName&connection=Storage&code=Avb4lMcPpaQnisWEc5fm6JzHLEkPnDGgtGESzDchauzqAzFu5eA6wA=="
}
statusQueryGetUriをたたいてみると処理結果が表示された。
{
"name": "hello_orchestrator",
"instanceId": "583f5b37ee2a4c4b829e728a6741c220",
"runtimeStatus": "Completed",
"input": null,
"customStatus": null,
"output": [
"Hello Seattle",
"Hello Tokyo",
"Hello London"
],
"createdTime": "2025-01-25T03:21:19Z",
"lastUpdatedTime": "2025-01-25T03:21:34Z"
}
VSCodeの設定
pip installでライブラリをインストールしているのに、import <ライブラリ>の下に黄色い波線が表示され、インテリセンスも効かないときがあった。そういう時はDev ContainerのVSCodeの設定で、Pylance拡張機能の設定を更新したところ直った。
"Pylance" を検索し、"Python > Analysis: Type Checking Mode" を "basic" に設定。