2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terragruntの循環依存がサイレントに失敗する問題と検出方法

Posted at

Terragruntの循環依存がサイレントに失敗する問題と検出方法

このメモはこんな人向け

  • Terragruntで複数モジュールを運用している/これから導入する人
  • CI/CDでterragrunt validate --allを回している(あるいは回したい)人
  • VPC Peeringやクロススタック参照など、モジュール間依存が多い構成を扱う人

はじめに

Terragruntで複数のモジュール間に循環依存(circular dependency)を作ってしまうと、どうなるのか?
今回、AWS VPC Peeringの実装を通じて、Terragruntが循環依存をサイレントに無視してハングするという重大な問題を発見しました。

この記事では、問題の再現方法、検出手段、そして汎用的な検出スクリプトの作成方法を紹介します。

TL;DR

  • terragrunt validate --all は循環依存があってもエラーを出さずにハングする
  • 個別実行 (cd module && terragrunt validate) では循環依存エラーが表示される
  • 循環依存を確実に検出するには、全モジュールを個別にvalidateする必要がある
  • 汎用的な検出スクリプトを作成して解決

実験: 循環依存の作成

構成


AWS VPC Peeringで相互参照する構成を意図的に作成しました。

infra/
├── tf/modules/
│   ├── network1/          # VPC + Peering (network2へ)
│   └── network2/          # VPC + Peering (network1へ)
└── tg/live/dev/
    ├── network1/terragrunt.hcl
    └── network2/terragrunt.hcl

モジュール構成


modules/network1/main.tf:

resource "aws_vpc" "this" {
  cidr_block = "10.1.0.0/16"
  tags = { Name = "vpc-network1" }
}

resource "aws_vpc_peering_connection" "to_network2" {
  vpc_id        = aws_vpc.this.id
  peer_vpc_id   = var.peer_vpc_id  # network2のVPC IDが必要
  auto_accept   = true
  tags = { Name = "peering-network1-to-network2" }
}


modules/network2/main.tf:

resource "aws_vpc" "this" {
  cidr_block = "10.2.0.0/16"
  tags = { Name = "vpc-network2" }
}

resource "aws_vpc_peering_connection" "to_network1" {
  vpc_id        = aws_vpc.this.id
  peer_vpc_id   = var.peer_vpc_id  # network1のVPC IDが必要
  auto_accept   = true
  tags = { Name = "peering-network2-to-network1" }
}

Terragrunt設定で循環依存を作る


live/dev/network1/terragrunt.hcl:

terraform {
  source = "../../../../tf/modules/network1"
}

# network2に依存
dependency "network2" {
  config_path = "../network2"

  mock_outputs = {
    vpc_id = "vpc-mock-network2"
  }
  mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}

inputs = {
  peer_vpc_id = dependency.network2.outputs.vpc_id
}


live/dev/network2/terragrunt.hcl:

terraform {
  source = "../../../../tf/modules/network2"
}

# network1に依存(循環!)
dependency "network1" {
  config_path = "../network1"

  mock_outputs = {
    vpc_id = "vpc-mock-network1"
  }
  mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}

inputs = {
  peer_vpc_id = dependency.network1.outputs.vpc_id
}


これで network1 → network2 → network1 の循環依存が完成です。

問題発見: サイレントな失敗

--all オプションでハングする

$ cd infra/tg/live/dev
$ terragrunt validate --all
# ... 何も出力されず、永遠に待機 ...


エラーメッセージなし。タイムアウトまで応答なし。

これは非常に危険で、CI/CDパイプラインで使用すると、原因不明のタイムアウトに悩まされることになります。
ここで次の方法を使います!​

個別実行では検出される!

$ cd infra/tg/live/dev/network1
$ terragrunt validate
​
ERROR: Found a dependency cycle between modules:
./terragrunt.hcl -> ../network2/terragrunt.hcl -> ./terragrunt.hcl


個別実行では明確にエラーが表示されます。

試した検出方法


様々な方法を試して、循環依存を検出できるか検証しました。

コマンド 結果 循環依存検出
terragrunt validate --all ハング
terragrunt dag graph ハング
terragrunt output-module-groups ハング
terragrunt validate --all --terragrunt-log-level debug ハング
cd module && terragrunt validate エラー表示
terragrunt find --json 成功(ユニットをリスト) ⚠️

有効な検出方法

✅ 方法1: 個別validate(最も確実)

cd network1
terragrunt validate
# ERROR: Found a dependency cycle between modules...

✅ 方法2: タイムアウトで検出

timeout 5 terragrunt validate --all
if [ $? -eq 124 ]; then
  echo "❌ Timeout - likely circular dependency!"
fi


終了コード 124 = タイムアウト = 循環依存の可能性

✅ 方法3: すべてのモジュールを自動チェック

echo "=== network1 ===" && \
(cd network1 && terragrunt validate 2>&1) | grep -i cycle
​
echo "=== network2 ===" && \
(cd network2 && terragrunt validate 2>&1) | grep -i cycle

解決策: 汎用検出スクリプト


現在のディレクトリ配下のすべての terragrunt.hcl を自動検出し、循環依存をチェックするスクリプトを作成しました。

check-circular-deps.sh:

#!/bin/bash# Terragrunt循環依存チェックスクリプト
# 使い方: ./check-circular-deps.sh [root_directory]set -eROOT_DIR="${1:-.}"
CYCLE_FOUND=false
TOTAL_CHECKED=0
CYCLE_COUNT=0
​
echo "🔍 Checking for circular dependencies in Terragrunt modules..."
echo "Root directory: $(cd "$ROOT_DIR" && pwd)"
echo ""# terragrunt.hclファイルを再帰的に検索
while IFS= read -r -d '' hcl_file; do
    dir=$(dirname "$hcl_file")
    relative_path=$(realpath --relative-to="$ROOT_DIR" "$dir")TOTAL_CHECKED=$((TOTAL_CHECKED + 1))echo "[$TOTAL_CHECKED] Checking: $relative_path"# 各ディレクトリでterragrunt validateを実行
    if output=$(cd "$dir" && terragrunt validate 2>&1); then
        echo "    ✓ No issues"
    else
        # エラー出力から循環依存を検出
        if echo "$output" | grep -qi "cycle"; then
            echo "    ❌ CIRCULAR DEPENDENCY DETECTED"
            echo "$output" | grep -i "cycle" | sed 's/^/       /'
            CYCLE_FOUND=true
            CYCLE_COUNT=$((CYCLE_COUNT + 1))
        else
            # その他のエラー
            echo "    ⚠️  Other error (not circular dependency)"
            echo "$output" | head -3 | sed 's/^/       /'
        fi
    fi
    echo ""
done < <(find "$ROOT_DIR" -name "terragrunt.hcl" -type f -print0)echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📊 Summary:"
echo "   Total modules checked: $TOTAL_CHECKED"
echo "   Circular dependencies found: $CYCLE_COUNT"
echo ""if [ "$CYCLE_FOUND" = true ]; then
    echo "❌ 循環依存が見つかりました!修正が必要です。"
    exit 1
else
    echo "✅ 循環依存は見つかりませんでした。"
    exit 0
fi

使用方法

# カレントディレクトリ以下をチェック
./check-circular-deps.sh
​
# 特定のディレクトリをチェック
./check-circular-deps.sh tg/live/dev
​
# CI/CDで使用
./check-circular-deps.sh || exit 1

実行例

🔍 Checking for circular dependencies in Terragrunt modules...
Root directory: /path/to/infra/tg/live/dev
​
[1] Checking: network1
    ❌ CIRCULAR DEPENDENCY DETECTED
       * Found a dependency cycle between modules: ./terragrunt.hcl -> ../network2/terragrunt.hcl -> ./terragrunt.hcl
​
[2] Checking: network2
    ❌ CIRCULAR DEPENDENCY DETECTED
       * Found a dependency cycle between modules: ./terragrunt.hcl -> ../network1/terragrunt.hcl -> ./terragrunt.hcl
​
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Summary:
   Total modules checked: 2
   Circular dependencies found: 2
​
❌ 循環依存が見つかりました!修正が必要です。

VPC Peeringの正しい実装パターン


循環依存を避けるには、以下のいずれかの方法を使用します:

パターン1: 2フェーズアプローチ

  1. Phase 1: VNetのみ作成
  2. Phase 2: Peeringを別モジュールで作成

パターン2: 片方向依存

network1 (独立してVPC作成)
   ↑
network2 (network1のVPC IDを参照してPeering作成)


network2で両方向のPeeringを管理します。

パターン3: 専用Peeringモジュール

network1 (VPCのみ)
network2 (VPCのみ)
   ↓
peering (両方のVPC IDを参照)

まとめ

問題点

  1. terragrunt validate --all は循環依存を検出できない
  2. エラーメッセージなしでハングする
  3. デバッグログを有効にしても情報が得られない

解決策

  1. 個別validateで確実に検出
  2. 汎用スクリプトで自動化
  3. CI/CDに組み込んで早期発見

推奨事項

  • CI/CDパイプラインに循環依存チェックを組み込む
  • --all オプションには過度に依存しない
  • 設計段階で依存関係を可視化する
  • VPC Peeringなど相互参照が必要な場合は、専用モジュールを検討

参考リンク


あるべき状態(Definition of Done)

  • 循環依存が存在する場合はCI/CDで即時に検知・失敗する
  • terragrunt validate --allのみに依存せず、全モジュール個別validateまたは検出スクリプトを組み込んでいる
  • 依存関係設計が可視化され、VPC Peeringなど相互参照は専用モジュール/片方向/2フェーズなど安全なパターンで実装されている
  • 開発/本番いずれの環境でも循環依存検査のチェックリストが運用されている
  • Terragrunt公式ドキュメント - Dependencies
  • GitHub Issue: Circular dependency detection


Environment:

  • Terragrunt: latest
  • Terraform: 1.0+
  • AWS Provider: 6.0+

    この記事が、Terragruntの循環依存問題に悩む方の助けになれば幸いです!
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?