Terragrunt入門:公式Quickstartを実践してみた
Terragruntの公式ドキュメントにあるQuickstartチュートリアルを実際に手を動かしながら進めてみました。この記事では、Terragruntの基本的な概念から依存関係管理まで、段階的に学んだ内容をまとめます。
Terragruntとは
TerragruntはTerraform/OpenTofuのラッパーツールで、以下のような課題を解決します:
- DRY(Don't Repeat Yourself): 複数環境での設定の重複を排除
- 依存関係管理: モジュール間の依存関係を自動的に解決
- 一括操作: 複数のTerraformモジュールを一度に操作
前提条件
- Terraform または OpenTofu がインストール済み
- Terragrunt がインストール済み
- Unix系OS(macOS, Linux等)
1. 最初のTerragruntプロジェクト
基本構成
まず、fooディレクトリにシンプルなTerraformファイルを作成します。
foo/
├── main.tf
└── terragrunt.hcl
foo/main.tf
variable "content" {}
resource "local_file" "file" {
content = var.content
filename = "${path.module}/hi.txt"
}
foo/terragrunt.hcl
inputs = {
content = "Hello from foo, Terragrunt!"
}
実行
cd foo
terragrunt apply
Terragruntを使う利点:
-
terraform initを自動実行(Auto-init機能) -
inputsブロックで変数を宣言的に管理(-varフラグ不要)
2. ユニットの追加と共有モジュール
ユニットとは
Terragruntにおける「ユニット」は、terragrunt.hclを含むディレクトリのことで、単一のインフラストラクチャを表します。
共有モジュールの作成
fooをコピーしてbarを作成し、共通ロジックをsharedディレクトリに切り出します。
quickstart/
├── foo/
│ ├── main.tf
│ └── terragrunt.hcl
├── bar/
│ ├── main.tf
│ └── terragrunt.hcl
└── shared/
└── main.tf
shared/main.tf
variable "content" {}
resource "local_file" "file" {
content = var.content
filename = "${path.module}/hi.txt"
}
foo/main.tf
variable "content" {}
module "shared" {
source = "../shared"
content = var.content
}
この構成でも動作しますが、foo/main.tfとbar/main.tfに重複があります。
3. DRY構成への進化
terraformブロックでソースを指定
各ユニットのmain.tfを削除し、terragrunt.hclのterraformブロックでソースを指定します。
quickstart/
├── foo/
│ └── terragrunt.hcl
├── bar/
│ └── terragrunt.hcl
└── shared/
└── main.tf
foo/terragrunt.hcl
terraform {
source = "../shared"
}
inputs = {
content = "Hello from foo, Terragrunt!"
}
bar/terragrunt.hcl
terraform {
source = "../shared"
}
inputs = {
content = "Hello from bar, Terragrunt!"
}
ポイント
- Terraformコードは
shared/にのみ存在 - 各環境の差分は
terragrunt.hclのinputsで管理 -
.tfファイルの重複が完全に排除される
4. スタック管理(複数ユニットの一括操作)
スタックとは
「スタック」は、一緒に管理されるユニットの集合です。dev/staging/prod環境やプロジェクト全体を指します。
run-allコマンド
親ディレクトリから複数ユニットを一括操作できます。
cd quickstart
terragrunt run-all plan # 全ユニットでplan
terragrunt run-all apply # 全ユニットでapply
実行例:
The stack at . will be processed in the following order for command apply:
Group 1
- Module ./bar
- Module ./foo
Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n)
Terragruntは並列実行し、各ユニットの出力を区別して表示します。
5. DAG(依存関係)管理
依存関係の設定
barがfooの出力に依存する場合を考えます。
shared/output.tf(追加)
output "content" {
value = local_file.file.content
}
bar/terragrunt.hcl
terraform {
source = "../shared"
}
dependency "foo" {
config_path = "../foo"
}
inputs = {
content = "Foo content: ${dependency.foo.outputs.content}"
}
DAGによる実行順序の自動決定
run-all実行時、Terragruntは依存関係を分析してDAG(有向非巡回グラフ)を構築します。
Group 1: foo (先に実行)
Group 2: bar (fooの後に実行)
これにより、barはfooの出力を確実に参照できます。
6. mock_outputs(依存先のoutputがない場合のフォールバック)
問題
fooがまだapplyされていない状態でbarをplanしようとするとエラーになります。
../foo/terragrunt.hcl is a dependency of ./terragrunt.hcl but detected no outputs.
Either the target module has not been applied yet, or the module has no outputs.
解決:mock_outputs
mock_outputsを設定することで、依存先のoutputがない場合でもplanが可能になります。
bar/terragrunt.hcl
terraform {
source = "../shared"
}
dependency "foo" {
config_path = "../foo"
mock_outputs = {
content = "Mocked content from foo"
}
}
inputs = {
content = "Foo content: ${dependency.foo.outputs.content}"
}
mock_outputsの動作
| フェーズ | fooのstate | barで使われる値 |
|---|---|---|
| plan(foo未apply) | なし | mock_outputsの値 |
| apply | あり | fooの実際のoutput |
| plan(foo apply済み) | あり | fooの実際のoutput |
run-all applyの場合、DAGに従ってfooが先にapplyされるため、barのapply時には実際の値が使用されます。
7. mock_outputs_allowed_terraform_commands
用途
CIパイプラインなどで「plan時はmockでOKだが、apply時に依存先が未applyの状態は危険なので禁止したい」場合に使用します。
bar/terragrunt.hcl
terraform {
source = "../shared"
}
dependency "foo" {
config_path = "../foo"
mock_outputs = {
content = "Mocked content from foo"
}
mock_outputs_allowed_terraform_commands = ["plan"] # planのみ許可
}
inputs = {
content = "Foo content: ${dependency.foo.outputs.content}"
}
動作確認
fooのstateがない状態で:
| コマンド | mock使用 | 結果 |
|---|---|---|
terragrunt plan |
許可 | 成功(mock値使用) |
terragrunt apply |
禁止 | エラー |
terragrunt run-all apply |
不要 | 成功(DAG順で実行) |
8. .terragrunt-cache と path.module
ファイルが作成される場所
${path.module}/hi.txtを使用すると、ファイルは.terragrunt-cache内に作成されます。
foo/.terragrunt-cache/xxxxx/yyyyy/hi.txt ← ここに作成される
理由
Terragruntの動作フロー:
-
foo/でterragrunt applyを実行 -
shared/のコードをfoo/.terragrunt-cache/xxxxx/yyyyy/にコピー - そのキャッシュディレクトリ内でTerraformを実行
このため、path.moduleはキャッシュディレクトリを指します。
get_terragrunt_dir()で解決
terragrunt.hclが存在するディレクトリにファイルを作成したい場合は、get_terragrunt_dir()を使用します。
shared/main.tf
variable "content" {}
variable "output_dir" {}
resource "local_file" "file" {
content = var.content
filename = "${var.output_dir}/hi.txt"
}
foo/terragrunt.hcl
terraform {
source = "../shared"
}
inputs = {
content = "Hello from foo, Terragrunt!"
output_dir = get_terragrunt_dir() # "/path/to/foo" が渡される
}
関連する組み込み関数
| 関数 | 説明 |
|---|---|
get_terragrunt_dir() |
terragrunt.hclが存在するディレクトリのパス |
get_parent_terragrunt_dir() |
親のterragrunt.hclが存在するディレクトリのパス |
get_repo_root() |
Gitリポジトリのルートディレクトリのパス |
まとめ
Terragruntを使うことで:
-
DRY構成:
terraform { source = "..." }で共通モジュールを参照し、inputsで環境差分を管理 -
スタック管理:
run-allで複数ユニットを一括操作 -
DAG:
dependencyブロックで依存関係を定義し、実行順序を自動決定 - mock_outputs: 依存先が未applyでもplanを実行可能
- mock_outputs_allowed_terraform_commands: mockの使用を特定コマンドに制限
Terragruntは、Terraform/OpenTofuプロジェクトをスケールさせる際の複雑さを大幅に軽減してくれるツールです。
Discussion