TypeScriptを使用したAzure Functions: AzuriteでBlobトリガーを動かす
この実験では、TypeScriptでAzure Functionsを開発し、Azuriteを使用してBlobトリガーの動作をローカルでテストします。これにより、Azure Blob Storageでファイル作成したら動作する関数の開発をローカルで作成することができ、クラウド環境に依存せずに行うことができるようになります。
/Users/takumi/Documents/src/private_src/azure/functions/azfunc-samples/functions_with_azuriteで作業します。
今回の実験で作ったリポジトリはこちらです。
https://github.com/xiaotiantakumi/az-samples/tree/main/functions-with-azurite
Azuriteで動作させるので、今回はコンテナー (ポーリング)で作成しています。
トリガーの違いについてはこちらを参照してください。
Azure Functions の Azure Blob Storage トリガーの推奨としては、Event Gridを使ったイベント ベースのトリガーがあります。
少し宣伝させてください!Azureの試験対策本を執筆しました。
Blob Storageのイベントに反応する関数を作成
バージョン確認など
$ func –version 4.0.5455
- プロジェクト初期化時に、Node.jsランタイムとTypeScriptを選択します。
takumi@ ~/Documents/src/private_src/daily/2024-09-16/functions_with_azrite$ func init
Select a number for worker runtime:
1. dotnet
2. dotnet (isolated process)
3. node
4. python
5. powershell
6. custom
Choose option: 3
node
Select a number for language:
1. javascript
2. typescript
Choose option: 2
typescript
The new Node.js programming model is generally available. Learn more at https://aka.ms/AzFuncNodeV4
Writing package.json
Writing .funcignore
Writing tsconfig.json
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing /Users/takumi/Documents/src/private_src/daily/2024-09-16/functions_with_azrite/.vscode/extensions.json
Running 'npm install'...%
func new
この部分では、Azure Functions CLIを使用して新しい関数を作成しています。「func new」コマンドを実行し、利用可能なトリガーテンプレートのリストから選択しています。
Azure Blob Storageトリガーを選択し、新しい関数「storageBlobTrigger」を作成しました。これにより、Blob Storageのイベントに反応する関数が生成されました。
takumi@ ~/Documents/src/private_src/daily/2024-09-16/functions_with_azrite$ func new
Select a number for template:
1. Azure Blob Storage trigger
2. Azure Cosmos DB trigger
3. Durable Functions entity
4. Durable Functions orchestrator
5. Azure Event Grid trigger
6. Azure Event Hub trigger
7. HTTP trigger
8. Azure Queue Storage trigger
9. Azure Service Bus Queue trigger
10. Azure Service Bus Topic trigger
11. Timer trigger
Choose option: 1
Azure Blob Storage trigger
Function name: [storageBlobTrigger]
Creating a new file /Users/takumi/Documents/src/private_src/daily/2024-09-16/functions_with_azrite/src/functions/storageBlobTrigger.ts
The function "storageBlobTrigger" was created successfully from the "Azure Blob Storage trigger" template.
コードの内容
ここからが今回のメインの話になります。
src/functions/storageBlobTrigger.ts
このコードは、Azure FunctionsでBlob Storageからトリガーを受け取り、それに基づいて処理を行う「Storage Blob Trigger」の実装です。特定のBlobがアップロードや更新された際にこの関数が実行され、Blobデータを受け取ってログに記録します
import { app, InvocationContext } from '@azure/functions';
export async function storageBlobTrigger(
blob: Buffer,
context: InvocationContext
): Promise<void> {
context.log(
`Storage blob function processed blob "${context.triggerMetadata.name}" with size ${blob.length} bytes`
);
}
app.storageBlob('storageBlobTrigger', {
path: 'work/{name}',
connection: '',
handler: storageBlobTrigger,
});
local.settings.jsonにfunctionsで使用するの環境変数を設定します。
AzureWebJobsStorageにAzuriteの接続文字列を設定します。
Azure Functions では、Blob、Queue、Table などのストレージサービスを使用するトリガーやバインディングを設定することができます。この場合、関数がデータにアクセスするためには、Storage Accountに接続する必要があります。AzureWebJobsStorage
は、このストレージアカウントの接続情報を提供します。
Azuriteを使う場合、ローカルに対する接続文字列は決まっています。
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"AzureWebJobsStorage": "AccountName=devstoreaccount1;AccountKey=xxx;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;",
"Hoge": "AccountName=devstoreaccount1;AccountKey=xxx;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;"
}
}
ここでのポイントはmy_azurite
という部分です。my_azurite
は、Dockerコンテナ内でAzuriteを動かす際に、Dockerのカスタムネットワークでホスト名として指定されたものとなります。この設定により、他のコンテナからmy_azurite
でアクセス可能です。一般的には、接続文字列のエンドポイントはhttp://127.0.0.1:10000/
となります。
こちらを参考にしてください。
https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage#http-connection-strings
storageBlobの設定について掘り下げる
下記の設定について掘り下げてみていきたいと思います。
handlerは今回は固定となります。pathとconnectionについて深掘りします。
app.storageBlob('storageBlobTrigger', {
path: 'work/{name}',
connection: '',
handler: storageBlobTrigger,
});
まずは動作確認
実際に動作させるために、Microsoft Azure Storage Explorerを使用します。
別記事でMicrosoft Azure Storage Explorerについて書いてます。
とりあえず、workとhogeというコンテナを作成しました。
準備ができたら、workというコンテナーを作成し、その中に任意のファイルをアップロードします。
VSCodeでデバッグ実行してみましょう。ブレークポイントを置いて待ち構えます。
ターミナルでDebugger attachedになるとブレークポイントも灰色から赤くなります。
この状態でファイルをアップロードしてみます。workというコンテナーを先ほど作成したのでここにファイルアップロードしてみます。
ファイルがアップロードされ、vscodeに戻るとブレークポイントが発火しているのがわかります。
この状態でデバッグコンソールに引数で渡ってくるcontextやblobを入力してみます。
仮引数それぞれの変数にマウスカーソルを当てればツールチップで表示することもできます。
次に、別のhogeコンテナーにファイルをアップロードしてみます。
今回はブレークポイントが発火しませんでした。
というのも、下記のpathにworkコンテナーで絞る設定が入っているからでした。
app.storageBlob('storageBlobTrigger', {
path: 'work/{name}',
connection: '',
handler: storageBlobTrigger,
});
Pathについて
公式の内容はこちらが参考になります。
まず、ここではpathに何が設定できるかを確認します。
公式にも書いてますが、Blobパターン名というので指定できます。
さて、先ほどhogeコンテナーにファイルをアップロードしてトリガーが発火しませんでした。
コンテナーを横断してトリガーを発火させることができるかを調べてみました。
調べた限りでは、コンテナー (ポーリング)のトリガーではできないとなります。
もちろん、複数のエンドポイントを作成してhoge用の関数を作成することで対応することはできます。
他にもEvent Gridを使えば可能ということはわかりました。
コンテナーを横断して発火させる設定は現状無理というのがわかったので他を確認していきます。
拡張子で絞る
以下のように拡張子を指定してフィルターをかけることもできます。
app.storageBlob('storageBlobTrigger', {
path: 'work/{name}.test',
connection: '',
handler: storageBlobTrigger,
});
上記の場合、先ほどのファイルsample.txtではトリガーが発火しません。
BLOB トリガーのバインディング
こちらも合わせて参照してください。
下記のように{name}としていたところを{hoge}に変えてみます。
app.storageBlob('storageBlobTrigger', {
path: 'work/{hoge}',
connection: '',
handler: storageBlobTrigger,
});
この{hoge}は下記のように、context.triggerMetadataのkeyに出現します。
ここは先ほどnameだったのでこのキー情報が上記で定義したものとなるようです。
connectionについて
ここで個人的に一番躓きポイントは、connection stringを設定する項目ではないというところです。
例えば、以下のようにconnection stringを設定してみます。
app.storageBlob('storageBlobTrigger', {
path: 'work/{name}.test',
connection:
'AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;',
handler: storageBlobTrigger,
});
しかし、このままだとAzure Functionsの起動時にエラーが出ます。
エラー内容詳細
Azure Functions Core Tools
Core Tools Version: 4.0.6280 Commit hash: N/A +421f0144b42047aa289ce691dc6db4fc8b6143e6 (64-bit)
Function Runtime Version: 4.834.3.22875
[2024-09-28T05:00:15.638Z] Debugger attached.
[2024-09-28T05:00:15.795Z] Worker process started and initialized.
[2024-09-28T05:00:15.961Z] Microsoft.Azure.WebJobs.Host: Error indexing method 'Functions.storageBlobTrigger'. Microsoft.Azure.WebJobs.Extensions.Storage.Blobs: Storage account connection string 'AzureWebJobsAccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;' does not exist. Make sure that it is a defined App Setting.
[2024-09-28T05:00:16.004Z] Error indexing method 'Functions.storageBlobTrigger'
[2024-09-28T05:00:16.004Z] Microsoft.Azure.WebJobs.Host: Error indexing method 'Functions.storageBlobTrigger'. Microsoft.Azure.WebJobs.Extensions.Storage.Blobs: Storage account connection string 'AzureWebJobsAccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;' does not exist. Make sure that it is a defined App Setting.
[2024-09-28T05:00:16.005Z] Function 'Functions.storageBlobTrigger' failed indexing and will be disabled.
[2024-09-28T05:00:16.009Z] No job functions found. Try making your job classes and methods public. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
[2024-09-28T05:00:16.074Z] The 'storageBlobTrigger' function is in error: Microsoft.Azure.WebJobs.Host: Error indexing method 'Functions.storageBlobTrigger'. Microsoft.Azure.WebJobs.Extensions.Storage.Blobs: Storage account connection string 'AzureWebJobsAccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;' does not exist. Make sure that it is a defined App Setting.
Functions:
storageBlobTrigger: blobTrigger
For detailed output, run func with --verbose flag.
[2024-09-28T05:00:20.734Z] Host lock lease acquired by instance ID '000000000000000000000000F5594AAA'.
では実際にどうやってconnectionを使うかというとドキュメントにちゃんと書かれています。
ただ、ここに書かれている
接続文字列を含むアプリケーション設定の名前
というのが少しわかりにくいので、これが何かを説明します。
結論から言うと、local.setting.jsonに設定したキーをここに設定します。(Functionsに登録している場合はアプリケーション設定になります。)
以下のようにlocal.setting.jsonにBlobの接続文字列を設定したRemoteStorageというのを追加しました。
app.storageBlobも修正しました。
app.storageBlob('storageBlobTrigger', {
path: '0928/{name}',
connection: 'RemoteStorage',
handler: storageBlobTrigger,
});
上記状態でRemoteStorageに設定したBlob(0928コンテナ)にファイルをアップロードしてみます。
下記のようにブレークポイントが発火しました。
ところで、当初このconnectionは空文字にしていました。この場合は下記からも分かるようにAzureWebJobsStorage
が規定値として使用されます。
connection
を空のままにした場合、Functions ランタイムは、アプリ設定内のAzureWebJobsStorage
という名前の既定のストレージ接続文字列を使用します。
ID ベースの接続
こちらは下記のような設定で動作確認しました。
こちらも参考にしてください。
先ほどと変わっていない。
app.storageBlob('storageBlobTrigger', {
path: '0928/{name}',
connection: 'RemoteStorage',
handler: storageBlobTrigger,
});
local.setting.json
connectionに入れたRemoteStorageというのが<CONNECTION_NAME_PREFIX>__blobServiceUriの<CONNECTION_NAME_PREFIX>にあたります。なので以下のように設定します。
RemoteStorage__blobServiceUri
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
"AzureWebJobsStorage": "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://my_azurite:10000/devstoreaccount1;QueueEndpoint=http://my_azurite:10001/devstoreaccount1;TableEndpoint=http://my_azurite:10002/devstoreaccount1;",
"RemoteStorage__blobServiceUri": "https://<storage_account_name>.blob.core.windows.net",
"RemoteStorage__queueServiceUri": "https://<storage_account_name>.queue.core.windows.net"
}
}
次に、ターミナルでaz loginします。
これでDefault Credentialがaz loginした情報となります。
しかし、これだけだとエラーが出ます。
不思議です。というのも、az loginしたユーザはOwnerだったので。
調べてみるとこちらにも書いてますが、Ownerでは不十分なようです。
実行時に BLOB コンテナーへのアクセスを提供するロールの割り当てを作成する必要があります。 所有者のような管理ロールでは十分ではありません。
これはちょっとハマりポイントかなと思います。
Storage Blob Data Contributorに追加します。
また、この設定を入れても即時反映されないのか、しばらく待つ必要があります。なので、ちゃんと設定項目がわかっていないとハマります。設定して一旦10分くらい休憩して確認するという感じが良いでしょう。
こういう即時反映されない系って非常に混乱を招きます。
Appendix
devcontainerの準備
過去にも似たような記事を書いていることが判明しました。。。
なのでApendixにしました。
基本的にDevcontainerで作業します。ここでAzuriteのコンテナーも用意することになります。
docker-compose.yml、devcontainer.json、Dockerfileは、Visual Studio CodeのDev Containers機能を使用して、一貫性のある開発環境を構築するために用意されています。主な目的は以下の通りです:
- 一貫した開発環境の提供: チーム全体で同じ開発環境を使用することができ、「自分の環境では動作するが、他の人の環境では動作しない」といった問題を回避できます。
- 簡単なセットアップ: 新しいメンバーがプロジェクトに参加した際、複雑な環境設定を行う必要がなく、すぐに開発を始めることができます。
- Azure Functionsの開発環境: Node.jsとAzure Functions Core Toolsが事前にインストールされた環境を提供し、Azure Functionsの開発をすぐに始められるようにしています。
- Azuriteエミュレーターの統合: Azure Storageのエミュレーターを含めることで、ローカルでAzure Storageを使用する開発とテストが可能になります。
- VS Code拡張機能の自動インストール: 開発に必要な拡張機能(Azure Functions、ESLint、Jestなど)を自動的にインストールし、開発体験を向上させています。
これらの設定により、開発者はプロジェクトのクローンを作成し、VS CodeでDev Containerを開くだけで、すぐにAzure Functionsの開発を始めることができる環境が整います。
コンテナーで再度開くとすれば、コンテナーの中で作業するような感じになります。
docker-compose.yml:
このdocker-compose.ymlファイルは、Azure Functions開発環境とAzuriteエミュレーターを含む開発環境を設定しています。主な内容は以下の通りです:
- app サービス: Azure Functions アプリケーションを実行するためのコンテナ。Dockerfileを使用してビルドされ、ホストのワークスペースをマウントしています。
- azurite サービス: Azure StorageエミュレーターであるAzuriteを実行するコンテナ。ポート10000、10001、10002をホストにマッピングしています。
- ネットワーク設定: 両サービスは’my_sample_network’という名前のブリッジネットワークに接続されています。
この設定により、ローカル開発環境でAzure FunctionsとAzure Storageの機能をAzuriteでエミュレートすることができます。
services:
app:
build:
context: .
dockerfile: Dockerfile
platform: linux/amd64
networks:
- my_sample_network
container_name: my_app
volumes:
- ../..:/workspaces:cached
command: sleep infinity
azurite:
image: mcr.microsoft.com/azure-storage/azurite
restart: unless-stopped
networks:
- my_sample_network
container_name: my_azurite
ports:
- 127.0.0.1:10000:10000
- 127.0.0.1:10001:10001
- 127.0.0.1:10002:10002
networks:
my_sample_network:
driver: bridge
devcontainer.json
devcontainer.json
は、VS Codeで開発環境をコンテナ上に構築するための設定ファイルです。Azure CLI
やAzure Functions Core Tools
などのツールがインストールされ、ポート7071などがフォワードされます。VS Codeの拡張機能や設定も含まれており、PrettierでのコードフォーマットやJestでのテストが簡単に実行可能です。postCreateCommand
で依存パッケージも自動インストールされます。
{
"name": "Azure Functions (Node.js)",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces",
"forwardPorts": [7071, 10000, 10001, 10002],
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {}
},
"customizations": {
"vscode": {
"settings": {
"extensions.verifySignature": false,
"jest.runMode": "deferred"
},
"extensions": [
"ms-azuretools.vscode-azurefunctions",
"dbaeumer.vscode-eslint",
"firsttris.vscode-jest-runner",
"Orta.vscode-jest",
"GitHub.copilot",
"esbenp.prettier-vscode",
"VisualStudioExptTeam.vscodeintellicode",
"oderwat.indent-rainbow"
]
},
"settings": {
"files.encoding": "utf8",
"files.eol": "\n",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"workbench.iconTheme": "material-icon-theme",
"": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
},
"postCreateCommand": "npm install"
}
Dockerfile
FROM mcr.microsoft.com/devcontainers/javascript-node:1-20-bookworm
bookwormはDebianの最新安定版ディストリビューションで、このイメージにはNode.js 20がインストールされています。この設定により、Node.jsを使用するJavaScriptの開発環境がDebianベースのコンテナ上で提供されます。
デバッグ構成
以下を設定することでvscodeでデバッグできるようになります。
/.vscode/launch.json
基本的にはDebug Azure Functionのデバッグ構成を使用します。
「Attach to Node Functions」について この構成では、すでに実行中のNode.js Azure Functionsにデバッガを接続するために使用されます。関数アプリがデバッグモードで起動している場合、port
で指定されたポート (ここは使用しているポートによって変更してください) を通じて接続が可能です。ポート番号が他のプロセスと競合することがあるため、特にローカルで複数のアプリケーションが動作している場合は注意が必要です。
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Azure Function",
"request": "launch",
"runtimeArgs": ["run-script", "start"],
"runtimeExecutable": "npm",
"skipFiles": ["<node_internals>/**"],
"type": "node",
"console": "integratedTerminal"
},
{
"name": "Attach to Node Functions",
"type": "node",
"request": "attach",
"port": 7071,
"preLaunchTask": "func: host start"
}
]
}
ディスカッション
コメント一覧
まだ、コメントがありません