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 стало заметно приятнее, и это хорошо.
- Prometheus 2.0: новый TSDB и план миграции с 1.x без боли · 2 февраля 2018
- Ansible 2.4: network automation для Cisco IOS - переходим на ios_config без expect · 6 февраля 2018