Организация проекта и окружения
Хороший проект Terraform изолирует окружения: у dev, staging и prod отдельные state, чтобы ошибка в dev физически не могла задеть prod. Способов изоляции два — папки и workspaces.
Главный вопрос организации — не «как красиво разложить файлы», а «как сделать так, чтобы случайный apply в dev никогда не уронил prod». Ответ — раздельный state.
Внутри одного окружения файлы традиционно делят по роли: main.tf (ресурсы), variables.tf (входы), outputs.tf (выходы), providers.tf (провайдеры). Terraform всё равно читает все .tf в папке как одно целое — деление чисто для людей.
Папки против workspaces
environments/
dev/
main.tf # module "app" { source = "../../modules/app" }
dev.tfvars
backend.tf # key = "dev/terraform.tfstate"
prod/
main.tf
prod.tfvars
backend.tf # key = "prod/terraform.tfstate"
modules/
app/ # переиспользуемый child-модуль
Папки на окружение — самый явный и безопасный путь: у каждого свой бэкенд, свой state, разный код виден глазами. Workspaces (terraform workspace new prod) дают один код и несколько state, но изоляция слабее и легко ошибиться текущим воркспейсом. Для важных окружений предпочитают папки.
ВАРИАНТ A: папки ВАРИАНТ B: workspaces dev/ -> state dev один код prod/ -> state prod ws "dev" -> state dev явно, безопасно ws "prod" -> state prod (чуть больше копий) (легко перепутать ws!)
Как работает под капотом
Изоляция окружений — это изоляция state по ключу. Перепутать окружение = записать в чужой state. Смоделируем выбор state по окружению и цену ошибки:
states = {} # эмулируем удалённый бэкенд: ключ -> ресурсы
def apply(env, resources):
key = f"{env}/terraform.tfstate" # ключ состояния = окружение
states[key] = resources
return key
apply("dev", {"web": "t2.micro", "db": "t3.micro"})
apply("prod", {"web": "t3.large", "db": "t3.large"})
print("Ключи state:", list(states.keys()))
print("prod нетронут изменением dev:", states["prod/terraform.tfstate"]["web"])
# опасность: забыли сменить окружение -> запись в prod вместо dev
apply("prod", {"web": "t2.nano"}) # ОЙ, думали что dev
print("prod после ошибки:", states["prod/terraform.tfstate"]["web"])
«Попробуй сам ▶» — пока ключи разные, окружения изолированы. Но последняя строка показывает цену путаницы: апплай ушёл не туда. Отдельные папки делают такую ошибку заметнее.
Частые ошибки
- Один state на всё. Dev и prod в одном state — ошибка в dev рушит prod. Никогда так.
- Забыть текущий workspace.
applyв неверном воркспейсе бьёт по чужому окружению. - Гигантский монорут. Сотни ресурсов в одном state делают plan медленным и опасным; дробите по доменам.
Best practices
- Раздельный state на окружение (отдельный ключ бэкенда) — базовое правило изоляции.
- Для критичных окружений предпочитайте папки воркспейсам: явность важнее экономии строк.
- Дробите крупную инфраструктуру на несколько state по доменам (сеть, данные, приложения).
Разбор глубже
Деление файлов на main.tf, variables.tf и outputs.tf — это чистая условность для людей: Terraform склеивает все .tf файлы в папке в одну конфигурацию, и имена файлов ему безразличны. Но условность полезная: когда коллега открывает чужой модуль, он по привычным именам сразу знает, где искать входы, где выходы, а где основная логика. На крупных модулях ресурсы дополнительно разбивают по доменам (network.tf, compute.tf, iam.tf), чтобы не утонуть в одном гигантском файле. Главное — единообразие внутри команды.
Решение «один большой state против многих маленьких» — это компромисс между удобством и риском. Один state на всё проще в навигации, но любой plan становится медленным, а blast radius ошибки — огромным: повреждение state задевает всю инфраструктуру разом. Поэтому крупные системы режут на отдельные state по доменам и по жизненному циклу: редко меняющаяся сеть — отдельно, часто деплоящееся приложение — отдельно. Так команда приложения катит релизы по десять раз в день, не трогая и не рискуя стейтом сети. Цена — необходимость передавать значения между state, но это честный размен ради изоляции и скорости.
Итог: изоляция окружений = раздельный state по ключу; папки безопаснее workspaces для важного. Дробите большое на домены. Дальше — как это всё гонять в CI/CD.