Организация проекта и окружения

Хороший проект 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.

Проверьте себя
1. Почему опасно держать dev и prod в одном state?
AState будет слишком большим
BОшибка в dev может физически задеть prod-ресурсы
CЗамедлится init
DЭто запрещено лицензией
2. Чем папки на окружение предпочтительнее workspaces для важных сред?
AРаботают быстрее
BДают явную изоляцию: свой бэкенд и код, труднее случайно применить не туда
CНе требуют state
DШифруют state