「moduleを使っても、呼び出し側のroot moduleが環境ごとにほぼ同じ内容で増えていく……」
Terraformは設計の自由度が高い反面、backendブロックにはTerraform変数を使えないという制約があります。環境ごとにbackend設定を書き分けると、dev・stg・prodで同じS3バケット名とDynamoDBテーブル名の指定が3ファイルに散らばり、変更のたびに全ファイルを手動で更新しなければなりません。
この記事では、そのギャップを埋めるツールとして現場で広く使われているTerragruntの設計パターンを解説します。includeブロックで共通設定を一元管理し、dependencyブロックでモジュール間データを受け渡す方法を、実際のディレクトリ構成とHCLコードで説明します。
この記事のポイント
・TerraformのbackendにはHCL変数が使えないため、環境差分が重複しやすい
・Terragruntのincludeブロックで親設定を継承し、backend設定を1箇所に集約できる
・dependencyブロックでVPC→EC2のようなモジュール間outputを安全に参照できる
・run-allコマンドで依存グラフを自動解決しながら複数モジュールを一括実行できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜTerraformだけではDRYな設計が難しいのか
Terraformでマルチ環境を管理する場合、まず思い浮かぶのがworkspaceによる切り替えかディレクトリ分割かという判断です。workspaceはtfstateを分けてくれますが、backend設定は共用になるためリソース設定の分離が甘くなります。ディレクトリ分割(environments/dev・environments/stg・environments/prod)はより堅牢ですが、今度は重複の問題が浮上します。Terraformの
backendブロックは、HCL変数(var.*やlocal.*)を使うことができません。次のような書き方はエラーになります。# NG: backendブロックで変数は使えない terraform { backend "s3" { bucket = var.tfstate_bucket # エラー key = "${var.env}/terraform.tfstate" # エラー } }
backend.tfを置くか、-backend-configフラグで外から渡すかを選ぶことになります。前者は重複、後者はコマンド実行時の引数管理が複雑になります。さらに、moduleを呼び出すroot module(
main.tf)も、環境ごとに同じmoduleブロックをほぼ同じ内容で書くことになりがちです。変数ファイル(-var-file)で差分を吸収できる部分もありますが、backend設定だけはどうにもなりません。この構造的な制約を解決するのがTerragruntの役割です。Terragruntの基本構造——terragrunt.hclとディレクトリレイアウト
Terragruntは、Terraformのラッパーツールです。terragrunt.hclという設定ファイルを読み込み、backend設定の動的生成やモジュール間の依存管理を肩代わりします。インストールはバイナリを1つ配置するだけです。# Terragruntのインストール(v0.67.x) curl -L https://github.com/gruntwork-io/terragrunt/releases/download/v0.67.4/terragrunt_linux_amd64 \ -o /usr/local/bin/terragrunt chmod +x /usr/local/bin/terragrunt terragrunt --version # terragrunt version v0.67.4
infra/ ├── terragrunt.hcl # ルートの共通設定(backend・provider) ├── modules/ # 再利用可能なTerraformモジュール │ ├── vpc/ │ │ ├── main.tf │ │ ├── variables.tf │ │ └── outputs.tf │ └── ec2/ │ ├── main.tf │ ├── variables.tf │ └── outputs.tf └── environments/ ├── dev/ │ ├── vpc/ │ │ └── terragrunt.hcl # 環境ごとのterragrunt設定 │ └── ec2/ │ └── terragrunt.hcl ├── stg/ │ ├── vpc/ │ │ └── terragrunt.hcl │ └── ec2/ │ └── terragrunt.hcl └── prod/ ├── vpc/ │ └── terragrunt.hcl └── ec2/ └── terragrunt.hcl
includeブロックで共通設定を一元管理する
ルートのterragrunt.hclに、全環境共通のbackend設定とprovider設定をまとめます。# infra/terragrunt.hcl(ルート共通設定) locals { # ディレクトリ構造からaws_regionを自動検出する場合の例 aws_region = "ap-northeast-1" } generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <
path_relative_to_include()は、ルートのterragrunt.hclからみた相対パスを返す組み込み関数です。environments/dev/vpcディレクトリから実行した場合、tfstateのkeyはenvironments/dev/vpc/terraform.tfstateになります。環境名とモジュール名を含む一意のパスが自動で決まるのが大きな利点です。次に、環境側の
terragrunt.hclでルート設定を継承します。# infra/environments/dev/vpc/terragrunt.hcl include "root" { path = find_in_parent_folders() } terraform { source = "../../../../modules/vpc" } inputs = { vpc_cidr = "10.0.0.0/16" environment = "dev" }
find_in_parent_folders()は、現在のディレクトリから親ディレクトリをさかのぼってterragrunt.hclを探す関数です。ルートの設定が自動で見つかります。include "root"ブロックを書くだけで、backend設定とprovider設定の両方を継承できます。stg環境の設定は、変数の値を変えるだけです。# infra/environments/stg/vpc/terragrunt.hcl include "root" { path = find_in_parent_folders() } terraform { source = "../../../../modules/vpc" } inputs = { vpc_cidr = "10.1.0.0/16" environment = "stg" }
>> Terraform実践セミナーの詳細はこちら
dependencyブロックでモジュール間データを受け渡す
実際のインフラ設計では、VPCを作ってからEC2を作る、というようにモジュール間に依存関係があります。TerraformのみではルートmoduleにVPCとEC2を同居させるか、terraform_remote_stateを使う方法しかありませんでした。Terragruntのdependencyブロックはこれをすっきり解決します。# infra/environments/dev/ec2/terragrunt.hcl include "root" { path = find_in_parent_folders() } terraform { source = "../../../../modules/ec2" } dependency "vpc" { config_path = "../vpc" # terragrunt plan時(vpc未applyの状態)のフォールバック値 mock_outputs = { vpc_id = "vpc-00000000" private_subnet_ids = ["subnet-00000000"] } mock_outputs_allowed_terraform_commands = ["validate", "plan"] } inputs = { vpc_id = dependency.vpc.outputs.vpc_id private_subnet_ids = dependency.vpc.outputs.private_subnet_ids instance_type = "t3.micro" environment = "dev" }
dependency.vpc.outputs.*でVPCモジュールのoutputを直接参照できます。mock_outputsは、VPCがまだapplyされていない状態でEC2のplanを実行したいときのダミー値です。CI/CDのplan-onlyフェーズで依存モジュールのtfstateがない場合に必要になります。依存関係の方向は
config_pathに記述した相対パスで決まります。Terragruntはrun-allコマンド実行時にこの依存グラフを解析し、VPCが完了してからEC2を実行する順序を自動で決定します。1. mock_outputsが必要なケース
mock_outputsを使う主なケースは次の2つです。・CI/CD のPlanフェーズ: PRレビュー時にEC2のplanだけを確認したい。VPCはすでに別ブランチでapply済みだが、CI環境からはtfstateを参照できない
・新規環境の初回plan: 全モジュールを初めてapplyするとき、VPCのtfstateが存在しない状態でEC2のplanを先に確認したい
# planとvalidateのみmock_outputsを許可する設定 mock_outputs_allowed_terraform_commands = ["validate", "plan"] # applyでmock_outputsを使うとダミーIDがリソースに渡るため危険。applyは除外する
inputsとlocalsで環境差分を表現する
環境ごとの差分(環境名・CIDRレンジ・インスタンスタイプ等)はterragrunt.hclのinputsブロックに記述します。さらに、共通処理をlocalsにまとめることで、ファイル内の重複も削減できます。# infra/environments/dev/ec2/terragrunt.hcl include "root" { path = find_in_parent_folders() } locals { # 環境共通変数をまとめたYAMLファイルを読み込む(任意) env_vars = yamldecode(file(find_in_parent_folders("env_vars.yaml"))) env = local.env_vars.environment # "dev" region = local.env_vars.aws_region # "ap-northeast-1" } terraform { source = "../../../../modules/ec2" } inputs = { instance_type = "t3.micro" # dev環境は小さいインスタンス environment = local.env region = local.region }
find_in_parent_folders("env_vars.yaml")を使うと、環境ディレクトリに置いたenv_vars.yamlを自動検索して読み込めます。# infra/environments/dev/env_vars.yaml environment: dev aws_region: ap-northeast-1
environment: stgに変えるだけで、terragrunt.hcl本体は同一のコードで動きます。run-all コマンドで複数モジュールを一括実行する
terragrunt run-allは、ディレクトリツリー以下の全terrrgrunt.hclを検出し、依存関係に沿った順序でterraform plan / apply / destroyを実行します。# dev環境全体をplanする cd infra/environments/dev terragrunt run-all plan # 実行ログ例(抜粋) [terragrunt] INFO Found 2 modules in total [terragrunt] INFO Module environments/dev/vpc has no dependencies [terragrunt] INFO Module environments/dev/ec2 depends on environments/dev/vpc [terragrunt] INFO Running 'terraform plan' in environments/dev/vpc ... # ... vpc planの出力 ... [terragrunt] INFO Running 'terraform plan' in environments/dev/ec2 ... # ... ec2 planの出力 ...
run-all applyは非対話環境(CI/CD)では確認プロンプトが出ます。自動化するときは--terragrunt-non-interactiveフラグか環境変数TG_NON_INTERACTIVE=trueを設定します。# CI/CDでの一括apply(確認プロンプトをスキップ) TG_NON_INTERACTIVE=true terragrunt run-all apply # 特定ディレクトリだけをapply(通常のterraformと同様) cd infra/environments/dev/vpc terragrunt apply
よくあるエラーと対処手順
1. "Error finding parent terragrunt files above the current directory"
find_in_parent_folders()がルートのterragrunt.hclを見つけられていません。実行ディレクトリとルートのterragrunt.hclの配置場所を確認します。# 確認コマンド find . -name "terragrunt.hcl" | sort # 想定出力(ルートにterragrunt.hclがあることを確認) ./environments/dev/ec2/terragrunt.hcl ./environments/dev/vpc/terragrunt.hcl ./terragrunt.hcl ← これが見つからない場合は配置ミス
2. "dependency cycle detected"
dependencyブロックで循環参照が発生しています。モジュールAがBを参照し、モジュールBがAを参照する構成はエラーになります。dependencyブロックの方向を図に起こし、矢印が一方向になるように再設計します。3. applyでmock_outputsの値がリソースに使われてしまう
mock_outputs_allowed_terraform_commandsに"apply"が含まれているのが原因です。applyはリストから除外してください。# 修正後の設定 mock_outputs_allowed_terraform_commands = ["validate", "plan"] # "apply" は絶対に含めない
4. S3バックエンドに接続できない
AWSクレデンシャルが設定されていないか、IAMポリシーでS3バケットへのアクセスが拒否されています。# クレデンシャル確認 aws sts get-caller-identity # { # "UserId": "AIDA...", # "Account": "123456789012", # "Arn": "arn:aws:iam::123456789012:user/terraform-ci" # } # S3バケットへのアクセス確認 aws s3 ls s3://my-tfstate-bucket
Terragruntが本当に必要なプロジェクトの判断基準
Terragruntは便利なツールですが、すべてのプロジェクトに導入すべきではありません。以下の基準で判断します。・環境数 × モジュール数が多い: dev/stg/prodの3環境 × 5モジュール以上であれば導入コストに見合う効果が得られます
・backendの重複が痛い: 環境ごとにbackend.tfを手動で管理していて、更新忘れが発生している場合は導入の明確なメリットがあります
・モジュール間の依存関係がある: VPC→EC2→ALBのような依存チェーンをrun-allで自動管理できます
逆に、環境が1つだけ、またはシンプルなリソース構成の場合はTerraformのworkspaceとvar-fileで十分です。ツールを追加するとそのツール自体の学習コストと運用コストが増えます。チームメンバーのTerraform習熟度を考慮した上で導入を判断してください。
本記事のまとめ
| 機能 | TerragruntのHCL記法 | 解決する課題 |
|---|---|---|
| 共通設定の継承 | include "root" { path = find_in_parent_folders() } |
backend・provider設定の重複排除 |
| dynamic backend key | path_relative_to_include() |
環境ごとに一意のtfstate pathを自動生成 |
| モジュール間参照 | dependency { config_path = "../vpc" } |
outputを別モジュールから安全に参照 |
| CI/CDフォールバック | mock_outputs = { ... } |
依存モジュール未applyでもplanを実行可能 |
| 一括実行 | terragrunt run-all plan/apply |
依存グラフを自動解決して複数モジュールを順次実行 |
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformのインフラドリフトをPlan -refresh-onlyで検出する方法|手動変更との整合性設計と修正戦略
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます