Зачем
Directory-per-env (см. tf-module-basics и интермедиат-12) даёт изоляцию, но плодит дубль:
envs/dev/main.tf # source = "../modules/app", env = "dev"
envs/stage/main.tf # source = "../modules/app", env = "stage"
envs/prod/main.tf # source = "../modules/app", env = "prod"
Каждый из трёх файлов почти одинаковый. Terragrunt убирает дубль:
envs/dev/terragrunt.hcl # include root + inputs = { env = "dev" }envs/stage/terragrunt.hcl # include root + inputs = { env = "stage" }envs/prod/terragrunt.hcl # include root + inputs = { env = "prod" }terragrunt.hcl # общий source, backend, provider
Десять строк на env вместо тридцати.
Минимальная структура
live/
├── terragrunt.hcl # root, общее для всех env
├── dev/
│ └── terragrunt.hcl # дев-специфика
├── stage/
│ └── terragrunt.hcl
└── prod/
└── terragrunt.hcl
modules/
└── app/ # обычный Terraform-модуль
└── *.tf
live/terragrunt.hcl, корневой:
remote_state {backend = "s3"
config = {bucket = "company-tf-state"
key = "${path_relative_to_include()}/terraform.tfstate"region = "us-east-1"
encrypt = true
dynamodb_table = "tf-state-lock"
}
generate = {path = "backend.tf"
if_exists = "overwrite_terragrunt"
}
}
generate "provider" {path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {region = "us-east-1"
default_tags { tags = {ManagedBy = "terragrunt"
}
}
}
EOF
}
live/dev/terragrunt.hcl, конкретный env:
include "root" {path = find_in_parent_folders()
}
terraform {source = "../../../modules/app"
}
inputs = {env = "dev"
instance_size = "t3.small"
enable_logging = false
}
Запуск:
cd live/dev
terragrunt apply
Terragrunt:
- Идёт вверх, находит
live/terragrunt.hcl, инклюдит. - Генерирует
backend.tfс правильным S3-key (dev/terraform.tfstate). - Генерирует
provider.tf. - Копирует module из
../../../modules/appв кэш-папку. - Запускает
terraform applyс inputs изdev/terragrunt.hcl.
Generate-блоки
Главная фича Terragrunt, генерация файлов. Backend, provider, что угодно. Это убирает копипасту из Terraform-модулей.
Generate-options:
if_exists = "overwrite_terragrunt", перезапись своих файлов (созданных Terragrunt'ом ранее).if_exists = "skip", пропустить, если файл существует.if_exists = "overwrite", перезапись чего угодно (опасно).
Dependency-блок
Когда prod/network нужно поднять до prod/app (потому что app
читает VPC id):
# prod/app/terragrunt.hcl
dependency "network" {config_path = "../network"
}
inputs = {vpc_id = dependency.network.outputs.vpc_id
subnet_ids = dependency.network.outputs.subnet_ids
}
Terragrunt сам найдёт prod/network/terragrunt.hcl, прогонит plan/apply
если нужно, прочитает outputs, передаст в app'у. Это явный
cross-stack dependency.
Команды
terragrunt apply # apply этого env
terragrunt plan-all # plan по всем env (parallel)
terragrunt apply-all # apply по всем env (последовательно с deps)
terragrunt destroy-all
terragrunt run-all <cmd> # любая terraform-команда на всех env
apply-all гоняется по графу dependencies, сначала network, потом
app. На сотне stack'ов это спасает.
Когда Terragrunt оправдан
| Кейс | Решение |
|---|---|
| 1 env, маленький проект | Не нужно, overkill. |
| 3 env (dev/stage/prod), copy-paste терпим | Не нужно, KISS. |
| 5+ env или 10+ stacks с одинаковой структурой | Да, экономит код. |
| Cross-stack dependencies через outputs | Да, dependency блок. |
| Хочешь стандартизировать backend для всех projects | Да, root generate. |
Anti-pattern: один env, но прогрессивно используешь Terragrunt «на будущее». Получишь сложность, не сэкономишь.
Подводные камни
-
Ещё один HCL-диалект.
terragrunt.hclпохож на терраформный, но другой. Функцииfind_in_parent_folders(),path_relative_to_include()Terragrunt-only. Учиться. -
sourceподдерживает толькоgit::для версионирования. Локальныйsource = "../../modules/app"работает, но без версии. Production-best-practice:source = "git::ssh://...modules.git//app?ref=v1.2.3". -
Cache-папка
.terragrunt-cache/. Содержит копии модулей и.terraform/. Иногда «protухает», нужноterragrunt run-all init -upgrade. -
Версии Terragrunt и Terraform: в
terragrunt.hclможно зафиксироватьterraform_version_constraintиterragrunt_version_constraint. Без них разные dev'ы / CI-runner'ы получают разное. -
apply-allбез--terragrunt-non-interactiveзависает в CI. По дефолту Terragrunt подтверждает «apply on N modules»; в pipeline добавляй флаг. -
State per stack, отдельный bucket-key per env. Не выноси все env'ы в один state, потеряешь main benefit изоляции.
-
Bloat: на больших monorepo Terragrunt'ов компиляция всех
terragrunt.hclзанимает время. Для 100+ stacks, это секунды на каждый run. -
Альтернатива, Stacks (HashiCorp). С 2024 HashiCorp выпустил Terraform Stacks, который покрывает часть Terragrunt-функций нативно. Часть, не всё. Для новых проектов оценивай оба. См. tf-stacks.