Мы активно используем Terraform для выделения ресурсов облака AWS. Наша базовая терраформная структура выглядит так:
├─ modules
├── x
├── y
├─ environments
├── dev
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
└── uat
│ ├── main.tf
│ ├── output.tf
│ └── variables.tf
└── prod
├── main.tf
├── output.tf
└── variables.tf
Когда мы достигли точки, когда у нас есть много модулей и много сред, дублирование кода становится более серьезной головной болью, мы хотели бы избавиться от него как можно больше.
В настоящее время наша основная проблема связана с файлами output.tf - каждый раз, когда мы расширяем существующий модуль или добавляем новый модуль, нам нужно настроить для него конфигурацию среды (это ожидается), но нам все равно нужно скопировать / вставить требуемый части в output.tf для вывода результатов инициализации (например, IP-адресов, AWS ARN и т. д.).
Есть ли способ избавиться от дублированных файлов output.tf? Можем ли мы просто определить желаемые выходы в самих модулях и видеть все определенные выходы всякий раз, когда мы запускаем terraform для конкретной среды?





Один из способов решить эту проблему - создать среду base, а затем создать символическую ссылку на общие элементы, например:
├─ modules
├── x
├── y
├─ environments
├── base
│ ├── output.tf
│ └── variables.tf
├── dev
│ ├── main.tf
│ ├── output.tf -> ../base/output.tf
│ └── variables.tf -> ../base/variables.tf
├── uat
│ ├── main.tf
│ ├── output.tf -> ../base/output.tf
│ └── variables.tf -> ../base/variables.tf
├── super_custom
│ ├── main.tf
│ ├── output.tf # not symlinked
│ └── variables.tf # not symlinked
└── prod
├── main.tf
├── output.tf -> ../base/output.tf
└── variables.tf -> ../base/variables.tf
Этот подход действительно работает только в том случае, если ваши файлы output.tf и variables.tf одинаковы для каждой среды, и хотя у вас могут быть варианты без символических ссылок (например, super_custom выше), это может сбивать с толку, поскольку не сразу очевидно, какие среды являются пользовательскими, а какие нет. т. YMMV. Я стараюсь, чтобы изменения между средами были ограничены файлом .tfvars для каждой среды.
Стоит прочитать Отличный пост Charity Major о файлах tfstate, который поставил меня на этот путь.
Я опубликовал еще один ответ, в котором проблема решается путем полного удаления модулей для каждой среды. Похоже, что ваши среды очень похожи, это может быть еще более чистым подходом. Если нет, не могли бы вы расширить свой вопрос дополнительной информацией о различиях между средами?
Если ваши среды dev, uat и prod имеют одинаковую форму, но разные свойства, вы можете использовать рабочие места для разделения состояния среды вместе с отдельными файлами *.tfvars для указания различных конфигураций.
Это могло выглядеть так:
├─ modules
│ ├── x
│ └── y
├── dev.tfvars
├── prod.tfvars
├── uat.tfvars
├── main.tf
├── outputs.tf
└── variables.tf
Вы можете создать новое рабочее пространство с помощью:
terraform workspace new uat
Тогда развертывание изменений становится:
terraform workspace select uat
terraform apply --var-file=uat.tfvars
Функция рабочих пространств гарантирует, что различные состояния среды управляются отдельно, что является преимуществом.
Этот подход работает только тогда, когда различия между средами достаточно малы, чтобы иметь смысл инкапсулировать логику для этого в отдельных модулях (например, наличие флага high_availability, который добавляет некоторую дополнительную избыточную инфраструктуру для uat и prod).
Мы тоже рассматривали этот подход, но лично я считаю его слишком опасным. Как только кто-то забывает установить правильный файл состояния при вызове terraform, и у нас уже большие проблемы .. Также мы используем общее состояние S3 для наших скриптов, я не уверен, что это сработает с таким подходом.
Кроме того, я считаю, что собственная домашняя страница Terraform перечисляет это как антипаттерн в разделе Best Practices, так что, по их мнению, это, вероятно, не лучший подход :(
У вас есть ссылка на это? Рабочие области были разработаны для использования отдельно для каждой среды (terraform.io/docs/enterprise/guides/recommended-practices/…). Я согласен, что это звучит рискованно, если вы вводите команды вручную, но мы обошли это, заключив эти команды в один сценарий, так что вы всегда последовательны.
Также потенциально актуальным является открытый запрос функции для автоматической загрузки файла .tfvars соответствующей рабочей области (github.com/hashicorp/terraform/issues/12845).
да, см. terraform.io/docs/state/workspaces.html, параграф «Лучшие практики». Я бы также выделил эту часть: «Сами по себе рабочие области полезны для изоляции набора ресурсов для тестирования изменений во время разработки. Например, ветвь в VCS обычно ассоциируется с временным рабочим пространством, чтобы можно было разрабатывать новые функции, не затрагивая рабочее пространство по умолчанию.». Исходя из этого, я не думаю, что рабочие места лучше всего подходят для этой проблемы.
Привет, @KristofJozsa, я думаю, что в этом разделе говорится, что рабочие области должен должны использоваться для этого класса проблем, однако, если у вас есть сложные различия в конфигурации, вам следует также использовать terraform_remote_state для инкапсуляции конфигурации в каждый модуль. Когда они говорят «Только рабочие пространства», они имеют в виду «Рабочие пространства полезны как единственный механизм изоляции для разработчиков», а не «Рабочие пространства полезны только для изоляции разработчиков».
Мы создали и открыли исходный код Террагрунт, чтобы решить эту самую проблему. Одна из функций Terragrunt - возможность загрузки удаленных конфигураций Terraform. Идея состоит в том, что вы определяете код Terraform для своей инфраструктуры только один раз, в одном репо, например, modules:
└── modules
├── app
│ └── main.tf
├── mysql
│ └── main.tf
└── vpc
└── main.tf
Это репо содержит типичный код Terraform с одним отличием: все в вашем коде, которое должно отличаться в разных средах, должно быть представлено как входная переменная. Например, модуль приложения может предоставлять следующие переменные:
variable "instance_count" {
description = "How many servers to run"
}
variable "instance_type" {
description = "What kind of servers to run (e.g. t2.large)"
}
В отдельном репо, называемом, например, live, вы определяете код для всех ваших сред, который теперь состоит только из одного файла .tfvars на компонент (например, app/terraform.tfvars, mysql/terraform.tfvars и т. д.). Это дает вам следующий макет файла:
└── live
├── prod
│ ├── app
│ │ └── terraform.tfvars
│ ├── mysql
│ │ └── terraform.tfvars
│ └── vpc
│ └── terraform.tfvars
├── qa
│ ├── app
│ │ └── terraform.tfvars
│ ├── mysql
│ │ └── terraform.tfvars
│ └── vpc
│ └── terraform.tfvars
└── stage
├── app
│ └── terraform.tfvars
├── mysql
│ └── terraform.tfvars
└── vpc
└── terraform.tfvars
Обратите внимание, что ни в одной из папок нет конфигураций Terraform (файлов .tf). Вместо этого каждый файл .tfvars определяет блок terraform { ... }, который указывает, откуда загружать код Terraform, а также зависящие от среды значения для входных переменных в этом коде Terraform. Например, stage/app/terraform.tfvars может выглядеть так:
terragrunt = {
terraform {
source = "git::[email protected]:foo/modules.git//app?ref=v0.0.3"
}
}
instance_count = 3
instance_type = "t2.micro"
А prod/app/terraform.tfvars может выглядеть так:
terragrunt = {
terraform {
source = "git::[email protected]:foo/modules.git//app?ref=v0.0.1"
}
}
instance_count = 10
instance_type = "m2.large"
См. Документация Террагрунт для получения дополнительной информации.
спасибо за Ваш ответ. Я думал о символических ссылках, но не очень-то интересовался этим подходом ... если это единственный способ, я все же приму его. Спасибо за ссылку на этот пост, он действительно информативный, отличное прочтение!