Terraform 0.8: модули, S3-бекенд и первый HCL-репозиторий в продакшене
Terraform 0.8 привёл синтаксис модулей в норму и добавил S3/Consul-бекенды для state. Начинаем кодировать инфраструктуру в HCL: структура репо и первые грабли.
Terraform 0.8 (декабрь 2016) - переработанный синтаксис модулей, remote state backends (S3, Consul, GCS), улучшенная интерполяция
Terraform 0.8 вышел в декабре, и мы дотянули руки до него только в январе. На одном из объектов накопилось достаточно ручных изменений в AWS, чтобы боль от «это никто не помнит зачем» стала сильнее страха «а вдруг сломается». Вот с этим и решили разобраться.
Что изменилось в 0.8
Главное для нас - не фичи как таковые, а то, что несколько вещей перестали быть экспериментальными или странными.
Remote state backends - в 0.8 это полноценный блок terraform {} в конфигурации, а не флаг в командной строке. Для AWS логичный выбор - S3 с шифрованием. До 0.8 remote state тоже существовал, но инициализация через terraform remote config с кучей флагов выглядела как временный хак. Теперь конфиг живёт в коде:
terraform {
backend "s3" {
bucket = "company-tfstate"
key = "prod/network/terraform.tfstate"
region = "eu-west-1"
encrypt = true
}
}
Модули - синтаксис подтянули. До 0.8 передача переменных в модуль и получение outputs из него работали, но требовали знания ряда неочевидных деталей. В 0.8 это стало предсказуемым: module.name.output_name работает так как написано.
Интерполяция - добавили функции типа lookup(), element(), format(). Мелочь, но раньше приходилось городить несколько ресурсов там, где теперь хватает одной строки.
Структура репозитория
Потратили время на обсуждение структуры до того как начать писать. Наученные горьким опытом с Ansible, где «просто положу playbook в корень» приводило к репозиторию-помойке через полгода.
Остановились на такой схеме:
infra/
terraform/
modules/
vpc/
security-groups/
ec2-base/
rds/
environments/
prod/
network/
main.tf
variables.tf
outputs.tf
compute/
main.tf
database/
main.tf
staging/
...
shared/
backend.tf.example
Логика простая: modules/ - переиспользуемые блоки без привязки к окружению, environments/ - конкретные инстансы этих блоков с параметрами. State разбит по компонентам, а не один файл на всё окружение. Это замедляет terraform plan на пустом месте, зато terraform apply в compute/ не может случайно тронуть базу данных.
Первые грабли
terraform import - это не волшебная палочка. Мы рассчитывали импортировать существующие ресурсы одной командой и получить готовый HCL. Не так. terraform import добавляет ресурс в state, но не генерирует .tf-файл. Нужно написать описание ресурса вручную, потом запустить import, потом plan и убедиться что diff пустой. На несколько десятков ресурсов это несколько дней работы, а не несколько часов.
Ручные изменения ломают картину. Вот импортировали VPC-группы безопасности, написали правила в HCL, всё сошлось. Через неделю кто-то добавил правило в консоли - и plan снова показывает diff. Договорились: консоль только для чтения, изменения только через код. Договорённость держится пока, но это дисциплина, а не техническое ограничение.
Зависимости между state-файлами. Разбили state по компонентам - хорошо. Но compute/ нужен VPC ID из network/. Решается через terraform_remote_state data source, который читает outputs другого state. Работает, но добавляет неочевидную зависимость: если network-state недоступен, compute-план не запустится. Это нужно документировать явно, иначе через месяц непонятно почему plan падает с «can't read remote state».
Версионирование provider-а. В 0.8 появился required_version, но многие примеры в интернете ещё без него. Без пина версии провайдера terraform init может подтянуть свежую версию с breaking changes. Пинируем всё:
terraform {
required_version = ">= 0.8.0"
}
Для провайдеров явный пин в 0.8 ещё не такой удобный как хотелось бы - это скорее конвенция чем синтаксис. Но хотя бы terraform version и зафиксированный результат в README.
Где сейчас
Импортировали около половины ресурсов одного объекта. Сеть, группы безопасности, базовые EC2-инстансы - в коде. RDS и Load Balancer - в очереди, там сложнее из-за количества параметров которые нужно сверить с реальным состоянием.
Параллельно с сопровождением инфраструктуры начинаем выстраивать пайплайн: plan в CI при пул-реквесте, apply вручную после ревью. GitLab CI для этого настроен, но пока без apply - просто чтобы видеть diff в MR. Про apply автоматически разговор отдельный: это требует доверия к тестам и рецензии, которых пока нет.
Как мы писали про Git и инфраструктурный код - история изменений ценна сама по себе. Даже если Terraform не будет применяться автоматически, уже то, что изменение в AWS теперь сначала появляется как коммит - это другой уровень контроля.
Terraform 0.8 достаточно стабилен чтобы начать. Не идеален - грабли реальные, импорт трудоёмкий. Но альтернатива в виде «посмотрим в консоли что там есть» хуже.