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

Terraform 0.11: count в модулях и улучшенный plan output в продакшне

Переводим инфраструктуру клиента на Terraform 0.11: разбираем count в модулях, новый lifecycle, что изменилось в plan output и где ещё трётся.

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

Terraform 0.11 - улучшения count, lifecycle и providers для enterprise-инфраструктур

Terraform 0.11 вышел в ноябре, и мы наконец добрались до его нормального изучения на живом клиентском стеке. Задача была конкретная: перевести инфраструктурный код под управлением нашей managed-службы с 0.10 на 0.11, попутно разобравшись что именно изменилось и стоит ли оно того. Стоит - хотя и не без шероховатостей.

Почему 0.11, и почему сейчас

На 0.10 у нас накопились два устойчивых раздражителя. Первый - нестабильный count. Если значение count зависело от атрибута другого ресурса или data source, Terraform вёл себя непредсказуемо при plan - мог упасть или выдать бессмысленный diff. count в блоке module {} при этом вообще не поддерживался, и люди обходили это дублированием блоков, что некрасиво и неудобно сопровождать.

Второй раздражитель - план. С ростом инфраструктурного кода вывод terraform plan превращался в многостраничный поток сознания, в котором реальные изменения терялись среди <computed> и прочего шума. Делать ревью PR с инфраструктурными изменениями становилось неприятным занятием.

0.11 адресует оба.

count в ресурсах: стало надёжнее

Изменение, которое нас интересовало - более предсказуемое поведение count при вычисляемых значениях и зависимостях между ресурсами. В 0.10 count, зависящий от атрибутов другого ресурса, нередко приводил к панике или некорректному diff при plan. В 0.11 это стало стабильнее.

Пример использования count для распределения инстансов по availability zones:

resource "aws_instance" "app_server" {
  count = "${var.app_server_count}"

  ami           = "${var.ami_id}"
  instance_type = "${var.instance_type}"
  subnet_id     = "${var.subnet_ids[count.index % length(var.subnet_ids)]}"

  tags {
    Name = "app-${count.index}"
  }
}

count в блоке module {} по-прежнему не поддерживается - это известное ограничение Terraform, и 0.11 его не снял. Модуль приходится вызывать отдельным блоком на каждый инстанс или городить конструкции с null_resource. Некрасиво, но так работает.

На реальном кейсе: у клиента три группы серверов - app, worker, monitoring. Для каждой группы отдельный resource-блок с count. После перехода на 0.11 стало стабильнее при пересчёте количества инстансов: план не валится там где раньше было непредсказуемое поведение.

Улучшенный plan output

Вот это изменение скромнее с точки зрения кода, но ощутимее в ежедневной работе. В 0.11 вывод terraform plan стал более структурированным:

  • ресурсы с реальными изменениями выводятся чётче, с явным ~ для обновлений и -/+ для пересоздания
  • поля с неизвестным значением отображаются как <computed> - чище чем раньше, без лишнего шума рядом с реальными изменениями
  • итоговая строка в конце плана (Plan: X to add, Y to change, Z to destroy) стала появляться стабильнее и точнее

Для нас это важно потому что ревью инфраструктурных PR - обязательный шаг перед apply. Инженер, который не трогал этот код, смотрит на diff плана и должен понять что произойдёт. С улучшенным выводом это стало делать проще: реальные изменения заметны быстрее.

lifecycle: что изменилось

В 0.11 lifecycle блок получил небольшие уточнения. Конкретно нас интересовало поведение create_before_destroy при работе с зависимостями между ресурсами.

Раньше при использовании create_before_destroy на ресурсе, от которого зависят другие, Terraform иногда путался с порядком операций при замене всего поддерева. В 0.11 это стало надёжнее - план корректно показывает порядок операций и не пытается удалить зависимый ресурс до создания нового экземпляра родительского.

На практике это важно для нас вот в каком сценарии: TLS-сертификаты, security groups, IAM roles - всё что пересоздаётся при изменении некоторых полей. Цепочка certificate -> load balancer listener -> target group с create_before_destroy на сертификате раньше иногда приводила к неожиданному порядку в плане. Сейчас - работает как ожидается.

Провайдеры: явная передача в модули

Ещё одно изменение, которое мы заметили - провайдеры теперь нужно явно передавать в модули через блок providers:

module "replica" {
  source = "./modules/rds-replica"

  providers = {
    aws = "aws.eu-west-1"
  }
}

Это ломающее изменение для тех кто использует несколько провайдеров в одном стеке (мультирегиональные конфигурации, например). В 0.10 провайдер наследовался неявно, в 0.11 нужно прописывать явно. Миграция не сложная, но требует пройтись по всем вызовам модулей и проверить где используется non-default провайдер.

У нашего клиента именно такой кейс: основной регион и DR в другом. Потратили час на перечитывание модульных вызовов и добавление явных providers. Зато теперь код честно документирует куда что деплоится - неявного наследования нет.

Что ещё трётся

Честный список того, что не идеально:

  • State-файлы. 0.11 меняет формат state при первом apply. Откатиться на 0.10 после этого - приключение. Мы делаем снапшот state перед миграцией и держим его неделю.
  • Interpolation в count. Если значение count зависит от data source или вычисляется из ресурса - Terraform при plan может потребовать сначала запустить apply чтобы узнать count. Это ограничение не убрали в 0.11, нужно учитывать.
  • terraform validate стал строже. Несколько наших модулей не проходили validate - в основном из-за переменных без типов (в 0.11 рекомендуется явно указывать type = "string" / "list" / "map"). Не критично, но на исправление ушло время.

Где стоим

Один клиентский стек уже на 0.11 в продакшне - неделя без сюрпризов. Ещё два в процессе переноса. Основная работа - проверка модульных вызовов на провайдеры и прогон validate по всему коду.

Улучшенный plan output и стабильный count в ресурсах - это не революция, но именно те качества жизни которых не хватало при сопровождении живых стеков. Ревью инфраструктурных PR стало заметно приятнее, и это хорошо.

Контакт

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

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