Azure Virtual Network Manager(AVNM/旧称として「Azure Network Manager」と呼ばれることもあります)のIPAMプールでvNetのCIDRを自動割り当てしていると、vNetのCreateOrUpdateを同時に複数走らせた際に「IpamApiCallFailed(PreconditionFailed)」でデプロイが落ちることがあります。直列化だけでは運用が詰まりがちなので、原因の整理と“実務で効く”回避策を具体的にまとめます。
発生する現象:並列デプロイ時にIpamApiCallFailed(PreconditionFailed)で失敗する
IPAMプールを使ってvNet(必要に応じてサブネットも)へアドレスを割り当てる構成で、同一プールを参照するvNetのCreateOrUpdateを同時に2件以上実行すると、ARMデプロイが失敗するケースが報告されています。
エラーは環境やIaCによって見え方が多少異なりますが、代表的には次のような断片が含まれます。
| 見える症状(例) | よく含まれる文言 | 補足 |
|---|---|---|
| ARM/デプロイがFailedで終了 | DeploymentFailed / ResourceDeploymentFailure | “どこか1つでも失敗したら全体失敗”として返る |
| vNetの書き込みで落ちる | IpamApiCallFailed | IPAMへの割り当て呼び出しが失敗したことを示す |
| ステータスにPreconditionFailedが出る | Ipam API call failed with status PreconditionFailed | “しばらく待って再試行して”が一緒に出ることが多い |
| 外側が400に見えることがある | unexpected status 400 (Bad Request) | 外側のHTTPと内側の失敗要因が一致しないケースがある |
エラーメッセージ例(要旨)
unexpected status 400 (400 Bad Request) with error: IpamApiCallFailed: Ipam API call failed with status PreconditionFailed with error message Precondition failed. Please retry the operation after some time.
結論:これは「想定内(期待される)挙動」とされている
Microsoft Q&A上でNetworkingの担当(Microsoft External Staff)が回答しており、同一IPAMプールに対して複数の更新(割り当て)操作が同時に走ると、PreconditionFailedとして失敗するのは想定される、という扱いです。
また同回答では、長期的にはバックエンドを競合に強くする修正をロールアウト予定で、投稿時点の見込みとして9〜10月の展開が示唆されています(あくまで当時のETAとして読み、恒久対策は別途持っておくのが安全です)。
なぜPreconditionFailedになるのか:IPAM割り当ては「プールの状態更新」に相当する
IPAMプールからvNetへ自動でCIDRを払い出す場合、vNet側のCreate/Updateの裏で、プールの割り当て台帳(どのCIDRを誰に渡したか)に相当する情報が更新されます。つまり、同じプールに対して同時に割り当て要求が集中すると、バックエンド側では整合性を守るための競合制御(排他・楽観ロック・内部キューなど)が働き、タイミングによっては「いまは前提条件を満たせない=PreconditionFailed」として弾かれます。
ポイントは、これが単なる「vNet作成の並列数」ではなく、“同一プールへの同時書き込み数”に依存していることです。vNetが別リソースグループでも、別サブスクリプションでも、最終的に同じプールを叩けば競合しえます。
「閾値が明記されない理由」:スロットリングは固定値として公開されないことが多い
同じQ&A回答の中でも触れられていますが、APIスロットリングの閾値は、サービス品質維持・内部実装・リージョン差・負荷状況などの都合で、固定値として明文化されないことが一般的です。
さらにAzureでは、入口のAzure Resource Manager(ARM)と、奥のリソースプロバイダー(RP)で別々の制御が存在します。ARM側は主に429(Too Many Requests)とRetry-Afterで返し、RP側もRP固有の制御・一時失敗を返すことがあります。ARM側の制限や考え方(トークンバケットなど)が公式に説明されている一方、RP固有の“内部競合”は個別にドキュメント化されないこともあります。
| 分類 | 起きやすい返り方 | 確認の勘所 | 対処の基本 |
|---|---|---|---|
| ARM側のスロットリング | HTTP 429 / Retry-After | レスポンスヘッダーにx-ms-ratelimit-remaining系 | Retry-After尊重+自前スロットリング |
| RP側のスロットリング/一時失敗 | 429以外もあり得る | エラー本文のcode/メッセージ、依存リソースの状態 | リトライ(指数バックオフ+ジッター) |
| IPAMプールの同時更新競合 | IpamApiCallFailed / PreconditionFailed | 同一プールを参照するCreateOrUpdateが並列になっていないか | プール単位の並列度制御+リトライ |
まず押さえたい前提:AVNM IPAMで“何が更新されるのか”
AVNMのIPAMは、IPアドレスプールを作成し、そこからvNetやサブネットへCIDRを割り当てて管理します。公式ドキュメントでも、IPAMはプールを用いた集中管理・非重複CIDRの自動割り当て・使用状況の可視化などを目的としていることが明記されています。
IaC的には、vNetのaddressSpaceにipamPoolPrefixAllocationsを指定し、プールIDと必要IP数(numberOfIpAddresses)を渡す形が基本です。
{
"type": "Microsoft.Network/virtualNetworks",
"apiVersion": "2024-01-01",
"name": "vnet-example",
"location": "[parameters('location')]",
"properties": {
"addressSpace": {
"ipamPoolPrefixAllocations": [
{
"pool": { "id": "[parameters('poolResourceID')]" },
"numberOfIpAddresses": "256"
}
]
}
}
}
また、Bicepのサンプルでは、vNetだけでなくサブネット側にもipamPoolPrefixAllocationsを指定して、vNetとサブネットをまとめて自動割り当てする例も提示されています。つまり、並列更新の“当たり判定”はvNetだけでなくサブネット作成を含むテンプレートでも増え得ます。
再現しやすいパターン:組織で1つの大きなプールを共有している
現場で特に問題になりやすいのは、次のような構図です。
- 組織標準として、例えば「10.0.0.0/8」などの大きなルートプールを用意
- 各プロジェクト/各環境が同じプール(または同じ子プール)を参照してvNetを自動作成
- CI/CDやセルフサービスでデプロイが重なり、同一プールへの更新がバーストする
IPAMはプール階層(ルート/子プール)で整理でき、最大7階層まで作れる、とされています。これ自体はスケール設計の助けになりますが、運用設計を誤ると「全員が同じプールを叩く」形になり、競合が表面化しやすくなります。
当面の回避策:直列化“だけ”ではなく「プール単位」で並列度を絞る
最も効くのは、vNet作成全体を完全直列にするのではなく、同一IPAMプールに触る処理だけ並列度を絞ることです。これなら、別プール(別環境/別部門/別リージョン)へのデプロイは並列のままスループットを維持できます。
| 回避策 | 効果 | 実装コスト | 向くケース |
|---|---|---|---|
| プール単位の同時実行制限(推奨) | 競合の根を断てる | 中 | 複数チームが同一プールを共有する大規模組織 |
| リトライ(指数バックオフ+ジッター) | 一時失敗を吸収 | 低〜中 | 失敗を“ゼロ”にできない運用、夜間バッチなど |
| テンプレートの依存関係で同時更新を回避 | テンプレ内の競合を抑制 | 低 | Bicep/ARMでループ作成している |
| プール分割(部署/環境/リージョン単位) | そもそも競合確率を下げる | 中〜高 | 将来の拡張・権限分離も含めて設計を見直す |
リトライ設計:指数バックオフ+ジッターを“前提”にする
Precondition failed. Please retry… と明示される通り、これは一時的な失敗としてリトライが妥当です。
ただし、リトライを雑に実装すると「全員が同時に再試行してまた衝突する」状態になりやすいので、次の2点が重要です。
- 指数バックオフ:待ち時間を段階的に伸ばす
- ジッター:待ち時間にランダム性を混ぜ、再試行の同期を崩す
| 推奨パラメータ例 | 値(目安) | 狙い |
|---|---|---|
| 最大試行回数 | 6〜10回 | 一時的な競合を吸収しつつ、無限リトライは避ける |
| 初期待機 | 5〜10秒 | 即時再試行を避ける |
| 倍率 | 2倍 | 指数バックオフ |
| 上限(キャップ) | 120〜180秒 | 待ちすぎでパイプラインを詰まらせない |
| ジッター | ±20〜40% | リトライの同時発火を分散 |
ARM/SDKによっては429の場合 confirmできるRetry-After が返ることがあります。そうしたときは、可能な限りRetry-Afterを尊重しつつ、今回のように429以外の一時失敗も起こり得る点を踏まえて自前のバックオフも用意しておくと堅牢です。
IaC別:並列度制御を入れる“現実的な場所”
Bicep:@batchSizeでループの同時実行数を制限できる
Bicepはループで複数リソースを作るとデフォルトでは並列です。そこに@batchSizeデコレーターを付けることで、指定した数だけ同時に作り、次のバッチは前のバッチが終わってから実行されます。IPAMプールに同時更新が入るのを抑えるのに非常に相性が良いです。
@description('同一IPAMプールを使うvNet群')
param vnets array
@description('IPAMプールのResource ID')
param ipamPoolId string
// 例:同時に1つずつ(完全シリアル)
@batchSize(1)
resource vnet 'Microsoft.Network/virtualNetworks@2024-05-01' = [for v in vnets: {
name: v.name
location: v.location
properties: {
addressSpace: {
ipamPoolPrefixAllocations: [
{
numberOfIpAddresses: v.numberOfIpAddresses
pool: {
id: ipamPoolId
}
}
]
}
}
}]
「全部を@batchSize(1)にすると遅い」という場合は、プールごとにループを分ける/本当に競合する部分(IPAM割り当てが絡む部分)だけ別デプロイに切り出すといった分割が効きます。
ARMテンプレート:copyのmodeとbatchSizeで“直列/バッチ”を指定できる
ARMテンプレートでもcopyループに対して、mode=serialとbatchSizeで逐次/バッチ実行が指定できます。Bicepが生成するARMにも同等の考え方があるので、ARMテンプレート派でも対処可能です。
Terraform:terraform apply -parallelismで同時実行数を抑える
Terraform側で根本的に並列数を落とすなら、CLIの-parallelismが使えます。公式のapplyコマンドリファレンスにも、グラフのウォーク時の同時オペレーション数を制限し、デフォルトは10であることが記載されています。
# 例:IPAMプールに触るワークスペース/ディレクトリのapplyだけ並列度を落とす
terraform apply -parallelism=1
ただし、-parallelism=1は「そのTerraform実行内」を直列化するだけです。組織内で複数のパイプラインやワークスペースが同時に走る場合は、結局プール競合が起き得ます。その場合は次のいずれか(または併用)がおすすめです。
- パイプラインの同時実行制御(同一プールを使うジョブは同時に走らせない)
- プール分割(後述)
- 失敗時の自動リトライ(バックオフ+ジッター)
「プール分割」で根本的に競合確率を下げる設計
組織でデプロイ数が増えるほど、単一プール共有は必ずボトルネックになります。IPAMはそもそも階層構造(ルートプール/子プール)で整理できるため、競合とガバナンスを両立するための“分割設計”がしやすいのが利点です。
| 分割軸 | 例 | メリット | 注意点 |
|---|---|---|---|
| 環境 | prod / nonprod / sandbox | 運用・権限・承認フローも分けやすい | 環境間の接続要件(Peering/ER/VPN)を踏まえて設計 |
| 部署/プロダクト | 部門A pool / 部門B pool | “誰の作業が誰に影響するか”を小さくできる | 割り当てルールの統一(サイズ感の標準化)が必要 |
| リージョン | japaneast pool / japanwest pool | 同時更新が分散しやすい | 原則、vNetとプールは同一リージョンが基本(例外としてクロスリージョン関連付けプレビューあり) |
なお、IPAMには“静的CIDR(Static CIDR)”という、Azure外(オンプレなど)や未対応リソースのCIDRを確保しておく用途も用意されています。プールを分割する際に「ここは将来のオンプレ拡張用に予約」などの運用ができるので、無計画に枯渇しにくくなります。
一方で、IPAMに関しては“できないこと”も把握しておく必要があります。例えば、IPAMで管理されているアドレス空間を既存のvNet/サブネットから取り外す(削除する)ことや、IPAMプールからアドレス空間を取り除くことがサポートされない、という制限が明記されています。設計段階で「後から自由に抜き差しできる」と思い込むと移行が苦しくなるので、分割は早めが安全です。
運用で詰まらないための“現場向け”ガードレール
プール単位のキュー(ソフトロック)を作る
CI/CDでよく効くのが、「同一IPAMプールIDをキーにした排他」です。実装は環境依存ですが、概念はシンプルです。
- デプロイ前に「poolIdのロック」を取得
- 取得できなければ待機(またはキューに積む)
- デプロイ完了後にロック解放
これにより、“誰かが同じプールを触っている間は待つ”を組織横断で強制でき、IaCの種類(Terraform/Bicep/ARM)が混在しても事故率を下げられます。
「失敗=即アラート」ではなく「失敗=リトライ→収束」を前提にする
今回のPreconditionFailedは、メッセージ上も“待って再試行”が想定されています。
運用としては、最初の失敗で即アラートにするとノイズになりがちです。次のように段階を分けると、運用品質が上がります。
- 1〜2回の失敗は自動リトライで吸収(通知しない)
- 一定回数を超えたら通知(プール枯渇・RBAC・設定誤りの可能性が高い)
- Tracking IDやタイムスタンプを添えて通知(調査時間を短縮)
調査・問い合わせで用意すべき情報
Microsoft側に調査を依頼する場合、Q&A回答でも求められている通り、最低限次を揃えると話が早いです。
- 対象のvNet情報(リソースID、リージョンなど)
- 対象のIPAMプール情報(リソースID)
- 正確なエラーメッセージ(省略なしが理想)
- 発生時刻(タイムスタンプ)(Activity Logで追える粒度)
- 表示されていればTracking ID(Correlation/Tracking)
ARMデプロイの失敗は「どの操作が落ちたか」を深掘りしないと原因がぼやけます。デプロイ操作一覧(deployment operations)やActivity Logを必ず確認し、IpamApiCallFailedが出ている対象リソース(vNetなのか、subnetなのか)を特定してください。
長期的な見通し:バックエンド改善は期待しつつ、設計側でも備える
前述のとおり、Microsoft側コメントとして、バックエンドを競合に強くする修正のロールアウトが示されています。
ただし、たとえ改善が入ったとしても、組織のセルフサービスが加速すれば競合の種は残ります。おすすめの落とし所は次の3点です。
- プール単位の並列度制御はガードレールとして残す(完全に外さない)
- リトライは標準機能として持つ(“瞬間的な混雑”を吸収)
- プール分割を進め、ボトルネックが一点に集中しない構造にする
ポイント整理:最短で安定させるならこの順番
| 優先度 | やること | 理由 | すぐ効く度 |
|---|---|---|---|
| 高 | 同一IPAMプールの同時実行数を1〜少数に制限 | 競合そのものを抑える | ★★★★★ |
| 高 | PreconditionFailedは自動リトライ(指数バックオフ+ジッター) | 一時失敗を吸収し、運用の手戻りを減らす | ★★★★☆ |
| 中 | Bicepなら@batchSize、ARMならcopy mode/batchSizeでバッチ化 | テンプレ内の並列を手軽に制御できる | ★★★★☆ |
| 中 | Terraformは-parallelismやパイプラインの同時実行制御を併用 | Terraform単体の直列化だけでは組織横断の競合を防げない | ★★★☆☆ |
| 中〜低 | プール分割(環境/部署/リージョン)と権限設計を見直す | 長期的にボトルネックを解消しやすい | ★★☆☆☆ |

コメント