Terraform 0.13: частный реестр модулей и конец copy-paste между проектами
HashiCorp анонсирует Terraform 0.13 с count/for_each для модулей и поддержкой частных реестров. Рассказываем как внутренний registry стандартизировал IaC между командами.
Terraform 0.13 анонсирует поддержку count/for_each для модулей и частный реестр модулей с версионированием через required_providers
HashiCorp на прошлой неделе подробнее раскрыла что будет в Terraform 0.13: помимо count/for_each для вызовов модулей - а это мы ждём с декабря, когда разбирали превью - появляется ещё кое-что интересное. Поддержка сторонних реестров модулей через блок required_providers с указанием источника. То есть не только публичный registry.terraform.io, но и свой, внутренний.
Мы как раз несколько месяцев назад пошли именно в эту сторону, не дожидаясь официальной поддержки. Расскажем, как оно выглядит на практике.
Откуда взялась проблема
У нас три проекта с похожей сетевой топологией - VLAN-сегментация, одинаковые подходы к именованию, один провайдер. Когда конфигурировали первый, написали модуль vlan-segment прямо в репозитории проекта: создаёт VLAN, назначает тегирование на портах, прописывает DHCP-скоупы. Стандартный набор для нашей типовой инфры.
Когда второй проект потребовал то же самое - скопировали папку. Когда третий - скопировали снова. Три копии одного и того же кода, которые начали расходиться: в одном проекте подправили логику именования, в другом добавили переменную для MTU, в третьем забыли и то и другое. Классическая история с copy-paste.
Обнаружили рассинхрон случайно: делали аудит перед переездом state на Terraform Cloud и заметили, что модули в трёх репозиториях уже не идентичны. Разобрались, какая версия «правильная», и задумались как не допустить повторения.
Внутренний реестр: что это вообще такое
Terraform Module Registry - это HTTP API с определённым контрактом. HashiCorp описала спецификацию, и реализаций несколько: можно поднять самостоятельно через open-source решения, а GitLab в свежих версиях двигается в сторону встроенного реестра модулей. У нас GitLab, поэтому пошли туда.
Принцип простой: модуль живёт в отдельном репозитории, тегируется по semver (v1.0.0, v1.1.0 и так далее), реестр публикует его по стандартному адресу. В Terraform-конфиге указываешь источник:
module "segment_prod" {
source = "gitlab.internal.company/infra/vlan-segment/adg"
version = "~> 1.0"
name = "prod-backend"
vlan_id = 101
mtu = 9000
}
~> 1.0 - pessimistic constraint, берёт любую 1.x, но не 2.0. Это принципиально: мы можем выкатить патч или минорное обновление в модуле, и все три проекта подтянут его при следующем terraform init. Ломающее изменение - major bump - требует явного обновления каждого потребителя.
Как выглядит сейчас
Репозиторий terraform-module-vlan-segment у нас в GitLab, там один модуль, CHANGELOG.md и тесты через Terratest - ну, последнее мы пока только начали, там пара базовых проверок что ресурсы создаются. Модуль на v1.2.0.
Три проектных репозитория в required_providers / source указывают на реестр и на конкретный constraint. CI/CD при terraform plan скачивает нужную версию из реестра через terraform init. Никакого copy-paste, никакого «а в каком репозитории правильная версия?».
Что реально поменялось:
- Единственный источник правды для логики сегментирования. Починили баг - тегируем релиз, все проекты получают при следующем init.
- История изменений видна в одном месте, а не размазана по трём репозиториям.
- Версионирование честное: если ломаем обратную совместимость, major-версия сигнализирует об этом, и мы идём обновлять потребителей осознанно.
При этом есть нюансы, которые стоит знать до того как начинать. terraform init скачивает модули каждый раз - в CI это нужно кэшировать или использовать зеркало, иначе каждый план ходит в реестр. У нас в GitLab CI прописан кэш директории .terraform, решили вопрос. Ещё - авторизация: реестр GitLab требует токен, передаётся через переменную TERRAFORM_CLI_ARGS или через .terraformrc. Держим в секретах CI, не в коде.
Что нам даёт 0.13
count/for_each для модулей - это отдельная история, которую мы ждали ещё с декабря. Сейчас, чтобы создать три одинаковых сегмента с разными параметрами, приходится писать три отдельных блока module {}. После 0.13 это станет:
module "segments" {
for_each = var.vlan_segments
source = "gitlab.internal.company/infra/vlan-segment/adg"
version = "~> 1.0"
name = each.key
vlan_id = each.value.vlan_id
mtu = each.value.mtu
}
Одна декларация вместо трёх. Для конфигов с десятками VLAN это принципиально: у одного заказчика их больше двадцати, Terraform-файл выглядит как поэма.
Поддержка частных реестров через required_providers в 0.13 тоже станет более явной - сейчас мы используем source внутри блока module, что работает начиная с 0.12, но блок required_providers с явным указанием hostname реестра и его версионирования - это то, что добавляет 0.13. Разница в том, что hostname реестра явно объявлен - это шаг к более строгой воспроизводимости, хотя верификация хэшей через lock-файл придёт в следующих версиях.
Статус
Реестр работает, три проекта на нём живут. v1.2.0 модуля vlan-segment в продакшне второй месяц, замечаний не было. На 0.13 смотрим с интересом - GA ещё не вышел, в беты по привычке не лезем на рабочих конфигурациях. Но for_each для модулей опробовали на превью и видим что работает как задумано.
Инфраструктурный код в managed-сопровождении в итоге управляется так же, как и прикладной: модуль в отдельном репозитории, версии через semver, потребители пинят constraint. То, что Terraform в enterprise стараются делать правильно, нам пришлось организовать самостоятельно через GitLab-реестр - и в целом оно того стоило.