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

Terraform 0.12 GA: мигрировали треть модулей - конфиги стали читаться как код

Terraform 0.12 вышел в мае с HCL2, for-выражениями и нормальной типизацией. Рассказываем как прошла миграция модулей и что из этого вышло на практике.

Контекст момента

Terraform 0.12 GA (май 2019): новый синтаксис HCL2, первоклассные выражения, улучшенная типизация переменных

В мае HashiCorp выпустили Terraform 0.12 GA. Без «alpha», без «beta», без оговорок - финальный релиз с новым HCL2, первоклассными выражениями и переработанной системой типов. В январе мы гоняли alpha на тестовом модуле и пришли к выводу, что миграция - это работа, которую надо планировать отдельно. С тех пор прошло полгода, и мы эту работу сделали. Не всю, но значительную часть.

Масштаб оказался больше, чем казалось

В январе у нас было «несколько десятков модулей разного размера». К маю инвентаризация дала точную картину: тридцать с лишним модулей, часть которых живёт активно, часть - в состоянии «работает, не трогать». Плюс конфигурации конкретных инфраструктур под каждый клиентский проект, которые эти модули используют.

terraform 0.12upgrade - инструмент от HashiCorp для автоматической конвертации - закрывает примерно то что и обещал: убирает лишние кавычки вокруг чисел, переписывает простые интерполяции, чинит type = "list" на type = list(string). Но там где логика строилась на count-трюках или null_resource с условиями - инструмент либо конвертирует формально неверно, либо оставляет как есть с предупреждением. Такие места пришлось проходить руками.

Итого: около трети модулей потребовали ручной работы сверх того что сделал upgrade-инструмент. Не катастрофа, но и не «запустил скрипт и готово».

Что реально изменилось к лучшему

Это не преувеличение: конфиги после миграции стали другими. Не просто синтаксически - по смыслу.

For-выражения вместо count-костылей. Раньше чтобы получить список имён из переменной, преобразованный в верхний регистр, либо делалось снаружи Terraform (в пайплайне), либо городился null_resource с count и element(). Теперь это [for name in var.names : upper(name)] прямо в locals. Читается сразу, без расшифровки.

for_each вместо count для ресурсов. Разница принципиальная - мы её видели ещё в alpha и она не разочаровала. При count удаление элемента из середины списка пересоздаёт все последующие ресурсы из-за сдвига индексов. С for_each по map - удаляется только нужный. Для IAM-ролей, DNS-записей, правил security group это меняет операционную картину.

Нормальная типизация переменных. type = object({name = string, port = number}) вместо type = "map" с комментарием «тут строка, тут число, тут список». Переменные стали самодокументированными. terraform validate теперь ловит несоответствие типов до plan, а не в момент apply когда уже потрачено время.

Динамические блоки. Несколько модулей с security group rules избавились от copy-paste блоков ingress {} через dynamic "ingress" { for_each = var.rules ... }. Было семь одинаковых блоков с разными портами - стал один динамический с переменной. Очевидно, но раньше язык просто не позволял.

Где было тяжелее всего

Самые трудоёмкие места - это условные ресурсы через count = 0/1 с нетривиальной логикой. В HCL1 был популярный паттерн: count = "${var.enable_thing ? 1 : 0}", и дальше к этому ресурсу обращались через element(module.thing.output_list, 0) потому что при count = 0 вывод - пустой список, а не null. В HCL2 появился null как значение и тернарник без кавычек, но семантика поменялась тонко: count = 0 теперь означает что ресурса нет, а его output просто не существует. Места где на это опирались через element(..., 0) нужно переписывать осмысленно, а не механически.

Ещё один момент: template_file data source формально работает, но несколько мест мы переписали на встроенный templatefile() - он чище и не требует data-блока только ради генерации строки.

Стратегия миграции

Мигрировали по принципу «сначала листовые модули, потом корневые конфигурации». Листовой модуль не зависит от других наших модулей - можно конвертировать и проверять изолированно. Корневая конфигурация использует несколько модулей и мигрировала последней, когда все зависимости уже на 0.12.

Параллельно держали два remote state backend: часть инфраструктур работала на 0.11, часть - на 0.12. State-файлы между версиями несовместимы напрямую, но если мигрировать конфигурацию без изменения ресурсов, Terraform 0.12 подхватывает существующий state нормально. Ни одного вынужденного terraform import не понадобилось.

На managed-проектах клиентский код трогали только с явным согласованием: объясняли что за миграция, что план до и после должен показать zero diff. Все прошло без инцидентов, хотя на паре инфраструктур план показал diff в тегах - оказалось, что 0.12 иначе вычисляет некоторые строковые интерполяции в значениях тегов. Поправили.

Что не сделали

Новые возможности HCL2 - object() типы переменных, полноценные условные выражения - мы добавляем только в новые модули или при переписывании старых. Механически заменять всё ради «стиля» смысла нет: работающий код лучше тронутого без причины.

Registry-версионирование модулей настроили ещё в прошлом году, и это сильно помогло: ограничение version = "~> 2.0" в source позволило держать 0.11-совместимые версии модулей для ещё не мигрированных инфраструктур, пока параллельно публиковали 0.12-совместимые под новой мажорной версией.

Итог

Миграция прошла без боли - медленнее чем хотелось бы, но предсказуемо. Главное что изменилось: читать конфиги теперь приятно. Звучит субъективно, но на практике это означает что новый человек в команде разбирается в чужом модуле за минуты, а не за час с матом. Условия - это условия, циклы - это циклы, типы - это типы. Terraform перестал быть языком угадывания.

Контакт

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

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