こないだ Azure App Service / Functions 向けに App Configuration を使ったシンプルな Service Discovery を作ってみたのですが、 Azure Resource は全て Azure Resource Manager の API を使えば探せます。
そして各 Resource にはタグを設定できるので、こっちの方が優れている気がしたので ARM を利用する Service Discovery を実装しました。
ドキュメントは未整備なので、とりあえず軽く仕組みと使い方を紹介しておきます。
最近実装した SimpleDiscovery に対する拡張して SimpleDiscovery.AzureResourceManager
というパッケージを用意しています。SimpleDiscovery については前回のエントリを参照してください。
結局のところ、環境ごとに変わってしまう URL をそのまま扱うのではなく、識別子を使って実行時にサービスの接続先を解決することで、柔軟な依存関係を構築できるようにしたいという目的です。
今回の ARM 拡張では、その識別子に Azure Resource のタグを使ったという話です。
特定のタグが付いている Azure Resource を抽出するためには、Resource Graph と Kusto を使ってクエリを書きます。前回 Resource Graph で遊んだのはこれを実装していたからです。
実装の解説はこれぐらいにして、実際に使ってみます。現在の制約としては、サービスの接続先は App Service (Web Apps / Functions) かつ HTTPS を受け付けるようになっている必要があります。*1
サンプルとしてフロントエンドとなる ASP.NET Core MVC アプリケーションと、サービス API を提供している Azure Function という構成を用意します。この例では呼び出しは一方通行です。
呼び出される Azure Function を用意
先に API として Function を用意しておきました。ほぼテンプレートのままで、API 名だけ変更しています。
public static class SayHello { [FunctionName("SayHello")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); name = name ?? data?.name; return name != null ? (ActionResult)new OkObjectResult($"Hello, {name}") : new BadRequestObjectResult("Please pass a name on the query string or in the request body"); } }
適当な Resource Group に Azure Function を新しくデプロイして、作成した Function App もデプロイするわけですが、忘れないようにタグを追加しておきます。
タグ名は今のバージョンでは Registry
となっていますが、微妙な気がしてきたので正式版までに変える気がします。値には API のサービス名を設定しておきます。
この辺りは ARM Template を使ったデプロイを行えば、タグの追加も行えるので楽です。Azure Function 側の設定はこれでほぼ終わりです。
呼び出し側 Core MVC アプリケーションの用意
まずはサービスを呼び出す Core MVC 側に SimpleDiscovery をインストールします。
Install-Package SimpleDiscovery.AzureResourceManager -Pre
インストールが済んだら Startup
で DI に登録するコードを追加します。Managed Identity を Azure 上では使うので、基本的にコード内での設定は不要ですが、開発環境のために必要な情報を設定可能にしています。
この辺りのオプションは、正式リリースまでには Configure<TOptions>
も使えるようにします。
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSimpleDiscovery() .AddAzureResourceManager(); // 必要であれば TenantId / SubscriptionId / ResourceGroup の設定をする //.AddAzureResourceManager(options => //{ // options.TenantId = "<tenant id>"; // options.SubscriptionId = "<subscription id>"; // options.ResourceGroup = "<resource group>"; //}); services.AddHttpClient<DemoService>() .WithSimpleDiscovery(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } }
後は AddHttpClient<TClient>
と WithSimpleDiscovery
を呼び出して、サービス名で接続先を解決するように設定します。設定はこれで終わりです。
登録した DemoService
の実装は HttpClient
を使って、Function App に実装されている /api/SayHello
にリクエストを投げているだけなので省略します。
サービスを使う側ですが、コントローラを少し弄って POST で渡された名前を、そのまま DemoService
の呼び出しに使うようにしたという、非常に簡単なコードです。
public class HomeController : Controller { public HomeController(DemoService demoService) { _demoService = demoService; } private readonly DemoService _demoService; public IActionResult Index() { return View(); } [HttpPost] public async Task<IActionResult> Index(string name) { var result = await _demoService.SayHelloAsync(name); ViewBag.ApiResult = result; return View(); } }
後はビューを用意しましたが、本質的な部分ではないので省略します。今回の重要なポイントは ARM の情報を使って接続先を動的に解決して、ちゃんと通信が確立できるかという部分です。
呼び出し側のアプリケーションはこれで完成したので、既に Azure にデプロイされているサービスを使ってローカルからテストしてみたのが以下の図です。
意図したとおりに Managed Identity を使って ARM から接続先のサービスを解決できています。
動作は問題なかったので、このアプリケーションも Azure にデプロイしますが、当然ながら Managed Identity の設定が必要になるのでそこだけは注意が必要です。
アクセス先となる Function App に対して個別に IAM を設定しても良いですし、リソースグループに対して付けても良い気がします。複数のサービスがある場合には共通の User Assigned Managed Identity を作成して、それを使いまわした方が便利でしょう。
Managed Identity を設定済みの Web App にデプロイすると、ローカルと同じようにすんなり動きます。
これで環境が変わったとしても適切なタグが設定されていれば、サービスの URL を気にせずに扱えるようになりました。ちなみに同名のサービスが複数見つかった場合は、ランダムでどちらかが選ばれます。
更に Application Insights を ASP.NET Core アプリと Azure Functions で共通化しておけば、分散トレーシングもちゃんと動作するので共通化はお勧めしています。
ネットワーク周りの課題に関しては VNET Integration と Service Endpoint、そして Azure Functions の Premium Plan でほぼ解決しそうです。
これで Azure Serverless を使って Microservices Architecture を実現し、更に ARM Template で展開まで出来そうな感じがしてきました。暇な時に大きめのサンプルを書いてみたいところです。
*1:普通に App Service を作るとデフォルトで HTTPS が使えるので、特に気にする必要はないです。