しばやん雑記

ASP.NET Core とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure App Configuration を一通り試したのでメモ

今朝出てきた Azure の新しいアプリケーション向けサービスの Azure App Configuration が、今持ってる課題をいい感じに解決してくれそうなので、プレビューのうちに一通り触っておきました。

普通は ASP.NET Core の Configuration Provider や ASP.NET の Configuration Builder を使って触るはずなので、基本的な部分はドキュメントだけで十分です。なので特に触れません。

REST API のドキュメントは GitHub に用意されていました。Provider が叩いてる API なので、よくわからない設定や挙動があった場合はこっちを読めば大体解決します。Consistency Model の話は興味深いですね。

App Configuration で解決できそうな課題として、複数の App Service や Azure Functions を管理している時に App Settings がバラバラになって事故りやすいという問題です。特に複数リージョンにデプロイしている場合や、本番とステージングなど複数持っている場合などでは簡単に事故ります。

これまでも Key Vault を使うことで同じように解決できますが、Key Vault はもっと堅いイメージがあります。Managed Identities との組み合わせで、Key Vault の secret を使う機会は減りそうな予感がします。

昔に Azure Storage を使う Configuration Provider を書いたことがありましたが、完全に App Configuration で置き換え可能かつ便利です。余裕で移行する予定です。

App Configuration は単なる Key-Value Store なので、大雑把には Azure Table と変わらない感じもありますが、クエリが充実していて更に履歴も保持してくれるので設定向けに最適化されています。

そのあたりについてはドキュメントに書いてあるので読んでおいてください。

実際にドキュメント通りに動かしてみましたが、当然ながらあっさり動くので特に書くことはないです。

ASP.NET Core の Configuration と同じ構造を持っているので、キーの参照時は以下のように直接でも Section を経由しても扱えます。この辺りもお馴染みですね。

@Configuration["TestApp:Title"]

<hr>

@Configuration.GetSection("TestApp")["Title"]

f:id:shiba-yan:20190228210152p:plain

ドキュメントには他にも動的に設定を更新する方法もありましたが、App Configuration が変更があったタイミングで Push してくれるわけではなく、単にクライアント側で Polling してるだけっぽいので使う機会はあまりなさそうです。

ASP.NET Core では IOptionsSnapshot<T> で意識せずに扱えますが、実行中に設定が変わることを考慮して作るより、再起動で変わってくれた方が個人的には好きです。

ここから先は気になった機能について調べたことをメモとして残しておきます。

Azure Portal で使える機能

History

App Configuration に追加したキーは履歴を持ちます。どのくらい持てるのかは調べてないですが、時間ベースでのアクセスも出来るので、それなりの期間保持できるのではないかと思っています。

f:id:shiba-yan:20190228214447p:plain

Configuration 系のライブラリを使っている場合は意識しないですが、ETag を使った条件付きアクセスが REST API には実装されています。例によって If-Match / If-None-Match を使う形になります。

Import / Export

地味に欲しかったのが Import / Export ですね。ASP.NET Core プロジェクトに含まれている appsettings.json を読み込ませると、簡単に App Configuration への移行が出来るはずです。

f:id:shiba-yan:20190228201947p:plain

地味にいろんなフォーマットに対応しています。properties ファイルは最近見なくなりましたが、Win Forms や WPF で使っていた XML ベースのやつです。

インポートしたいファイルをアップロードすると、Separator や Prefix などを指定できます。

f:id:shiba-yan:20190228213715p:plain

Label に関してはいろんな使い道がありそうです。ドキュメントではバージョン番号を設定していたりしますが、リージョン名や環境名*1も良さそうな気がしています。

エクスポートもファイル形式や Separator / Label を指定するだけです。簡単ですね。

Compare Settings

割とあるあるネタだと思うのが Production / Staging / Development で何故か設定されているキーの数や、環境非依存のはずの設定値が異なっていることです。比較するのも割と手間がかかるので、問題が起こるまで放置されがちですし、追加だけされて削除されないのも特徴だと思います。

そして App Configuration では比較する機能が用意されてました。こういうのが欲しかったです。

f:id:shiba-yan:20190228214341p:plain

特徴としては別の App Configuration とも比較できる点ですね。Production / Staging などをリソースグループ単位で分けている場合には、App Configuration 自体も分けてデプロイするはずですが、そういった場合でもサクッと差分を確認できます。

ちなみに日付を指定すると、その時点での設定値との比較も出来ます。

クライアントで使える機能

Label Filter

Label の使い道としてバージョン番号を付けるのがドキュメントにありましたが、付けた Label を使うためには App Configuration Provider 側で設定が必要です。

正直メソッド名は分かりにくいですが、Use を使うとフィルタを設定できます。以下の例では Label として 1.0.0 が付いているものだけを拾うことが出来ます。

config.AddAzureAppConfiguration(options =>
{
    options.ConnectionString = settings["ConnectionStrings:AppConfig"];

    // Label = 1.0.0 が付いているものを追加
    options.Use("*", "1.0.0");
});

バージョン番号の場合は、全てのキーに対して同じ Label を用意すると思うので上のコードで問題ないですが、リージョンや環境名の場合には一部の値は共通にしつつ、特定のキーでは別にしたいケースが出てくると思います。そういった場合では少し工夫が必要になります。

例として以下のように Label 無しの共通キーと Label を付けたキーを用意しました。

f:id:shiba-yan:20190228212134p:plain

共通キーを使いつつ、特定の Label が付けられたキーも使う場合には Use を複数回呼び出すことで実現できます。呼び出す順番が重要なので、そこだけは注意が必要です。

config.AddAzureAppConfiguration(options =>
{
    options.ConnectionString = settings["ConnectionStrings:AppConfig"];

    // Configuration は後から追加したものが優先
    options.Use("*");
    options.Use("*", "NewVer");
});

これで実行してみると、Label が付けられたキーの値が優先して使われていることが確認できます。

f:id:shiba-yan:20190228212230p:plain

キーにリージョンや環境名を入れるのは長くなるし、共通のキーを用意するのも手間がかかってイマイチだと思っていたので、上手く Label を使って解決出来そうです。

Time-Based Access

上で使ったUse メソッドで preferredDateTime を指定すると、その時点での値を取ってくることが出来ます。ただし今のバージョンでは英語 OS 以外だとエラーで動かないと思われます。

config.AddAzureAppConfiguration(options =>
{
    options.ConnectionString = settings["ConnectionStrings:AppConfig"];

    // 現在のバージョン (1.0.0-preview-007830001) ではエラーになって動かない
    options.Use("*", preferredDateTime: new DateTimeOffset(2019, 2, 28, 19, 0, 0, TimeSpan.FromHours(9)));
});

例外の内容からしてカルチャ指定を忘れているっぽいです。報告はしたので GA までには多分直るでしょう。

追記

Slack のチャンネルで報告したら Issue が上がってました。

再現できたらしいので直るはずです。Workaround も公開されたので、一応はこれでエラーを回避できます。

Offline Cache

設定系を外部のサービスに依存した時に問題となるのが、そのサービスが落ちた時です。デフォルトでは起動時に 1 回だけ読み込むのでインスタンスが起動している限りは問題にはならないですが、App Service はちょいちょいインスタンスが変わるので、タイミング次第では起動しなくなります。

GA した時には SLA が提供されると思いますが、サービスが落ちるときは落ちるので本番系では Offline Cache を有効にしておいた方が良さそうです。名前の通り最後に使った設定を、オフラインでも使えるように保存しておいてくれる機能です。

基本は SetOfflineCache を呼ぶだけですが、用意されているのはファイルに保存する OfflineFileCache となります。App Service の場合は OfflineFileCache の設定不要で動いてくれるみたいです。

config.AddAzureAppConfiguration(options =>
{
    options.ConnectionString = settings["ConnectionStrings:AppConfig"];

    // 開発環境以外では Offline Cache を有効化する
    if (!hostingContext.HostingEnvironment.IsDevelopment())
    {
        options.SetOfflineCache(new OfflineFileCache());
    }
});

ローカル開発環境では動いて欲しくないので if で囲んでおきます。

App Service 以外の場合は永続化されたストレージに書き込んでおけば良いです。適当に TEMP 以下に書くようなコードで試してみましたが、保存場所には注意しましょう。

config.AddAzureAppConfiguration(options =>
{
    options.ConnectionString = settings["ConnectionStrings:AppConfig"];

    // 開発環境以外では Offline Cache を有効化する
    if (!hostingContext.HostingEnvironment.IsDevelopment())
    {
        options.SetOfflineCache(new OfflineFileCache(new OfflineFileCacheOptions
        {
            Path = Environment.ExpandEnvironmentVariables(@"%TEMP%\app-config-cache.json")
        }));
    }
});

ちなみに Offline Cache が有効になるのはネットワーク系のエラーの時だけのようです。

手元でネットワーク接続を切った後に実行して、アプリケーションが起動することは確認しました。

Managed Identities

App Configuration で設定周りは楽になったといっても、結局は App Configuration へのアクセス情報を持たないといけないので、App Service の場合は Managed Identities を有効にして使いましょう。

AAD 統合されているわけではなく、アクセスキーを Management API を使って取得してるだけみたいです。

自分の環境では Visual Studio の Azure サービス認証を使ったアクセスはエラーで動かなかったのですが、仕組み上は可能なはずなので自分のアカウントに問題があったのかも知れません。

*1:Production / Staging / Development など