Azureの構成をBicepで管理するときの落とし穴|手作業の設定が消える理由と、本番に適用する前の確認

Bicep の再デプロイは、テンプレートに書かなかった設定を「そのまま残す」のではなく、原則として既定値に戻します。既定の増分モードでもそうなります。

運用を始めてから手作業で足したセキュリティの設定ほど、コード化するときに漏れやすく、しかも事前確認の what-if では既定値の表示や評価できない式に紛れて見落としやすくなります。消えた後では、何が入っていたかを Azure の履歴からたどれないこともあります。

増分モードではテンプレートに無いリソースは削除されない、本番に適用する前に what-if で差分を見る。ここまでは Bicep を使う方の多くが押さえている基本だと思います。弊社はお客様専用の環境を手作業で構築したあとに Bicep でコード化し、同じ定義で検証環境と本番環境を揃える運用をしています。その過程で、ネットワークの拒否ルールやマネージド ID が、適用のたびに気付かれないまま外れる経験をしました。この記事では、なぜ手作業の設定が消えるのか、what-if だけでは防げない理由、消えやすい設定の場所、そして本番に適用する前に確かめる手順を解説します。

株式会社ロクシアシステムズは、福岡と東京を拠点に、全国のお客様へ Microsoft 365・Azure の導入支援と、AIの業務活用(AIプライベート)の支援を提供しているIT企業です。

増分モードでも、書かなかった設定は既定値に戻る

Bicep のデプロイには増分モードと完全モードがあり、既定は増分モードです。増分モードでは、リソースグループにあってテンプレートに無いリソースは、そのまま残ります。この説明から「テンプレートに書いていない設定も残る」と受け取りがちですが、それは誤りです。

Microsoft Learn はこの点を明確に書いています。既存のリソースを増分モードで再デプロイすると、すべてのプロパティが適用し直され、プロパティは差分として足されるわけではない。テンプレートに含めなかったプロパティは既定値に戻る。テンプレートのリソース定義は常にそのリソースの最終的な状態を表し、既存のリソースへの部分的な更新は表せない、という説明です。

つまり増分モードが残してくれるのは、テンプレートに書いていない「リソース」までです。テンプレートに書いたリソースの中で書き漏らした「設定」は残りません。手作業で構築した環境をあとからコード化すると、この差がそのまま事故になります。

ただし、リソースの種類によっては例外があります。Learn は、Web アプリのサイト構成を空のオブジェクトで書いた場合は子リソースが更新されないことや、仮想ネットワークのサブネットの扱いに注意が要ることを挙げています(サブネットについては後の章で触れます)。例外の中身はリソースの種類ごとに異なるため、弊社では例外に頼らず、テンプレートに書いたリソースは最終状態をすべて書く、という前提で扱っています。

「what-if で差分を見れば防げるのでは?」

ここで多くの方が思うのは、「それなら適用する前に what-if で差分を見れば分かるのではないか」という疑問です。what-if は、テンプレートを適用したら各リソースがどう変わるかを予測する機能で、既存のリソースは何も変更しません。本番に適用する前の確認として欠かせない機能です。

ただし what-if は予測であり、Learn 自身がいくつかの限界を挙げています。手作業の設定が消えるかどうかの判定に関わるのは、次の3つです。

既定値が「削除」と表示される

テンプレートに書いていないがデプロイ時に既定値として自動で入るプロパティは、実際には変わらないのに「削除」と表示されることがあります。Learn はこれをノイズと呼んでいます。弊社の本番環境では、差分が無いはずの定義に対して、数十件の変更が表示されました。1件ずつ調べると、既定値の表示や評価されない式によるもので、実際に変わる設定はありませんでした。

ノイズが多いと、何十件もの表示の中から、本当に消える1件を見つけ出さなければならなくなります。手作業の拒否ルールが消える差分も、同じ「削除」の記号で並びます。この場合、消える設定は表示されてはいるものの、ノイズに埋もれて見落とされます。次の2つは、そもそも差分が正しく表示されない場合です。

評価できない式がある

what-if はデプロイの外で予測するため、評価できない式があります。Learn が挙げているのは、日時や GUID を生成する関数、secure を指定したパラメーターの値、同じテンプレートでデプロイしないリソースへの参照、listKeys() のようなリソース関数などです。これらを含むプロパティは、式のまま表示されるか、変わらないのに変わると表示されます。

アプリ設定には、Storage の接続文字列や API キーのように、secure のパラメーターや listKeys() で組み立てる値がよく入ります。こうした値が入った設定は、what-if の表示から中身の変化を確かめられません。

一部を分析できないことがある

入れ子のテンプレートが上限(500個・5分など)を超えると、残りのリソースは分析されずに「無視」として扱われます。また、リソース ID や API バージョンをデプロイの外で計算できない場合、そのリソースやモジュール全体が分析の結果から外れることがあります。Learn はこの現象を short-circuiting と呼び、新しい版の Azure CLI や Azure PowerShell ではその旨の診断メッセージが出ると説明しています。

what-if は必要ですが、それだけで「設定は変わらない」と判断する根拠にはなりません。what-if の表示を読み分けることに加えて、適用する前の状態を手元に残しておく必要があります。

手作業の設定が消えやすい4つの場所

書かなかった設定が既定値に戻るのは、どのリソースでも同じです。そのうち、運用を始めてから手作業で足すことが多く、消えたときの影響が大きいのが次の4つです。

1. アプリ設定:一覧全体が置き換わる

Azure Functions や App Service のアプリ設定は、Bicep では siteConfig.appSettings に書きます。Learn は、テンプレートでアプリ設定を追加・更新するときは既存の設定もすべて含めるよう求めています。背後の REST の呼び出しが、アプリ設定の一覧(/config/appsettings)全体を置き換えるためです。テンプレートでアプリ設定を定義している場合、ポータルで1つだけ足した設定は、テンプレートに無ければ次の適用で消えます。

個別の設定だけを変えたいときは、Azure CLI や Azure PowerShell、ポータルを使うよう Learn は案内しています。ただしそうすると、同じ一覧をテンプレートと手作業の両方で管理することになり、次の適用で手作業の分が消えます。どの設定をどちらで持つかを、先に決めておく必要があります。

2. 認証の設定:書いていない項目が既定値に戻る

App Service 認証(Easy Auth)の設定は、Microsoft.Web/sites/config の authsettingsV2 という子リソースで表します。テンプレートでこのリソースを定義している場合、これも1つのリソース定義として丸ごと適用されるため、書いていない項目は既定値に戻ります。

認証の設定には、ヘルスプローブのために認証を外すパスや、Front Door の背後で転送元のホスト名を信頼する設定など、構築後の調整で足す項目が多くあります。これが既定値に戻ると、サインイン後の戻り先が変わる、正常なのに監視が失敗と判定する、といった形で表に出ます。Front Door と組み合わせるときの設定はAzure Front Door の背後のアプリを守るで解説しています。

3. NSG のルール:空で書くと手作業のルールごと消える

ネットワークセキュリティグループ(NSG)のルールは、NSG の securityRules に並べて書けます。弊社では、ルールを空の配列で定義していた NSG に適用した結果、運用中に手作業で追加していた拒否ルールが消えたことがあります。テンプレートは「ルールは1つも無い」という最終状態を宣言していたので、その通りになった、ということです。

Bicep — ルールを空で宣言すると、既存のルールはすべて外れる
resource nsg 'Microsoft.Network/networkSecurityGroups@2024-05-01' = {
  name: nsgName
  location: location
  properties: {
    securityRules: []   // 「ルールは無い」という宣言になる
  }
}

拒否ルールは、閉域の環境で外向きの通信を絞る要の設定です。消えても通信が止まるわけではないので、利用者からの問い合わせでは気付けません。閉域の設計でどのルールを持つかはAzure OpenAI・AI Searchを閉域で使うで解説しています。

なお仮想ネットワークのサブネットは、仮想ネットワークの API が改められ、subnets を書かずに適用すれば既存のサブネットが残るようになりました。一方で、subnets を空で書くとすべてのサブネットが削除されます。「書かない」と「空で書く」は意味が違う、という点は NSG と同じです。

4. マネージド ID:種別が宣言と違うと外れる

マネージド ID は、リソースの identity で種別(システム割り当て・ユーザー割り当て)を宣言します。実環境で付けていた ID が宣言に含まれていなければ、適用によってその ID は外れます。

Bicep — 実環境で付けている ID の種別をすべて宣言する
identity: {
  type: 'SystemAssigned, UserAssigned'
  userAssignedIdentities: {
    '${uami.id}': {}
  }
}

弊社では、宣言した種別が実環境と違っていたために ID が外れたことがあります。厄介なのは症状です。アプリの画面は開き、多くの機能は動くのに、その ID でトークンを取得してデータを読む処理だけが失敗し続けました。当初は適用の直後に目立った異常が見えず、「影響なし」と判断していましたが、実際にはその読み取りの処理は全面的に止まっていました。なお、外れたのがシステム割り当ての ID の場合は、付け直すと別の ID になるため、ロールの割り当てもやり直すことになります。ユーザー割り当ての ID は、付け直せば元のロールの割り当てをそのまま使えます。

依存関係を書かないと、並列に動いてぶつかる

手作業の設定が消える話とは別に、コード化で初めて表に出る問題がもう1つあります。手作業では1つずつ順に作っていたリソースが、テンプレートでは同時に作られることです。

Learn は、Resource Manager がリソース間の依存関係を評価し、依存し合わないリソースは並列にデプロイすると説明しています。弊社では、Azure OpenAI のモデルの配備と、同じアカウントへの Private Endpoint の作成が同時に動き、一方が「アカウントが更新中」の状態のため拒否されて、デプロイが失敗したことがあります。2つのリソースは互いのプロパティを参照していないため、テンプレート上は依存関係がありません。しかし実際には、同じ Azure OpenAI のアカウントに対する操作が同時に走っていました。

こうした場合は、dependsOn で明示的に順序を付けて直列にします。

Bicep — モデルの配備が終わってから Private Endpoint を作る
resource pe 'Microsoft.Network/privateEndpoints@2024-05-01' = {
  name: peName
  location: location
  properties: {
    // 省略
  }
  dependsOn: [
    modelDeployment
  ]
}

Learn は、明示的な依存関係が必要になる場面はまれで、不要な依存関係は並列化を妨げてデプロイを遅くするとも書いています。dependsOn は、実際にぶつかった組み合わせにだけ付け、なぜ付けたかをコメントに残しておきます。

消えた後では、何が入っていたか分からない

設定が消えたと気付いたとき、次に必要なのは「元は何が入っていたか」です。ところが、それを Azure の側から後でたどれるとは限りません。

リソースの変更をたどる仕組みとして、Azure Resource Graph の変更分析(Change Analysis)があります。Resource Manager を通したリソースの作成・更新・削除を、変更されたプロパティの前後の値とともに記録する機能で、追加の費用なしで使えます。ただし Learn は、次の2つを明記しています。

  • 変更を照会できるのは14日間。それより長く残すには、自分で Log Analytics などへ書き出す
  • App Service のファイルと構成の変更には、現時点では対応していない

アプリ設定や認証の設定も App Service の構成にあたるため、変更分析で前の値をたどれるとは限りません。デプロイの履歴から分かるのも、どのテンプレートを適用したかであって、適用する前に何が入っていたかではありません。手作業で足した設定は、そもそもテンプレートのどこにも書かれていません。

このため弊社は、本番に適用する前に、対象のリソースの状態を丸ごと手元に退避することを必須にしています。退避しておけば、消えたものを比べて特定し、元に戻せます。

Azure CLI — 適用する前に、アプリの状態を退避する
# リソース本体(マネージド ID を含む)
az resource show --ids <アプリのリソースID> -o json > site.before.json

# アプリ設定(値に秘密情報を含むため、保管先に注意する)
az functionapp config appsettings list \
  --resource-group <リソースグループ名> \
  --name <アプリ名> -o json > appsettings.before.json

# 認証の設定
az resource show --ids <アプリのリソースID>/config/authsettingsV2 --api-version 2024-04-01 -o json > auth.before.json

# NSG のルール
az network nsg rule list \
  --resource-group <リソースグループ名> \
  --nsg-name <NSG名> -o json > nsg-rules.before.json

アプリ設定の退避には、接続文字列やキーの値がそのまま含まれます。退避したファイルは作業端末に残さず、アクセスを制限した場所に保管し、不要になったら削除します。秘密情報そのものは Key Vault に置き、アプリ設定からは Key Vault の参照で読む形にしておくと、退避ファイルに値が載らなくなります。

本番に適用する前の確認手順

ここまでの内容を、本番に適用するときの手順にまとめます。弊社は次の4段階を、検証環境で一度通してから本番で繰り返しています。

段階行うこと省くと起きること
1. 状態の退避リソース本体・アプリ設定・認証の設定・NSG のルールを、適用する直前に保存する消えた設定を特定できず、元に戻せない
2. what-if の読み分け削除・変更と表示された項目を1つずつ、既定値によるノイズか、手作業で入れた設定かに仕分ける。評価できない式が出た箇所は、差分が見えていないものとして扱う本物の差分がノイズに埋もれて見落とされる
3. 手作業の設定の取り込み2で見つけた手作業の設定をテンプレートに書き足し、もう一度 what-if を取る適用のたびに同じ設定が消える
4. 適用後の比較1で退避した状態と、適用後の状態を突き合わせる。あわせて、ID でデータを読む処理など、止まっても目立たない機能を実際に動かして確かめる画面は動くのに一部の機能だけが止まった状態に気付けない

what-if の結果は、画面の表示だけでなく JSON でも取れます。JSON にしておくと、前回の結果と比べて増えた項目だけを見る、といった読み分けがしやすくなります。

Azure CLI — what-if の結果を JSON で保存する
az deployment group what-if \
  --resource-group <リソースグループ名> \
  --template-file main.bicep \
  --parameters main.bicepparam \
  --no-pretty-print > whatif.json

この記事の要点

  • 増分モードが残すのはテンプレートに無い「リソース」までで、書いたリソースの中の書かなかった「設定」は既定値に戻る
  • what-if は予測であり、ノイズ・評価できない式・分析できない部分があるため、差分が少ないことを安全の根拠にしない
  • アプリ設定・認証の設定・NSG のルール・マネージド ID は、手作業で足しやすく消えても気付きにくい
  • 依存関係の無いリソースは並列に動くため、同じリソースへの操作がぶつかる組み合わせは dependsOn で直列にする
  • 変更の履歴は14日で、App Service の構成は対象外。適用する前の状態は自分で退避する

弊社の支援

弊社は、お客様専用のAI基盤やアプリを Azure 上に構築する形で、AIの業務活用を支援しています。構成を Bicep でコード化しておくと、検証環境と本番環境を同じ定義で揃えられ、構成を説明する資料としても使えます。一方で、手作業で育ててきた環境をコード化する最初の適用は、この記事で挙げた設定が消えやすく、特に注意が要る場面です。

弊社では、既存の環境の状態を採取してテンプレートとの差を洗い出し、手作業の設定を取り込んでから適用する、という進め方でコード化を支援しています。コード化した後の払い出しや変更管理の考え方はAzureのインフラ払い出しをセルフサービス化するもあわせてご覧ください。

よくあるご質問

こちらをクリックして「よくある質問」を表示

増分モードなら、既存の設定は残るのではありませんか?

残るのは、テンプレートに書いていないリソースです。テンプレートに書いたリソースは、すべてのプロパティが適用し直され、書かなかったプロパティは原則として既定値に戻ります。Microsoft Learn も、リソース定義は常に最終的な状態を表し、部分的な更新は表せないと説明しています。

what-if で差分が出なければ、安全と考えてよいですか?

それだけでは安全の根拠になりません。what-if には、secure のパラメーターや listKeys() などの評価できない式があり、入れ子の上限を超えた部分や計算できない部分は分析から外れます。what-if で確かめたうえで、適用する前の状態を退避し、適用後に比べることをお勧めします。

アプリ設定だけは、Bicep の外で手作業で管理してもよいですか?

テンプレートに siteConfig.appSettings を書いている限り、手作業で足した設定は次の適用で消えます。アプリ設定の一覧は全体が置き換わるためです。テンプレートと手作業のどちらで持つかを設定ごとに決め、同じ一覧を両方で管理しないようにしてください。

完全モードを使えば、テンプレートと実環境の差をなくせますか?

完全モードは、テンプレートに無い「リソース」を削除するモードで、リソースの中の設定の扱いは増分モードと変わりません。Microsoft Learn は完全モードを推奨しておらず、デプロイでリソースを削除したい場合は Deployment Stacks を使うよう案内しています。

消えた設定を、変更の履歴から調べられますか?

Azure Resource Graph の変更分析で、リソースのプロパティの変更を前後の値とともに調べられます。ただし照会できるのは14日間で、App Service のファイルと構成の変更には対応していません。アプリ設定や認証の設定は、適用する前に自分で退避しておく必要があります。

dependsOn は、念のため多めに付けておくべきですか?

お勧めしません。Microsoft Learn は、明示的な依存関係が必要な場面はまれで、不要な依存関係はデプロイを遅くすると説明しています。同じリソースに対する操作が同時に走ってぶつかるなど、実際に問題が起きた組み合わせにだけ付け、理由をコメントに残しておくのが扱いやすい方法です。

手作業で構築してきた Azure の環境をコード化したい、すでにコード化したが適用するのが不安だ、というご相談を承っています。現在の構成をお聞かせいただければ、状態の採取からテンプレートとの差の洗い出し、本番に適用するときの手順まで、進め方をご提案します。お問い合わせよりお気軽にご相談ください。

関連サービス・関連コラム

まずは無料トライアルで、効果をご確認ください

「AIプライベート」は短期・低コストで試せます。帳票入力(AI-OCR)・電話対応(AI-Voice)・計画づくり(AI-Enhance)の自動化から、クラウド(Azure・Microsoft 365)・DXのご相談まで、お気軽にどうぞ。