Когда одна конфигурация инфраструктуры должна обслуживать несколько окружений

У вас есть Terraform-конфигурация, которая описывает VPC, подсети и балансировщик нагрузки. Она отлично работает для окружения разработки. Теперь нужно то же самое для staging и production. Наивный подход — скопировать всю папку три раза и поменять несколько значений переменных. Это работает до тех пор, пока не потребуется обновить правило security group, и вы не осознаете, что нужно вносить одно и то же изменение в трех местах, надеясь ничего не пропустить.

Проблема не в написании кода инфраструктуры. Проблема в управлении одним и тем же кодом в нескольких окружениях без дублирования всего подряд и без риска случайно сломать production, работая над development.

Два распространенных подхода решают эту задачу: workspaces и отдельные root-модули. Они принципиально по-разному подходят к одной и той же проблеме, и выбор между ними зависит от того, как работает ваша команда и насколько сильно нужно изолировать окружения.

Workspaces: Одна кодовая база, несколько состояний

Workspaces — это встроенная возможность таких инструментов, как Terraform, позволяющая использовать один и тот же исходный код с разными файлами состояния. Представьте себе одну папку с конфигурацией, которая может порождать три разных окружения. Код идентичен. Меняется только файл состояния, который отслеживает, что было развернуто.

Рабочий процесс прост. Вы создаете workspace для каждого окружения, затем переключаетесь на нужный workspace перед внесением любых изменений. Инструмент автоматически направляет чтение и запись состояния в правильное место в бэкенде в зависимости от того, какой workspace активен. Если вы находитесь в workspace разработки, ваши изменения затрагивают только состояние разработки. Состояние production остается нетронутым.

Вот как этот процесс выглядит на практике:

# Создаем workspace для каждого окружения
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod

# Переключаемся на workspace разработки
export TF_WORKSPACE=dev
# Или: terraform workspace select dev

# Планируем и применяем изменения для активного workspace
export TF_VAR_instance_type=t3.micro
terraform plan -out=dev.tfplan
terraform apply dev.tfplan

# Переключаемся на production, когда готовы
export TF_WORKSPACE=prod
export TF_VAR_instance_type=t3.large
terraform plan -out=prod.tfplan
terraform apply prod.tfplan
flowchart TD subgraph Workspaces A1["Одна кодовая база"] --> B1["Workspace: dev"] A1 --> C1["Workspace: staging"] A1 --> D1["Workspace: prod"] B1 --> E1["State: dev"] C1 --> F1["State: staging"] D1 --> G1["State: prod"] E1 --> H1["Один бэкенд"] F1 --> H1 G1 --> H1 end subgraph Root_Modules I1["Общий модуль"] --> J1["Root Config: dev"] I1 --> K1["Root Config: staging"] I1 --> L1["Root Config: prod"] J1 --> M1["Backend: dev"] K1 --> N1["Backend: staging"] L1 --> O1["Backend: prod"] end

Этот подход хорошо работает, когда ваши окружения похожи, а различия ограничиваются значениями переменных. Возможно, в разработке используются инстансы меньшего размера, в staging — средние, а в production — самые большие. Структура одна и та же. Меняются только цифры.

Но у workspaces есть скрытая цена. Поскольку все окружения живут в одной кодовой базе, риск случайно сработать не с тем workspace реален. Разработчик, работающий в workspace разработки, может случайно выполнить plan или apply для production, если забудет переключиться. Инструмент этого не предотвращает. Он только отслеживает, какой workspace активен. Барьером безопасности становится человеческое внимание.

Есть и другое ограничение. Когда вы меняете структуру конфигурации, изменения применяются сразу ко всем окружениям. Вы не можете сначала обновить staging, проверить, что все работает, а затем раскатить то же изменение на production днем позже. Все окружения движутся синхронно, потому что используют один и тот же код. Если изменение что-то ломает, оно ломает все одновременно.

Root-модули: Отдельные папки, общая логика

Альтернатива — дать каждому окружению собственный root-модуль. Вместо одной папки с тремя workspaces у вас есть три папки: dev, staging и prod. Каждая папка содержит свой собственный файл основной конфигурации и свою конфигурацию бэкенда состояния. Они полностью независимы.

Но вы не переписываете логику инфраструктуры трижды. Каждый root-модуль вызывает одни и те же общие модули из директории modules. Определение VPC, логика подсетей, конфигурация балансировщика — все это живет в переиспользуемых модулях. Root-модули просто вызывают эти модули с переменными, специфичными для окружения.

Преимущество очевидно. Вы не можете случайно изменить production, работая в development, потому что вам нужно явно перейти в другую директорию. Разделение физическое, а не только логическое. Вы также можете обновлять окружения независимо. Production может оставаться на стабильной версии модуля, пока в разработке используется последняя версия с экспериментальными изменениями. Staging можно обновить первым, протестировать и только потом продвигать в production.

Обратная сторона — дублирование, но это структурное дублирование, а не логическое. Вы повторяете вызовы модулей и определения переменных для каждого окружения. Вы не повторяете саму логику инфраструктуры. Модули содержат логику, и они общие. Дублирование происходит в «проводке», а не в реализации.

Когда что использовать

Workspaces подходят небольшим командам с малым количеством окружений и минимальными различиями между ними. Если у вас два почти идентичных окружения, workspaces снижают накладные расходы. Вы пишете код один раз, а workspace обеспечивает разделение состояний.

Root-модули лучше работают для больших команд, окружений, требующих строгой изоляции, или ситуаций, где для каждого окружения нужны разные процессы согласования. Если изменения в production требуют ревью пул-реквеста, одобрения менеджера и окна изменений, а изменения в разработке можно применять напрямую, root-модули делают такое разделение естественным. В каждой папке окружения может быть свой CI/CD-пайплайн с разными шлюзами.

Многие команды начинают с workspaces, потому что их проще настроить. По мере роста команды и увеличения числа окружений они мигрируют на root-модули. Миграция не болезненна, потому что модули уже разделены. Вы просто создаете новые корневые папки, которые вызывают те же модули.

Практический чек-лист

Прежде чем выбирать подход, ответьте на эти вопросы:

  • Сколько окружений вы управляете? Больше трех — склоняйтесь к root-модулям.
  • Нужны ли для окружений разные процессы согласования? Если да — root-модули.
  • Может ли ошибка в одном окружении повлиять на другое? Если да, root-модули снижают этот риск.
  • Размер вашей команды больше пяти человек? Большим командам полезно явное разделение по папкам.
  • Нужно ли обновлять окружения в разное время? Root-модули делают поэтапные развертывания возможными.

Если вы ответили «да» на три или более из этих вопросов, начинайте с root-модулей. Если вы ответили «нет» на большинство из них, workspaces пока вам подойдут.

Конкретный вывод

Workspaces и root-модули решают одну и ту же проблему с разными компромиссами. Workspaces уменьшают дублирование кода, но увеличивают операционный риск. Root-модули добавляют структурное дублирование, но дают четкие границы между окружениями. Выбирайте, исходя из того, как работает ваша команда и насколько сильная изоляция нужна вашим окружениям. Начинайте с простого, но знайте, когда нужно переключиться. Цель не в том, чтобы выбрать идеальный подход в первый же день. Цель — вовремя заметить, что текущий подход начинает создавать больше проблем, чем решает.