App Configurationの動的な構成の設定と動作確認

Azure

皆さんはAzureサービスの1つであるApp Configueationをご存じですか。アプリの設定や構成情報を一元管理するためのサービスです。アプリはApp Configurationから設定値を取得して利用します。設定値をコードに直接記載すると、変更のたびにアプリのデプロイが必要になったり、変更を忘れて開発用のまま本番にリリースしてしまうなんてことも起こり得ます。そのような設定値をApp Configurationで管理することで、変更に強いアプリを作成できます。

そんな便利なサービスですが、設定値を変更したときにアプリには自動的にその更新を反映してほしいですよね。App Configurationには動的な構成と呼ばれる機能があり、この機能を使用することで設定値の変更を動的にアプリに反映することができます。

今回はこの動的な構成の設定方法と、具体的な挙動についてみていきます。

結論

  • 動的な構成を利用することで、自動的にアプリが最新の設定値を取得できる
  • 動的な構成は.NET SDKを利用して簡単に設定できる
  • 設定値の更新確認はキャッシュ期間で自由に設定できる
  • キャッシュが有効な間はキャッシュデータを利用するため、App Configurationへのリクエストは行われない

環境

基本情報

言語.NET 6(6.0.413)
関数の言語isolated(分離ワーカープロセス)
App ConfigurationFree
OSWindows 11

App Configurationに格納するデータ

キー備考
Test:ChangeKeyv1.0変更検知のための設定値。更新する際はバージョンを上げる。
Test:AlertMessageAlert実際にアプリが使用する設定値。アプリはメッセージを返却する。

App Configurationの動的な構成とは

例えば、アプリ起動時に設定値をApp Configurationから取得するとします。その後、設定値を変えたくなった時にアプリにその更新を反映させるにはどうすればいいでしょうか。まずは、1リクエストごとにApp Configurationから設定値を取得する方法が考えられます。しかし、この方法ですとApp Configurationへのリクエスト数が多くなり、レート制限やコスト面で問題が発生する可能性があります。

そこで、定期的にApp Configurationに設定値を確認しに行き、変更があれば最新版を取得します。こうすることで、リクエスト数を減らすことができてコストも最適化できます。この実装を独自に作成するのは大変ですが、.NET SDKを利用することで簡単に実装できます。

動的な構成は、定期的にアプリからApp Configurationにデータを確認する「ポーリングモデル」とApp Configurationの変更を検知してアプリに伝える「プッシュモデル」の2種類方法がありますが、今回紹介するのは前者の「ポーリングモデル」になります。

動的な構成の設定方法

Azure Functions(isolated)における動的な構成は次の公式ドキュメントの通りに行えば問題なく実装できます。

下記に私が作成したProgram.csのコードを記載します。6~12行目で、App Configurationからデータを取得する設定を記述します。今回は簡単に接続文字列でアクセスしますが、本番ではよりセキュアなAAD認証が良いと思います。

Registerメソッドの第一引数には変更を検知するためのApp Configurationのキーを指定します。今回はChangeKeyを変更検知キーとして登録しているため、Test:AlertMessageキーの値だけを変えても、アプリは変更されたことを検知しません。Changekeyも同時に更新する必要があります。第二引数には「refreshAll: true」を指定していますが、ChangeKeyが変更された場合、他の設定値もすべて取得するためのものです。

さらにSetCacheExpirationメソッドにてキャッシュする期間を指定します。今回は3分に設定しているため、3分間はキャッシュされた情報を使うことになります。

16行目以降の詳細は割愛しますが、DIのための設定やデータ更新のためのミドルウェアの設定などを行っています。

    public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureAppConfiguration(builder =>
            {
                builder.AddAzureAppConfiguration(options =>
                {
                    options.Connect(appcs_con_string)
                        .ConfigureRefresh(ro =>
                        {
                            ro.Register("Test:ChangeKey", refreshAll: true)
                              .SetCacheExpiration(TimeSpan.FromMinutes(3));
                        });
                });
            })
            .ConfigureServices(s =>
            {
                s.AddAzureAppConfiguration();
            })
            .ConfigureFunctionsWorkerDefaults(app =>
            {
                app.UseAzureAppConfiguration();
            })
            .Build();
        host.Run();
    }

1つ注意点を上げるとすると、16行目のConfigureServicesと20行目のConfigureFunctionsWorkerDefaultsは公式ドキュメント通りの順番で設定します。UseAzureAppConfigurationメソッドの実装の中にもコメントで先にAddAzureAppConfigurationが実行されていないとエラーになる旨が記載されています。

もし順番を逆に設定すると以下のエラーが発生します。

Unhandled exception. System.InvalidOperationException: Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddAzureAppConfiguration()' in the application startup code.

動作確認

それでは実際に動かしてみます。ついでに、App Configurationのログも同時に確認してApp ConfigurationとAzure Functions間のリクエストについても見ていきます。

Functions側の動作

30秒おきにAzure Functionsへリクエストを送るカスタムアプリを実行します。その途中でApp Configurationのデータを更新します。「Test:AlertMessageをAlert!!!」に「Test:ChangeKeyをv1.1」に更新しました。設定値の更新は15:54:30頃に実施しています。

データ更新後、約2分間は更新前のメッセージが表示され続けています。その後更新されたメッセージが出力されるようになりました。App Configurationの設定値を更新後、しばらくはキャッシュデータを使用していることが分かります。

App Configurationへのリクエスト確認

App ConfigurationへのリクエストをHttpRequestログで見てみると、3分おきにGETリクエストを受信していることが分かります。この3分はSetCacheExpirationメソッドで設定したキャッシュ期間になります。

6時55分や7時01分のログを見ると、ステータスコードが304でHit数が1となっています。これは変更検知キーを確認しに行って変更がなかったことを示しています。RequestURIを見ても変更検知キーである「Test:ChangeKey」をGETしていることが分かります。また公式ドキュメントでも304はNotModifiedであると記載されています。

一方、6時58分のリクエストではステータスコードが200でHit数が2になっています。変更検知キーが更新されていたためすべての設定値(ChangeKeyとAlertMessageの2つ)を再取得したことが分かります。

6時51分のリクエストは、Azure Functions起動時のリクエストです。Hit数は1になっていますが、RequestURIを確認すると、6時55分以降のリクエストと異なりこちらは「ラベルなしかつすべてのキーを取得するというクエリ」が実行されていることが分かります。Hit数が2ではなく1であるのは、あくまでクエリの結果、リストが1件取得できたことを表しているようです。実際のところ、設定値自体は2件とも問題なく取得できています。試しにAzure CLIから同様のクエリを投げてみたところ、ログではHit数が1と表示されるが設定値は2件とも取得できており、同じ結果となりました。

ちなみに、コンソールの結果とログとの間で時間のずれがあります(ログからは6時58分18秒頃に更新された設定値を取得しているが、コンソールでは6時57分55秒時点で、すでに変更後の設定値が返却されている)。検証環境で時間のずれがあるのかもしれません。しかしそれ以外は概ね期待していた通りの動作が確認できました。

まとめ

設定値をコードから分離することは、モダンなアプリを作成する12のベストプラクティスをまとめた「The Twelve-Factor App」にも記載されています。Twelve-Factor Appでは環境変数に設定を格納すると記載されていますが、App Configurationも1つの選択肢となると思います。

また、動的な構成を利用することで、設定値を更新した後にアプリが自動的に最新の値を取得できるようになります。変更確認の頻度はキャッシュの設定期間で指定します。キャッシュが有効な間はApp Configurationへのリクエストは行われずに、キャッシュからデータを取得していました。キャッシュ期間が短いとApp ConfigurationへのGETリクエスト数が多くなることで、App Configurationのレート制限に達したりコストが上昇する懸念があります。一方、キャッシュ期間が長いと変更がすぐにアプリに適用されないデメリットがあります。キャッシュ期間についてはアプリの特性に合わせて試行錯誤を重ねながら設定する必要があります。

いずれにしても、動的な構成を用いることで、安全にかつ簡単に設定値を外部から変更してアプリに適用することができます。

コメント