Короткий ответ
Практичная структура — директория modules/ с переиспользуемыми модулями (сеть, кластер, база данных) и отдельная директория на каждое окружение (envs/dev, envs/staging, envs/prod), где у каждого окружения свой backend (свой state), свой *.tfvars с конкретными значениями (размер инстансов, CIDR, количество реплик) и один и тот же вызов общих модулей с разными параметрами; это даёт явную изоляцию state и прав доступа между окружениями при минимальном дублировании самой логики ресурсов.
Как это работает подробнее
каждая директория окружения — это самостоятельный корневой модуль Terraform со своим terraform init/plan/apply и своим backend key, а общий код (модули) подключается через относительный путь source = "../../modules/network", поэтому изменения в логике ресурса делаются один раз в модуле и переиспользуются во всех окружениях при следующем plan, а различия (размеры, количество, включённые фичи) остаются только в tfvars/вызовах модуля конкретного окружения.
Пример кода
envs/
dev/
main.tf # module "network" { source = "../../modules/network" ... }
backend.tf # key = "env/dev/terraform.tfstate"
dev.tfvars
prod/
main.tf
backend.tf # key = "env/prod/terraform.tfstate"
prod.tfvars
modules/
network/
database/
$ cd envs/prod && terraform init && terraform plan -var-file=prod.tfvarsЧто использовать на практике
- Практика. для минимизации дублирования самого кода-обёртки (повторяющийся блок
module "network" { source = ... }в каждом env) можно использовать generate-инструменты (Terragrunt или свой скрипт), но для небольшой команды из 3 окружений часто дешевле принять небольшое дублирование вызовов модулей, чем вносить ещё один инструмент и его кривую обучения — выбор зависит от того, сколько окружений и как часто меняется сама структура вызовов.
Подводные камни
- Ошибка. частая ошибка — пытаться сделать один универсальный корневой модуль с условиями
count = var.environment == "prod" ? 1 : 0внутри самого корневого кода вместо разделения на директории, из-за чего один state содержит вперемешку логику всех окружений и риск случайного влияния на prod из dev-ветки кода растёт; другая ошибка — держать всеtfvarsв одном файле с комментариями «для prod раскомментировать это», что провоцирует человеческую ошибку при ручном редактировании перед apply.