依存グラフによるプロビジョニング順序決定
なぜ apply の順番をツール任せにできるのかを原理から理解。参照と depends_on から依存グラフ(DAG)を組み、並列適用順を決める仕組み、循環依存の検出、部分失敗時にロールバックされない理由と収束のさせ方まで解説します。
- 1.Terraform 等は設定中の属性参照(暗黙依存)と depends_on(明示依存)から有向非巡回グラフ(DAG)を構築し、トポロジカルソートで適用順を、依存のないノード集合の同時実行で並列度を決める。
- 2.依存に循環があると順序が定義できないため、グラフ構築時に閉路を検出して apply 自体を拒否する。閉路の解消は参照の張り直しか中間リソースの導入で行う。
- 3.apply はトランザクションではない。途中で失敗しても成功済みリソースは戻されず(ロールバック非対応)、state に部分的な結果が記録される。是正は再 apply による収束で、冪等性がこれを支える。
なぜ「順序」が問題になるのか
宣言的 IaC では、ユーザーは「あるべき姿」を書くだけで、リソースを作る順番を明示しません(/devops/iac/)。だが現実のクラウドには厳然たる順序制約があります。サブネットは VPC が無ければ作れず、EC2 はサブネットとセキュリティグループが無ければ起動できません。順序を書かないのに正しい順序で適用される——この一見魔法のような挙動を支えるのが、リソース間の依存関係から組み上げる 依存グラフ です。
ツールがやるのは、設定全体を読んで「どのリソースがどのリソースに先立つ必要があるか」を関係として抽出し、その関係を 有向グラフ として表現することです。各リソースがノード、依存が辺になります。順序決定とは、このグラフ上で「辺の向きに反しない並べ方」を求める問題に帰着します。
暗黙依存と明示依存
依存には二種類あります。大半は 暗黙依存(implicit dependency) で、ある設定が別リソースの属性を参照したときに自動生成されます。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id # ← main.id を参照 = 暗黙依存
cidr_block = "10.0.1.0/24"
}
aws_subnet.app は aws_vpc.main.id を参照しています。この id の値は VPC を実際に作るまで確定しない(クラウドが採番する)ため、VPC を先に作らなければサブネットを作れない。ツールは設定式を構文解析して参照を抽出し、「app は main に依存する」という辺を自動で張ります。ユーザーが順序を書かなくて済むのは、参照そのものが順序の宣言だから です。
一方、属性参照では表現できない順序制約には 明示依存(depends_on) を使います。たとえば IAM ポリシーの伝播のように、設定上は値を参照していないのに、論理的にあるリソースが先に存在していないと正しく動かない場合です。
明示依存は「ツールが推論できない隠れた順序」を人手で補うものです。多用すると依存グラフが実態より過剰に密になり、本来並列にできる適用を直列化して遅くします。可能な限り属性参照(暗黙依存)で表現し、depends_on は参照では捉えられない真の制約だけに限るのが原則です。参照で足りるのに depends_on を足すのは、二重宣言かつ並列性の損失です。
DAG とトポロジカルソート
抽出した辺を集めると、リソース全体は一つの有向グラフになります。順序が破綻なく決まるためには、このグラフが DAG(有向非巡回グラフ、Directed Acyclic Graph) であること——すなわち 閉路(サイクル)を含まないこと が必須です。
依存グラフ(辺 A → B は「A を先に作る」を意味):
vpc ──→ subnet ──→ instance
│ ↑
└────→ sec_group ────┘
トポロジカルソートの結果(一例):
vpc, subnet, sec_group, instance
(すべての辺で「先」が「後」より前に並ぶ順序)
適用順は トポロジカルソート で求めます。これは「すべての辺 A→B について A が B より前に来る」線形順序を計算するアルゴリズムです。DAG なら必ず一つ以上の妥当な順序が存在します。実装はおおむね次の発想です。
カーンのアルゴリズム(入次数ベース):
1. 各ノードの入次数(自分へ向かう辺の数)を数える
2. 入次数 0 のノード(依存を持たない=今すぐ作れる)を集合 R に入れる
3. R から1つ取り出して「確定順序」に追加し、
そのノードから出る辺を消す(先側が片付いたので)
4. 辺を消した結果 入次数 0 になったノードを R に追加
5. R が空になるまで繰り返す
6. 全ノードを処理できれば DAG、残れば閉路あり
重要なのは、トポロジカルソートは 一意とは限らない ことです。subnet と sec_group のように互いに依存しないノードは、どちらを先にしても辺の制約を満たします。ここに 並列適用 の余地が生まれます。
並列度はグラフが決める
順序を「一直線」に潰してしまうのはもったいない。実際のツールは、依存関係で順序が縛られていないリソースを同時に適用 します。並列度を決めるのもまた依存グラフです。
並列適用の波(ウェーブ)の考え方:
波0: 入次数 0 のノード群を同時に適用 → {vpc}
(完了したら vpc から出る辺を消す)
波1: 新たに入次数 0 になった群を同時適用 → {subnet, sec_group}
(subnet と sec_group は相互依存なし=並列可)
波2: → {instance}
各波の内部は並列、波と波の間は直列(依存の境界)
つまり クリティカルパス(最長依存連鎖)の長さが、並列化しても縮められない下限 になります。実装上は固定数のワーカーで「今 入次数 0 のノード」を取り出して実行し、完了通知が来るたびに後続の入次数を減らして新たに実行可能になったものを投入する、という動的なスケジューリングになります。並列度の上限(Terraform なら -parallelism、既定10)は、クラウドAPIのレート制限とのバランスで調整します。
並列度はグラフの「幅」で決まるため、不要な depends_on を消すと幅が広がり速くなることはあります。だが本質的な依存(VPC→サブネット→インスタンス)はクリティカルパスとして残り、これは設計上どうしても直列です。高速化の余地は「偽の直列化(過剰な明示依存)」の除去であって、真の依存は縮みません。まず depends_on の棚卸しから始めるのが定石です。
破壊と入れ替えでは順序が反転する
依存グラフは作成だけでなく 削除・置換 の順序も決めます。そしてここで向きが反転します。作成は「依存される側が先」ですが、削除は 「依存する側が先」 でなければなりません。サブネットがまだ存在するのに VPC を消そうとすればクラウドが拒否するからです。
作成順: vpc → subnet → instance (依存される側から)
削除順: instance → subnet → vpc (依存する側から=辺を逆向きに辿る)
置換(replace)時は両者が混ざる:
新リソース作成 → 参照の付け替え → 旧リソース削除
(create_before_destroy の場合。順序を誤ると参照が宙に浮く)
ツールは内部で、削除・置換のときにグラフの辺を 逆向きに辿った順序 を使います。これも DAG であるからこそ成立する性質で、閉路があれば「逆順」も定義できません。状態管理とドリフトの観点は別記事に詳しいので、state がどう更新されるかはそちらを参照してください(/devops/iac-state-drift-locking/)。
循環依存の検出と解消
依存グラフに閉路があると、トポロジカルソートが全ノードを処理しきれず(前述のカーンのアルゴリズムでノードが残る)、適用順そのものが定義不能 になります。ツールはこれを設定読み込み・グラフ構築の段階で検出し、apply に入る前にエラーで停止します。「適用してみて失敗」ではなく「適用できないと事前に拒否」する点が重要です。
閉路の例(互いに相手の属性を参照):
sec_group_a ──→ sec_group_b
↑ │
└────────────────┘
a のルールが b を参照し、b のルールが a を参照 → 閉路
解消の方針:
1. 参照を一方向に張り替える(どちらかを後付けにする)
2. 関係を別リソースへ外出しする
(例: ルールを aws_security_group_rule として独立させ、
グループ本体への循環参照を断つ)
セキュリティグループ同士の相互参照は典型例です。解消の常道は、循環している関係を独立したリソースに切り出す ことです。グループ本体と「グループ間ルール」を別ノードにすれば、本体→ルールの一方向の辺だけになり、閉路が消えます。閉路はたいてい「一つのリソースに詰め込みすぎ」のサインであり、関係の外出しで構造的に解けます。
apply は途中で失敗してもロールバックしない
ここが運用上もっとも誤解されやすい点です。依存グラフは 適用の順序 を決めますが、適用の原子性(all-or-nothing)は保証しません。Terraform をはじめ主要な IaC ツールの apply は トランザクションではない。
波1で 3 リソースを並列適用中に 1 つが失敗した場合:
instance_a ✓ 作成成功 → state に記録(残る)
instance_b ✗ API エラーで失敗
instance_c ✓ 作成成功 → state に記録(残る)
結果: 成功した a と c は「戻されない」。
b 以降の依存先(波2)は実行されない。
state には a・c が反映され、b は未作成のまま。
なぜロールバックしないのか。クラウドリソースの作成は外部世界への副作用であり、汎用的に巻き戻す手段が存在しないからです(作ったロードバランサを「無かったこと」にはできず、削除という別の破壊操作になる)。仮に失敗時に自動削除すれば、それ自体が失敗しうるうえ、復旧の手がかりになる成功済みリソースまで消してしまいます。そこで IaC は 「巻き戻し」ではなく「収束(convergence)」 を選びます。失敗した部分を直してもう一度 apply すれば、すでに成功したリソースは差分ゼロで触られず、未完了分だけが作られて全体があるべき姿へ近づく——これが宣言的モデルの中核です。
途中失敗した apply の後、state には成功分だけが記録された「中途半端な実体」が残ります。次の apply はこの state を起点に差分を計算するため、再実行は基本的に安全に収束します。ただし、リソース作成は成功したが state への書き込み前にプロセスが落ちた場合など、実体と state がズレることがあります。再 apply 前にリフレッシュ込みの plan で実体と state の整合を必ず確認してください。
収束を支える冪等性
部分失敗からの再 apply が安全なのは、各操作が 冪等(idempotent) ——同じ設定で何度実行しても結果が同じ——だからです(/devops/idempotency-exactly-once/)。再 apply のたびにツールは依存グラフを組み直し、各リソースについて「設定 と 実体(state)」を比較し、一致していれば何もしません。
再 apply の収束ループ(概念):
1. 設定からグラフを再構築(順序・並列度は毎回計算)
2. 各ノードで desired と current を比較
一致 → no-op(成功済みは触らない)
未作成 → create(失敗していた b を作る)
差分あり → update / replace
3. 依存順に従って実行 → 全ノード一致で収束(差分ゼロ)
依存グラフは毎回の apply で再計算される 動的な構造 です。設定が変われば辺が変わり、順序も並列度も変わります。だからこそ「一度組んだ順序」を保存する必要がなく、設定こそが唯一の真実になります。この性質は、設定を Git に集約し収束を継続的に回す GitOps の発想と地続きです(/devops/gitops-reconciliation/)。
| 観点 | 依存グラフ(IaC) | 命令的スクリプト |
|---|---|---|
| 順序の指定 | 参照から自動導出 | 人手で逐次記述 |
| 並列化 | グラフの幅から自動 | 自前で制御が必要 |
| 循環の扱い | 構築時に検出し拒否 | 実行時まで気づかない |
| 失敗時 | ロールバックせず再適用で収束 | 途中状態が残り手動復旧 |
| 再実行 | 冪等で安全 | 副作用が二重化しやすい |
まとめ
- 順序は参照から導かれる。属性参照が暗黙依存となり辺を張り、depends_on は推論できない制約だけを補う。設定を書くことが順序を宣言することと同義になる。
- DAG とトポロジカルソートが適用順を決める。閉路が無ければ妥当な順序が必ず存在し、互いに依存しないノードは並列適用できる。並列度はグラフの幅、最短時間はクリティカルパスで決まる。
- 削除・置換は辺を逆向きに辿る。依存する側から消す。これも非巡回だからこそ定義できる。
- 循環依存は事前に検出して拒否する。解消は参照の張り替えか、関係を独立リソースへ外出しすること。
- apply はトランザクションではない。途中失敗しても成功分は戻らず state に残る。巻き戻しではなく、冪等な再 apply による収束で全体をあるべき姿へ寄せるのが宣言的 IaC の原理。
DevOps/インフラ Article
依存グラフによるプロビジョニング順序決定を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
IaC
比較で見る軸
難易度: advanced / カテゴリ: DevOps/インフラ / タグ数: 6
導入後に効く点
依存に循環があると順序が定義できないため、グラフ構築時に閉路を検出して apply 自体を拒否する。閉路の解消は参照の張り直しか中間リソースの導入で行う。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- DevOps/インフラ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「IaC / Terraform」に近いか確認する。
- 強みである「Terraform 等は設定中の属性参照(暗黙依存)と depends_on(明示依存)から有向非巡回グラフ(DAG)を構築し、トポロジカルソートで適用順を、依存のないノード集合の同時実行で並列度を決める。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。