ADG Оставить заявку
Блог DevOps 5 мин чтения

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-реестр - и в целом оно того стоило.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.