Azure FunctionsのBlobトリガーを使ってみる:Key Vaultを使った安全な接続
構成図
Azure FunctionsのBlobトリガーを使ってみました。
Blobの接続文字列の管理にはKeyVaultのシークレットを使用して、セキュアに実行できるようにしてみます。
ローカル環境でFunctionsを実行する
前提
各種ツールは既にインストール済みの前提で進みます。
- .NET: 7.0.404
- Azure CLI: 2.54.0
- Azure Functions Core Tools: 4.0.5455
- Azure Storage Explorer
事前準備
Azuriteのインストール
Azuriteは、BlobストレージをはじめとするAzure Storageサービスをローカル環境でテストするためのツールです。
ローカル環境での検証に使用しますので、インストールします。
$ npm install -g azurite
$ azurite -v
3.29.0
ローカル環境で関数を実行する
関数アプリを作成する
下記のコマンドで関数アプリ用のプロジェクトを作成します。
# Functionsプロジェクトの基本的なファイルを作成します
$ func init --worker-runtime dotnet
$ dotnet build
# 関数ファイルを作成します
$ func new --name ProcessFile --template BlobTrigger
上記コマンドを実行すると、次のような構成のディレクトリやファイルが作成されます。
--name
で指定した名前で関数ファイルが作成されています(ProcessFile.cs
)。
関数ファイルの編集
作成されたProcessFile.cs
を次のように編集します。
using System.IO;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace func_app
{
public class ProcessFile
{
[FunctionName("ProcessFile")]
public async Task Run(
[BlobTrigger("input/{name}", Connection = "TestProjectStorage")] Stream inputBlob,
string name,
[Blob("output/{name}", FileAccess.Write, Connection = "TestProjectStorage")] Stream outputBlob,
ILogger log)
{
log.LogInformation($"Blobファイルの処理を開始します\n Name:{name} \n Size: {inputBlob.Length} Bytes");
await inputBlob.CopyToAsync(outputBlob);
}
}
}
今回はFunctionsのBlobトリガーの動作確認が目的です。
そのため、関数の内容自体はBlobストレージのinputコンテナ(ディレクトリ)にアップロードされた画像をoutputコンテナにコピーするだけの簡単な内容です。
設定ファイルの編集
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"TestProjectStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}
- AzureWebJobsStorage: 関数アプリと関連付けするストレージアカウントの接続文字列
- TestProjectStorage: 関数アプリ内で使用するストレージアカウントの接続文字列
【補足】AzureWebJobsStorageとは
前提として、Functionsはストレージアカウントと関連付けされている必要があります。Functionsの操作にはストレージアカウントが必須であり、それがないとFunctionsは実行されないからです。
具体的には、トリガーの管理や関数の実行のログ記録などの用途で関連付けされたストレージアカウントが使用されます。
AzureWebJobsStorage
には、このFunctionsに関連付けされたストレージアカウントの接続文字列が指定されます。
ローカル環境で関数を実行する
まず、azuriteを起動しておきます。
$ azurite
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
続いて、関数を実行します。
func start
コマンドで関数を起動しておきます。
$ func start
....
Functions:
ProcessFile: blobTrigger
For detailed output, run func with --verbose flag.
関数が起動した状態で、対象コンテナ(input
)に画像(SampleImage.png
)をアップロードします。
画像アップロードはAzure Storage Explorer
から行います。
画像をアップロードすると、BlobTriggerが発火しました。
関数が実行された旨のログも出力されています。
[2024-01-20T21:11:16.570Z] Executing 'ProcessFile' (Reason='New blob detected(LogsAndContainerScan): input/SampleImage.png', Id=2e23e99d-1c40-4091-87e4-9e6407e5d57d)
[2024-01-20T21:11:16.570Z] Trigger Details: MessageId: 6f2759a0-4281-4e2f-ae42-f71bddd4cf4d, DequeueCount: 1, InsertedOn: 2024-01-20T21:11:16.000+00:00, BlobCreated: 2024-01-20T21:11:16.000+00:00, BlobLastModified: 2024-01-20T21:11:16.000+00:00
[2024-01-20T21:11:16.575Z] Blobファイルの処理を開始します
[2024-01-20T21:11:16.575Z] Name:SampleImage.png
[2024-01-20T21:11:16.575Z] Size: 299338 Bytes
[2024-01-20T21:11:16.627Z] Executed 'ProcessFile' (Succeeded, Id=2e23e99d-1c40-4091-87e4-9e6407e5d57d, Duration=102ms)
Storage Explorerに戻ってみると、output
コンテナが作成され、input
にアップロードした画像がコピーされていることが確認できます。
Azure上で実行する
ローカル環境での動作確認が済みましたので、続いて、この関数アプリをAzure上で実行していきます。
そのために、次のリソースを作成します。
- Azure Functions
- Azure Blob Storage
- Azure Key Vault
※ リソース作成は、全てAzure CLI
で行います。
Blob Storageを作成する
まずは、ストレージアカウントを作成します。
az login
# リソースグループの作成
az group create --location japaneast --name test-func-project-rg
# ストレージアカウントの作成
az storage account create \
--resource-group test-func-project-rg \
--name testprojectblob20240121 \
--location japaneast \
--sku Standard_LRS
Portalで確認すると、該当のストレージアカウントが作成されています。
Functionsを作成する
続いて、Functionsです。
Functionsと関連付けするストレージを作成する
前述の通り、Functionsはその実行のためにストレージアカウントが必要です。Functions作成時には、関連付けするストレージアカウントの指定が必須となります。
そのため、Functionsを作成する前にFunctionsと関連付けするストレージを作成しておきます。
※ PortalからFunctionsを作成する場合、このストレージは自動的に生成されます。
az storage account create \
--resource-group test-func-project-rg \
--name testfuncstorage20240121 \
--location japaneast \
--sku Standard_LRS
Functionsを作成する
前項で作成したtestfuncstorage20240121
をストレージアカウントに指定して、Functionsを作成します。
az functionapp create \
--name test-func-20240121 \
--resource-group test-func-project-rg \
--storage-account testfuncstorage20240121 \
--consumption-plan-location japaneast \
--runtime dotnet \
--functions-version 4
Portalで確認すると、Functionsが作成されています。
App ServiceプランがY1
になっていたら、間違いなく従量課金プランです。
Functions作成時にApp Serviceプランを明示的に作成する必要はありませんが、内部的に「Y1」と呼ばれるApp Serviceプランが作成されます。従量課金プランのFunctionsはこのApp Serviceプランの下で実行されます。
作成されたApp Serviceプランの詳細はPortalからも確認できます。
Key Vaultでシークレットを作成する
最後にKey VaultとBlobの接続文字列のシークレットを作成します。
Key Vaultリソースを作成する
az keyvault create \
--name kv-test-func-project \
--resource-group test-func-project-rg
シークレットを作成する
先ほど作成したBlobストレージtestprojectblob20240121
の接続文字列を保持するシークレットを作成します。
シークレットは下記コマンドで作成できます。
az keyvault secret set \
--vault-name kv-test-func-project \
--name testprojectblob20240121-account-key \
--value "{blobストレージの接続文字列}を指定"
Blobの接続文字列は、Portalから確認できます。
Functionsの構成を更新する
FunctionsがBlobストレージにアクセスできるようにするため、Functionsの環境変数にTestProjectStorage
を追加します。
TestProjectStorage
には、Functions内で使用するストレージアカウントの接続文字列のKey Vaultシークレット(testprojectblob20240121-account-key
)を指定します。
まず、シークレットのURI(シークレットの識別子)をPortalから確認します。
続いて、Functionsの環境変数にTestProjectStorage
を登録していきます。
Functionsのアプリ設定でKey Vaultを参照する場合、以下の形式になります。
@Microsoft.KeyVault(SecretUri=シークレットのURI)
TestProjectStorage
: @Microsoft.KeyVault(SecretUri=シークレットのURI)
という形式で環境変数を登録します。
Functionsのデプロイを行う
先ほどローカルで実行した関数アプリをデプロイします。
デプロイは、Azure Functions Core Toolsの以下のコマンドで実行できます。
func azure functionapp publish <作成したFunctionsの名前>
az login
# デプロイしたい関数アプリがあるディレクトリに移動してコマンドを実行します
cd func-app
# デプロイ
func azure functionapp publish test-func-20240121
Setting Functions site property 'netFrameworkVersion' to 'v6.0'
MSBuild のバージョン 17.7.4+3ebbd7c49 (.NET)
復元対象のプロジェクトを決定しています...
復元対象のすべてのプロジェクトは最新です。
func-app -> /Users/******/projects/azure/func-app/bin/publish/func-app.dll
ビルドに成功しました。
0 個の警告
0 エラー
経過時間 00:00:02.34
Getting site publishing info...
[2024-02-08T23:22:32.748Z] Starting the function app deployment...
Creating archive for current directory...
Uploading 5.53 MB [###########################################################]
Upload completed successfully.
Deployment completed successfully.
[2024-02-08T23:23:29.113Z] Syncing triggers...
Functions in test-func-20240121:
ProcessFile - [blobTrigger]
PortalでFunctionsを見ると、ProcessFile
関数がデプロイされたことを確認できます。
FunctionsがKey Vaultに安全にアクセスできるようにする
先ほど、Functionsの環境変数にシークレットの参照先を登録しました。しかし、これだけではFunctionsがKeyVaultのシークレットを参照することはできません。
KeyVaultへのアクセスには、適切なAzureADの認証が必要だからです。
ここでは、この認証プロセスを簡略化&セキュアにするため、マネージドIDを使います。
※ 構成図でいうと、赤枠の部分の話です。
具体的には、下記の2つを実施します。
- FunctionsのマネージドIDを有効化する
- マネージドIDに対して、KeyVaultへのアクセス権を付与する
これらを行うことで、FunctionsがKeyVaultのシークレット値(今回はBlobの接続文字列)を取得できるようになります。
FunctionsのマネージドIDを有効化する
該当のFunctions
> 設定
> ID
を開きます。
システム割り当て済み
を選択し、状態をON
にして保存します。
今有効化したFunctionsのマネージドIDは、Microsoft Entra ID
> 管理
> エンタープライズアプリケーション
で確認できます。
マネージドIDに対して、KeyVaultへのアクセス権を付与する
作成したKeyVaultのページに行き、アクセス制御 (IAM) > 追加 > ロールの割り当ての追加 を選択します。
ロールに、キー コンテナー シークレット ユーザー
を選択します。
似たようなロールに、Key Vaultリーダー
やキー コンテナー閲覧者(Key Vault Reader)
がありますが、これらのロールは、シークレットの参照ができませんので要注意です。
メンバーのアクセス割り当て先にマネージドIDを選択し、「メンバーを選択する」を押します。
先ほど有効化したFunctionsのマネージドIDを選択します。
「レビューと割り当て」を押します。
更新完了後、アクセス制御(IAM)を表示すると、キーコンテナー シークレットユーザー
ロールがFunctionsのマネージドIDに割り当たっていることを確認できます。
動作確認
ここまでの手順でAzureリソースの準備が完了しました。
Blobに画像をアップロードして、Functionsが動作するか確認してみます。
動かない
本来は、inputコンテナに画像をアップロードしたらoutputコンテナに同じ画像が複製されるはずです。
が、outputコンテナが作成されません😥
まず、Application insightsでログを確認してみます。
関数アプリ > 監視 > ログのページに行き、下記のクエリを実行します。
ちゃんとBlobトリガーでFunctionsが動作していれば、requestsにログが残ります。
しかし、BlobトリガーでFunctionsが動いたことを示すログは見当たりません。そもそもBlobトリガーが成功していない可能性が高そうです。
接続文字列の確認
BlobとFunctionsの接続に問題がありそうなことが分かったので、Functionsの環境変数に設定した接続文字列(TestProjectStorage
)に誤りがないかを確認します。
この環境変数は、Key Vaultのシークレットを参照する形としていました。
どうやらここが怪しそうなので、試しにBlobストレージの接続文字列を直接指定してみます。
構成を変更したので、Functionsを再起動します。
この後、Blobストレージを見てみると、outputコンテナが作成されました。inputコンテナにアップロードした画像が複製されています。
リクエストのログも出ています。
原因はKey Vaultのアクセス許可モデル
接続文字列を直接指定したら想定通りの挙動となったので、原因はKey Vaultのシークレットの参照が上手くいっていないことにあるのが確定しました。
「おそらくシークレットに登録した値を間違えたか、付与したロールを間違えたのだろう」と思い、シークレット値、シークレットの参照方法、付与したロールを確認しましたが、いずれも誤っている箇所はありません🤔
そこで、改めてドキュメントを読んでみると、
Key Vault で Azure RBAC アクセス許可を有効にする
ありました。
Key Vaultでロールベースのアクセス制御を使用する場合、アクセス許可モデルをコンテナーのアクセス ポリシー から Azureロールベースのアクセス制御に変更する必要があるようです。
規定ではコンテナーのアクセス ポリシーが選択されていました。
コンテナーのアクセス ポリシーが選択されている場合、このKeyVaultへのアクセス制御は「アクセスポリシー」で行われます。そのため、ロールベースのアクセス制御の設定をしても効果がないようです。
これをAzure ロールベースのアクセス制御に変更します。
改めて、inputコンテナに画像をアップロードします。
すると、outputコンテナに画像が複製されました🎉
今回掛かった料金
総額7.65円でした。
触ってみた程度の用途であれば、懐が痛むことはなさそうです😄
Blob Storageは使用したストレージ量に応じて課金されますので、画像の消し忘れにはご注意ください。今回は、基本的に最安プランを選択しました。
Discussion