Terraform 0.13 preview: for_each для модулей - то, чего мы ждали с 0.12
HashiCorp работает над Terraform 0.13 с поддержкой for_each для модулей. Разбираем почему это решает нашу конкретную боль с дублированием конфигов клиентских окружений.
HashiCorp ведёт активную разработку Terraform 0.13 с поддержкой for_each и count для вызовов модулей - фича, которой не хватало в 0.12
HashiCorp открыто работает над Terraform 0.13, и в публичных issue на GitHub видно что одна из ключевых фич - это for_each и count для вызовов модулей. Не для ресурсов внутри модуля - для самих вызовов module {}. Мы следим за этим уже несколько месяцев, потому что проблема, которую это решает, у нас есть прямо сейчас.
Конкретная боль
После миграции на Terraform 0.12 мы получили хорошую типизацию, for-выражения и for_each для ресурсов. Это сильно улучшило читаемость конфигов. Но осталась одна область, где Terraform по-прежнему вынуждает писать вручную: когда нужно развернуть один и тот же модуль для нескольких похожих, но не одинаковых окружений.
У нас есть несколько клиентских проектов с похожей инфраструктурой - назовём их условно группа «региональные серверы». Каждый клиент получает набор одинаковых компонентов: группу виртуальных машин, балансировщик, настройки сети, мониторинг. Логика идентичная, параметры немного разные. Текущий способ это выразить выглядит так:
module "client_a" {
source = "../modules/regional-server"
name = "client-a"
region = "msk"
server_size = "medium"
}
module "client_b" {
source = "../modules/regional-server"
name = "client-b"
region = "spb"
server_size = "small"
}
module "client_c" {
source = "../modules/regional-server"
name = "client-c"
region = "msk"
server_size = "large"
}
Три клиента - три почти одинаковых блока. Восемь клиентов - восемь блоков. Добавился новый параметр в модуль - идёшь и правишь все восемь.
Это не катастрофа, но это не то что можно назвать DRY. И главное - это source of truth проблемы: нет единого места где объявлен «список клиентов», есть восемь разбросанных блоков.
Что обещает 0.13
Судя по обсуждениям в issue tracker и preview-коду, синтаксис планируется такой:
locals {
clients = {
client_a = { region = "msk", server_size = "medium" }
client_b = { region = "spb", server_size = "small" }
client_c = { region = "msk", server_size = "large" }
}
}
module "regional_server" {
source = "../modules/regional-server"
for_each = local.clients
name = each.key
region = each.value.region
server_size = each.value.server_size
}
Один блок module. Один источник правды - local.clients. Добавить клиента - добавить строку в map. Добавить параметр в модуль - добавить его в один блок.
Семантика for_each для модулей наследует ту же логику что и для ресурсов: если убрать client_b из map - Terraform удалит только его инстанцию, без пересоздания остальных. Это то что count не умеет нормально делать из-за индексов.
Почему не обходимся count сейчас
Технически count на модулях не поддерживается в Terraform 0.12. Точка. Можно попробовать count внутри модуля, но это переносит проблему внутрь модуля и ломает его интерфейс: модуль начинает принимать список вместо одиночных значений, outputs становятся списками, работать с ними снаружи - неудобно.
Другой популярный workaround - terragrunt с его generate блоками и иерархией директорий. Мы смотрели на terragrunt. Он решает проблему, но ценой дополнительного инструмента со своим слоем абстракции поверх Terraform. Для команды, которая уже знает Terraform, это дополнительный mental overhead. Хочется чтобы родной Terraform закрывал этот кейс - и 0.13 выглядит именно как этот ответ.
Что готовим сейчас
Дожидаться стабильного релиза и мигрировать всё разом - не наш план. Вместо этого сейчас мы:
- Приводим существующие модули к интерфейсу, который легко переедет на
for_each. Входные переменные типизированы,object()там где нужен набор параметров, никаких строк через запятую которые потомsplit()внутри модуля. - Переводим «список клиентов» в отдельные файлы переменных. Пусть пока это
terraform.tfvarsс дублированием в каждомmodule {}блоке - по крайней мере источник данных один, а копирование в блоки это механическая работа. - Не добавляем новые module-блоки для этой группы проектов. Новый клиент сейчас - повод подумать, не добавит ли он вручную больше боли чем стоит.
На managed-проектах это ощущается особенно: у нас несколько клиентов с похожими инфраструктурами, и каждый раз при изменении общего параметра приходится идти по всем блокам вручную. Цена ошибки небольшая - план покажет расхождение - но цена внимания накапливается.
Насколько это близко
По активности в репозитории HashiCorp и тому что фича уже есть в форматах обсуждений - это не «когда-нибудь», а ближайший мажорный релиз. Точной даты нет, stable релиза 0.13 нет. Браться за preview в продакшне мы не станем.
Но понять синтаксис заранее, подготовить модули так чтобы миграция была механической - это разумная подготовительная работа. Прошлая миграция на 0.12 показала: чем лучше модули написаны до миграции, тем меньше работы во время неё.