Terraformの「Error: Cycle detected in resource dependencies」を修正する方法

intermediate🏗️ Terraform2026-03-25| Terraform 0.13以降 / 1.x、任意のOS(Linux、macOS、Windows)、任意のクラウドプロバイダー(AWS、GCP、Azure)

Error Message

Error: Cycle detected in resource dependencies
#terraform#サイクル#依存関係#グラフ

エラーの内容

Error: Cycle detected in resource dependencies

  aws_security_group.web (expand) -> aws_instance.app (expand) -> aws_security_group.web (expand)

2つ以上のリソースが互いに循環依存しています。リソースAはリソースBが先に存在する必要があります。しかし、リソースBもリソースAを必要としています。どちらも先に作成できないため、Terraformはapplyを拒否します。

発生原因

変更を適用する前に、Terraformはすべてのリソースの有向非循環グラフ(DAG)を構築します。属性への参照があるたびにエッジが追加されます。それらのエッジが自身にループしてしまうと、グラフは非循環でなくなり、planは即座に失敗します。

よくある原因:

  • 2つのリソースがお互いの出力を参照している(例:security_group_idprivate_ipを相互参照している)
  • depends_onチェーンが誤って起点にループしてしまっている
  • 2つのモジュールが互いに出力をやり取りしている
  • リソースが自身の属性を参照している(見た目よりわかりにくい)

ステップ1:エラーのサイクルパスを読む

Terraformは常に正確なサイクルパスを出力します。何より先にこれを確認してください:

Error: Cycle detected in resource dependencies

  aws_security_group.web (expand)
  -> aws_instance.app (expand)
  -> aws_security_group.web (expand)

ループは明確です:aws_security_group.webaws_instance.appaws_security_group.webに戻る。これが出発点です。コード内のこの2つのリソースに直接向かってください。

ステップ2:依存関係グラフ全体を可視化する

リソースが多い複雑な設定の場合、視覚的なグラフで調査の手間を大幅に省けます:

terraform graph | dot -Tsvg > graph.svg

任意のブラウザでgraph.svgを開いてください。2つのノード間で矢印が双方向を向いている箇所を探します。その双方向エッジがサイクルです。Graphvizがインストールされていない場合は先にインストールしてください:

# Ubuntu/Debian
sudo apt install graphviz

# macOS
brew install graphviz

ステップ3:コード内の循環参照を特定する

以下はよくある問題パターンです。セキュリティグループとEC2インスタンスがそれぞれ相手の先行作成を必要としています:

resource "aws_security_group" "web" {
  name = "web-sg"

  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    # BAD: referencing aws_instance creates a cycle
    cidr_blocks = ["${aws_instance.app.private_ip}/32"]
  }
}

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  # BAD: this also references the security group above
  vpc_security_group_ids = [aws_security_group.web.id]
}

セキュリティグループはインスタンスのプライベートIPを必要とし、インスタンスはセキュリティグループのIDを必要とします。どちらも先に作成できません。Terraformは行き詰まります。

ステップ4:サイクルを解消する

状況に合ったオプションを選んでください。

オプションA:双方向参照を削除する

多くの場合、2つの参照のうち一方は厳密には不要です。上記の例では、セキュリティグループが本当に特定のインスタンスIPへのインバウンドを制限する必要があるでしょうか?通常はそうではありません。代わりにCIDR範囲を使用してください:

resource "aws_security_group" "web" {
  name = "web-sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    # FIXED: static CIDR, no reference to aws_instance
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web.id]
}

オプションB:aws_security_group_rule(別リソース)を使用する

両方のリソースが互いを認識する必要がある場合もあります。解決策:インバウンドルールなしでセキュリティグループを先に作成し、インスタンスが存在した後でルールをスタンドアロンリソースとして追加します。

resource "aws_security_group" "web" {
  name = "web-sg"
  # No inline ingress rules here
}

resource "aws_instance" "app" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.micro"
  vpc_security_group_ids = [aws_security_group.web.id]
}

# このリソースは両方に依存するが、どちらもこれに依存しない — サイクルなし
resource "aws_security_group_rule" "allow_instance" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["${aws_instance.app.private_ip}/32"]
  security_group_id = aws_security_group.web.id
}

ルールリソースは両方の下流に位置します。それらに依存しますが、それらはこれに依存しません。サイクルが解消されます。

オプションC:不要なdepends_onを削除する

明示的なdepends_onブロックは偶発的なサイクルのよくある原因です。Terraformは属性参照を通じて暗黙的な依存関係をすでに追跡しているため、再度宣言する必要はありません:

# このdepends_onがループを作成していないか確認する
resource "aws_s3_bucket_policy" "example" {
  bucket = aws_s3_bucket.main.id
  policy = data.aws_iam_policy_document.example.json

  # 上のバケット参照がすでに依存関係を作成している。
  # この明示的なdepends_onは冗長 — 削除してよい。
  depends_on = [aws_s3_bucket.main]
}

depends_onは、2つのリソース間に属性参照がないが一方が先に存在する必要がある場合にのみ使用してください。

オプションD:別々のモジュールにリファクタリングする

2つのモジュールにまたがるサイクルはアーキテクチャの問題です。パターン:モジュールAがモジュールBに必要なものを出力し、モジュールBがモジュールAに必要なものを出力しています。解決策は、両方が利用する共有リソースを第三のモジュールに抽出することです:

# モジュールAがモジュールBに出力し、モジュールBがAに出力し返す代わりに、
# 共有リソースを所有する第三の"shared"モジュールを作成する
module "network" {
  source = "./modules/network"
  # owns VPC, subnets, security groups
}

module "compute" {
  source = "./modules/compute"
  sg_id  = module.network.web_sg_id  # one-way dependency only
}

ステップ5:修正を確認する

まずterraform validateを実行し、次にterraform planを実行します:

terraform validate
# Expected: Success! The configuration is valid.

terraform plan
# Should produce a clean plan with no cycle errors

まだサイクルエラーが表示されますか?パスを再度読んでください。別のリソースペアを指している可能性があります。planがクリーンになるまで、新しいサイクルごとにステップ1〜4を繰り返してください。

今後の防止策

  • インラインルールと別ルールリソースを混在させない。aws_security_group_ruleリソースを使用する場合は、親のaws_security_groupからすべてのingressおよびegressブロックを削除してください。両方を混在させると競合とサイクルが発生します。
  • **コードを書く前に依存関係の方向を図に描く。**簡単な図を描くのに2分もかかりません。2つのリソース間の矢印は常に一方向でなければなりません。双方向は禁物です。
  • **リソースを追加するたびにterraform graphを実行する。**リソースが5個の時点でサイクルを発見する方が、50個になってから解きほぐすよりはるかに簡単です。
  • **モジュールレベルのdepends_onには注意する。**意図したリソースだけでなく、そのモジュール内のすべてのリソースに対して暗黙的な依存関係が作成されます。その広範な影響が予期しないサイクルを引き起こす可能性があります。

Related Error Notes