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

Terraform 0.12 alpha: протестировали HCL2 на реальном модуле - старые конфиги ломаются, новый синтаксис стоит усилий

Прогнали Terraform 0.12 alpha на тестовом модуле: HCL2 ломает старые конфиги без предупреждения, но for-выражения и нормальные типы оправдывают инвентаризацию legacy-кода.

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

Terraform 0.12 alpha/preview - переход на HCL2 с новым синтаксисом, типовой системой и выражениями for/for_each

HashiCorp в конце декабря выложили альфу Terraform 0.12. Не beta, не RC - именно alpha, с честным предупреждением «не используйте в продакшне». Мы не использовали, но один тестовый модуль через неё прогнали - хотелось понять реальный масштаб работы, которая нас ждёт. В ноябре 2017-го мы уже писали про preview HCL2, тогда это было только документацией и обещаниями. Теперь есть что пощупать руками.

Вывод короткий: сломается всё. Но кое-что из нового синтаксиса реально хорошее.

Как тестировали

Взяли один из наших внутренних модулей - примерно 300 строк HCL, описывает типовую конфигурацию с несколькими ресурсами, переменными и locals. Модуль использовался на нескольких managed-проектах, написан ещё под 0.11, живёт в GitLab. Подняли отдельный контейнер с terraform 0.12 alpha binary, изолированно от рабочих окружений. Запустили terraform init и terraform validate - посмотрели что посыплется.

Посыпалось почти сразу.

Что конкретно ломается

Интерполяции ${...} внутри строк. В HCL2 они не запрещены полностью, но их поведение поменялось. Конструкция "${var.project}-${var.env}" теперь должна быть просто "${var.project}-${var.env}" - синтаксически похоже, но вот count = "${length(var.list)}" в числовом контексте уже не работает. Число должно быть числом: count = length(var.list). Кавычки убираются, и это ломает каждую вторую строку в типичном модуле.

Типы переменных. type = "list" и type = "map" - в мусор. Теперь это type = list(string), type = map(string), type = object({...}). Типы стали настоящими типами с параметрами, а не строковыми константами. Это правильно, но найти и исправить надо в каждом variable-блоке.

template_file data source. Наш модуль местами использовал data "template_file" для генерации строк из шаблонов. В 0.12 это не отвалилось, но рядом с ним в выводе validate появилось несколько предупреждений о том, что шаблоны теперь лучше писать через встроенные функции templatefile() и format(). Не fatal, но зафиксировали.

null_resource с count-трюками. У нас был один условный ресурс через count = "${var.enable_feature ? 1 : 0}". В 0.12 тернарный оператор работает, но уже без кавычек и без интерполяционного синтаксиса: count = var.enable_feature ? 1 : 0. Мелочь, но в модуле таких мест несколько.

Итого за один terraform validate - примерно два десятка ошибок на 300 строк. Не катастрофа, но это один небольшой модуль. У нас в работе несколько десятков модулей разного размера.

Что реально хорошее

Ради чего вообще это всё: новые возможности языка, которых в HCL1 не было и которые обходились через костыли с count и null_resource.

For-выражения. В 0.12 можно написать:

locals {
  upper_names = [for name in var.names : upper(name)]
  name_map    = {for s in var.names : s => upper(s)}
}

До этого примерно то же самое делалось через null_resource с count и element(), или не делалось вообще - данные преобразовывались снаружи Terraform. Теперь это часть языка.

for_each для ресурсов. Вместо count с индексами - нормальный итератор по map или set:

resource "aws_iam_user" "users" {
  for_each = toset(var.user_names)
  name     = each.value
}

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

Нормальная типовая система. object({...}) с явными полями - переменная теперь самодокументирована. Раньше переменная типа map могла нести что угодно, и только комментарий объяснял что именно. Теперь это описывается в типе.

Динамические блоки. Вложенные блоки ресурса можно генерировать динамически:

dynamic "ingress" {
  for_each = var.ingress_rules
  content {
    from_port   = ingress.value.from_port
    to_port     = ingress.value.to_port
    protocol    = ingress.value.protocol
    cidr_blocks = ingress.value.cidr_blocks
  }
}

Security group rules через переменную без copy-paste блоков - это то, чего нам не хватало.

Что с обратной совместимостью

Никакой. 0.12 alpha чётко говорит: старые конфиги не работают, нужна миграция. HashiCorp обещают написать команду terraform 0.12upgrade по аналогии с тем, что было для 0.11 - она должна автоматически переписать большую часть синтаксиса. Для нашего тестового модуля мы прогнали её вручную: примерно 60-70% ошибок она закрывает сама, остальное - ручная работа, особенно там где типы переменных были сложными или где логика строилась на count-трюках.

Авто-конвертер реально помогает, но слепо доверять ему нельзя - после прогона нужен внимательный review, потому что семантика некоторых конструкций меняется, а не только синтаксис.

Что делаем сейчас

После этого теста стало понятно: миграция на 0.12 - это не обновление бинарника. Это работа, которую надо планировать отдельно. Начали инвентаризацию: сколько у нас модулей, какого размера, где самые запутанные count-конструкции, где template_file и другие вещи которые придётся переписывать.

После перехода на Module Registry у нас появилась централизация - это помогает: обновить модуль в одном месте и прогнать миграцию там, а не ловить копии по десяти репозиториям. Но количество модулей от этого не уменьшилось.

Пока план такой: держать 0.11 на продакшне, не торопиться. Следить за тем как alpha перейдёт в beta и RC. Параллельно - инвентаризация и аудит того, что у нас есть. Когда выйдет GA и финальный upgrade-инструмент, не хочется выяснять масштаб работы в последний момент.

0.12 alpha - не для production, и мы именно так к ней и отнеслись. Но посмотреть стоило: теперь понятно за что терпеть миграцию.

Контакт

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

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