結論
・・・から先に言うと、Azure App Configuration を使って設定を管理し、さらに Azure App Service (等)でサポートされている Managed Identity を合わせて使いましょう、ということです。また、この他にもいくつかの小粒なベストプラクティスもあるので以下に記述します。
時間の都合上、備忘録的な書き方になります。(すみません。本当はもっと丁寧に書きたいんですけど。)
問題
Azure クラウドプラットーフォーム上で App Service などを利用してサービスを実装するとしたら、なんらかの設定(configuration)が必要です。伝統的には appsettings.json などに記述して、プロセスが立ち上がるタイミングで設定内容を読み込むというやり方でやってきました。
この場合、いくつか問題があります。
- 設定情報をファイルとしてソースレポジトリに保存するので柔軟性に欠ける
- 設定内容を変更した場合、プロセスをリスタートして最新の内容を読み込む必要が生じる。継続性を保つために複数のスロットなどを利用することになり、それだけ複雑になる
- ステージング環境用や本番環境用など、環境ごとに設定内容を分けて管理するので、複数の appsettings.json をマネージしなくてはならなくコードベースが煩雑になる
- コードファイルとは目的も使い方も異なる設定ファイルが同じよう管理されるので、設定変更の度にコードレビュー、テスト、デプロイメントパイプライン実行などが必要になる
さらに、機密情報の管理とアクセスに関しても問題があります。機密情報はベストプラクティスとして Azure Key Vault で保存・管理しますが、設定情報管理と合わせて考えると、それでも以下のような問題が生じます。
- 設定情報と機密情報を別々の方法で管理するため、コードも二通りのやり方になる
- 設定情報と機密情報を合わせて利用するコードでは、ふたつのデータソースを必要とするのでロジックが複雑になる
- 機密情報を保持する Key Vault にアクセスするためのは別の機密クレデンシャルが必要。その機密情報にアクセスするために別の機密情報が必要になる→ 終わりのない戦い(もしくは機密情報をレポジトリにコミットしてしまうという事故)
これらの問題をほぼ完全に解決してしまうベストプラクティスを以下に記述しておきます。
解決 - ベストプラクティス
例として App Services を使ってサービスやAPIを構築しようとする場合、もしくは既存のサービスを設定・機密情報の管理面で改善したい場合、以下の組み合わせを推奨します。
- App Configuration サービスを利用して appsettings.json 等設定ファイルに保存していた設定情報を一元的に集めて管理する
- App Configuration の設定の一部として、機密情報を保存している Key Vault への参照を加える
- App Services のIdentity設定で Managed Identity を有効にする
- App Configuration の設定の一部として、その Managed Identity に対してアクセス権限を付与する (Azure Functions も同様の処置が可能)
さらにAzure App Services がASP.NET Core で実装されていると仮定し、以下のように構成します:
- App Configuration の NuGet パッケージで配布されているミドルウェアを、リクエスト処理パイプラインに組み入れる(この際、App Configuration サービスへのアクセスは Managed Identity を利用するように明示的に指定します)
- コントローラー等の、設定情報を活用するコンポーネントに対して
IOptionSnapshot<T>
を使って依存性の注入を行う(IOptionSnapshot<T>
はIOption<T>
の仲間で、ASP.NET Coreではお馴染みのパターンです) - 機密情報を活用するコンポーネントでも同じ方法を適用し、機密情報にアクセスします
恩恵
このように構成された Azure App Service では以下のような恩恵があります。
- appsettings.json などの設定ファイルに指定していた内容をクラウドに移行できるので appsettings.jsonが軽くなる
- 同じ理由で、設定変更がソースコード変更と分離されるので無用のコードレビューやデプロイメントサイクルが無くなる
- ミドルウェアを仕込むことでリクエスト毎に設定を読み込む機会が発生し、設定の変更をより素早くサービスに反映できる
- ミドルウェアはキャッシュ機能をすでに実装しているので、App Configuration サービスへのアクセス頻度とオーバーヘッドを調整できる
- 設定情報と機密情報が Azure App Configuration というファサード看板のもとで「一貫性」をもって公開されるのでアクセスするコードがより単純になる
- Managed Identity を利用することでサービスプリンシパルにまつわる煩雑な作業から解放される(サービスプリンシパルを認証するための証明書の管理・アクセスなど。ただし User Assigned Managed Identity という種類の場合は証明書などの管理は必要になる)
- 同じ理由で、機密情報の漏洩事故が減る
- 同じ理由で、証明書の期限切れを原因とするサービス障害が減る
さらに細かいベストプラクティス
通常、App Configuration で複数の設定項目を保存するでしょう。その際、更新があったかどうかの判定をするために、ひとつひとつの項目をチェックしていたらとても非効率的です。そのため、Sentinelと呼ばれる「代表」を立てます。これは単にひとつの設定項目ですが、一種のメタデータであり、複数の設定項目をひとつのグループとして抽象化し、そのいずれかに変更があった場合、Sentinelの内容を変更します(通常、バージョンのように1からスタートする数値を指定します)。上述したASP.NET Core用の App Configuration ミドルウェアにはこの Sentinel だけ追跡するように指定することで、オーバーヘッドを最小限にすることが出来ます。
クラウド上での設定・機密情報へのアクセスについては改善されましたが、それでもローカル環境での開発作業で設定情報などが必要です。機密情報に関しては ASP.NET Coreであれば、User Secrets という機能を利用することで、開発中の機密情報の管理に役立てることができます。
開発環境と本番環境向けに異なるコードパスを記述してしまうというやり方もありますが、コードの可読性に難があります。そこで DefaultAzureCredential
クラスを利用します。このクラスはインスタンスが作られる時、Managed Identity を含めていくつかのクレデンシャルを自動的にチェックしてくれます。ローカル環境では Managed Identityは当然失敗するので、別のVisualStudioやVisualStudioCodeから得られるクレデンシャルを取得するフォールバック機構が付いています。
まとめ
設定の管理、そして特にKey Vaultへアクセスするためのクレデンシャルの管理などはサービスを開発・保守する上で必ず必要です。独自に解決することも出来ますが Azure プラットフォームが、ASP.NET Core と連携して解決してくれるので使わない手は無いでしょう。