Terraform 0.11 + Module Registry: три клиента, меньше кода, план как документ
Перевели инфраструктуру трёх клиентов на Terraform 0.11 с remote state в Consul. Модули из реестра сократили повторяющийся код втрое, план читается как спецификация.
Terraform 0.11 с Terraform Module Registry достиг зрелости для enterprise-использования
Terraform 0.11 вышел ещё в ноябре 2017-го, и мы за ним наблюдали аккуратно, не торопясь. После перехода на 0.9 с нормальными бекендами жизнь стала проще, но лишнего кода в репозиториях меньше не стало: каждый клиентский проект нёс свои копии одних и тех же ресурсов - VPC, security groups, базовые IAM-политики. С Module Registry в 0.11 это наконец получилось решить по-человечески, а не через git submodules или ручное копирование.
В июне-июле перевели три managed-проекта на Terraform 0.11 с remote state в Consul. Рассказываем что из этого вышло.
Что появилось в 0.11, что нас интересовало
Версии 0.10 и 0.11 добавили несколько вещей, которых раньше не хватало:
- Terraform Module Registry - официальный реестр модулей на registry.terraform.io. Модуль подключается одной строкой с версионированием, Terraform сам скачивает нужную версию при
terraform init. Никакого копирования файлов руками. required_providersи версионирование провайдеров - можно зафиксировать версию провайдера прямо в конфиге,terraform initскачает именно её. Раньше это решалось внешними инструментами или коммитом папки.terraform/plugins/в репозиторий.localsблок - промежуточные вычисляемые значения без лишних переменных. Кажется мелочью, но когда пишешьlocals { name_prefix = "${var.env}-${var.project}" }и потом везде ссылаешься наlocal.name_prefix- читаемость конфига сразу вырастает.- Расширенный синтаксис условий - тернарный оператор с нормальной обработкой типов. До этого некоторые условные конструкции писались через
count = "${var.condition == "true" ? 1 : 0}"и выглядели как головоломка.
Module Registry - главное из этого списка по практическому эффекту.
Как выглядело до и как стало
До перехода типичный main.tf для клиентского проекта начинался примерно так: 150 строк описания VPC с подсетями, потом security groups, потом IAM. Примерно одинаково везде, с небольшими вариациями. Когда нужно было поменять что-то структурное - правили в трёх местах, и один раз ошиблись: в одном проекте забыли добавить правило в security group и обнаружили это через неделю на ревью.
С модулями из реестра те же ресурсы стали выглядеть вот так:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "1.46.0"
name = local.name_prefix
cidr = var.vpc_cidr
azs = var.availability_zones
private_subnets = var.private_subnets
public_subnets = var.public_subnets
enable_nat_gateway = true
single_nat_gateway = var.env != "prod"
tags = local.common_tags
}
Двадцать строк вместо ста пятидесяти. И самое важное: логика в модуле одна, протестированная, с понятным интерфейсом через переменные. Вариантов сделать по-разному в разных проектах меньше.
По трём проектам итог похожий: объём HCL-кода сократился примерно втрое. Частично это за счёт модулей, частично за счёт locals, которые убрали цепочки интерполяций.
Remote state в Consul: почему не S3
Раньше мы использовали S3 + DynamoDB для remote state. Для AWS-проектов это органично. Но у нас часть клиентских проектов живёт на собственном железе или в другом облаке, а Consul у нас уже стоит как часть инфраструктуры - service discovery, конфиги, и теперь ещё Terraform state.
Consul backend в 0.11 работает стабильно. Locking через Consul sessions - не чета DynamoDB по изяществу, но работает: если terraform apply упал и не отпустил сессию, через TTL она освобождается сама. Мы один раз проверили это намеренно: убили процесс в середине apply, подождали, попробовали запустить снова - через пару минут lock отпустился штатно.
Структура в Consul выбрали такую: terraform/<клиент>/<окружение>/state - каждое окружение отдельный путь, читать можно через data source в другом проекте. Это удобно когда одна часть инфраструктуры собирается из нескольких репозиториев.
План как документ
Это субъективное наблюдение, но оно важное. Когда terraform plan выдаёт изменения в модульной инфраструктуре, вывод читается иначе: видно не 40 строк ресурсов, а несколько модулей с понятными именами. Клиентам, которые хотят понимать что происходит с их инфраструктурой, можно показывать план напрямую - без перевода с terraform-птичьего.
Пример строки из plan до: aws_subnet.private.0: Modifying... (ID: subnet-xxxxxxxx). Пример после: module.vpc.aws_subnet.private.0: Modifying... (ID: subnet-xxxxxxxx) - казалось бы похоже, но когда таких строк сорок и ты видишь module.vpc, module.rds, module.ecs_cluster как отдельные секции, картина понятнее.
Что пока напрягает
Версионирование модулей из реестра требует дисциплины. Модуль обновился - нужно осознанно поднять версию, проверить что изменилось, протестировать. Это правильно, но это работа. Если просто написать source = "terraform-aws-modules/vpc/aws" без version - Terraform возьмёт последнюю, и однажды terraform init на CI принесёт неожиданные изменения.
Мы заранее договорились: версия модуля фиксируется, обновляется только через явный PR с проверкой plan. Пока это работает, но процесс надо поддерживать.
Ещё один момент - частные модули. Реестр HashiCorp отличный для общих конструкций, но клиентоспецифичные модули мы кладём в отдельный приватный GitLab и подключаем через git-источник. Это работает, но версионирование через git-теги чуть менее удобно чем через реестр.
Итог
Три проекта на Terraform 0.11 с Consul backend - работает. Модульная структура реально сократила количество кода и, что важнее, количество мест где можно ошибиться. Plan стал читаемее для всех участников, не только для тех кто писал конфиг.
Следующий шаг - смотрим на Terraform Enterprise с приватным реестром модулей для клиентских проектов. Пока держим модули в GitLab, но если число проектов вырастет - централизованный реестр с версионированием и доступами будет удобнее.