AspireプロジェクトとAzure Functions プロジェクトを並行開発する
株式会社ジェイテックジャパン CTOの高丘 @tomohisaです。
.NET Aspireはマイクロソフトがオープンソースで開発中のクラウド対応スタックです。
Aspireには以下のような機能があります。
- AppHostから複数のWebサービス、フロントエンドなどの実行をする
- 環境変数のインジェクト機能により、相互の接続情報などを実行時に解決する
- コンテナを起動する機能も備えているため、データベースやツールを同時に起動する
- コンテナやプログラムの設定を使用して、クラウド(現状はAzureだが、将来的には他のクラウドプロバイダーも対応)へのデプロイを簡単に行う機能
要するに開発からデプロイまでをワンストップで便利にするスタックとなっています。個人的にはこの一番下のデプロイ以外の、ローカル開発を効率化する機能として現在はAspireに注目しています。もちろん、デプロイまでできるととても便利なのですが、デプロイに関しては各サービス別々にGithub Actionsで構成可能で、Aspireでローカル開発していても、お互いには環境変数の注入をしているだけなので、デプロイはAspireに関係なく各サービで行うことができるためです。
Azure QueueストレージとBlobのホスティングに関して
Azure FunctionsにAzure Queue Storageから追加する方補に関してはAspireに提供されていて以下のコードでHostに書くことが可能です。
var azurite = builder.AddAzureStorage("storageAzuriteMyName")
.RunAsEmulator(
container =>
container.UseBlobPort(11000)
.UseQueuePort(11001)
.UseTablePort(11002)
.WithVolumeMount("VolumeMount.myStorageName.Azurite", "/data"));
var blob = azurite.AddBlobs("SekibanBlob");
var queue = azurite.AddQueues("QueueStorage");
上記のコードでAzuriteを実行しつつ、Volumeにデータが保存されているので再起動しても残ったデータにアクセス可能です。
英語ブログを書いたら、Aspire開発者のDavidに指摘していただきました!ありがとうございます。
上記の書き方でblobを実行でき、それを他のプロセスに共有できるのですが、この書き方だと現状、Volumeをコンテナに生成してデータを保持することができない設定となっています。そうすると複雑な処理を行った時に、あとから内容を確認することが難しくなります。
そのため、上記のFunctionsの例にもあるのですが、Aspire内のAzureStorage機能を使わずに、普通にコンテナを起動してそれを使用する形にすることにより、希望する形で開発することができます。
//下記のコンテナを直接起動する方法は必要ない
var azurite = builder.AddContainer("myStorageName", "mcr.microsoft.com/azure-storage/azurite")
.WithVolumeMount("VolumeMount.myStorageName.Azurite", "/data")
.WithEndpoint(10000, name: "blob", hostPort: 11000)
.WithEndpoint(10001, name: "queue", hostPort: 11001)
.WithEndpoint(10001, name: "table", hostPort: 11002);
このようにすることで、好きなホストポートでBlobとQueueを起動可能です。
接続文字列が必要になるのですが開発用ストレージはセキュリティキーが固定のため以下のコードのようにして生成可能です。
var apiService = builder.AddProject<YourProjectName_ApiService>("apiservice");
.WithReference(postgres)
.WithReference(queue)
.WithReference(blob)
.WithEnvironment("Sekiban__Infrastructure", "Postgres");
このように WithEnvironment
で好きな環境変数に値を注入でき、各アプリ側では、環境変数の値は appsettings.json に上書きされて使用できるので、自由に扱うことが可能です。これにより、各アプリ側のappsettings.jsonに設定を書かなくて良くなります。
WithReference
を使用することにより、接続文字列はAddBlobs
、AddQueues
で設定した名前で設定されます。
var blob = azurite.AddBlobs("SekibanBlob");
var queue = azurite.AddQueues("QueueStorage");
Aspire + Azure Functionsに関して
.NETでAzureにデプロイするために開発する場合、WebAPIをAppServiceで構成して、遅延処理をAzure Queue Storageに送ってAzure Functionsで扱うのがシンプルな形となります。Aspireは将来的にAzure Functionsに対応することは書かれていますが、現在では対応する機能が全くない状態です。
Are Azure Functions supported in .NET Aspire?
ただ、Aspireにおける子プロセスのモデルはシンプルで基本的には以下の形となります。
- 実行ファイルをプロセスとして実行する(WebAPI, フロントエンドなどはこの方法で実行)
- コンテナを起動する (Postgres, Redisなどはこの方法で実行)
ですので、Azure Functionsのプロジェクトに関しても、実行ファイルをプロセスとして実行できれば、Aspireに簡単に追加することができることがわかります。
Azure Functionsのサポートに関しては以下のIssueで議論されています。
ここで簡易のAzure Function対応のエクステンションが紹介されていて、それを実際に使ってみた方もおられます。そのリポジトリがこちらです。
ここにある簡易のAzure Functions対応機能をローカルのプロジェクトにコピーして実現可能です。
以下のように実行可能です。
builder.AddAzureFunction<YourProjectName_Common_Functions>("functions", 7121, 7122)
.WithReference(postgres)
.WithReference(blob)
.WithEnvironment(
"AzureWebJobsStorage",
() => queue.Resource.GetConnectionString() ??
throw new InvalidOperationException("Connection string is null"));
AzureWebJobsStorage環境変数にインジェクションしたい場合、それは ConnectionStrings__ の下にはないため、WithEnvironment
を使用する必要があります。また、builder.Build().Run();
の前にはGetConnectionString
メソッドで値を取得することができません。そのため、WithEnvironmentはビルドプロセス中に呼び出されるコールバックメソッドを使用して設定します。
デバッグに関して
attach to processで実行されたプロセスのデバッグを行うことにより、macのRiderからデバッグが可能でした。
まとめ
基本的には上記のリポジトリを真似して実行可能です。
Aspireの仕組みは基本的にはシンプルでローカルで色々なものを同時実行する仕組みを提供しています。私たちの取り組むアプリでは、Azure Entra Idによる認証が外部サービスで実行されるなどの要件があるため、以下の画像のようになっています。
上記のような構成で一括実行してローカルの開発を行うため、フロントエンドの開発者、バックエンドの開発者ともに環境を作って開発を行うことができます。
正式なリリースはまだですが、正式なリリース前でもローカル実行には使えますし、デプロイ機能は使わなくても構成を作ることができるので、すでに使用可能と考えています。
また便利な機能があれば紹介したいと思います。
Discussion