GitLab 13.3: встроенный Terraform State backend и что это значит для команды
GitLab 13.3 добавил нативный HTTP backend для Terraform state. Переносим state из S3 в GitLab: единый access control, history в MR и разбор lock-механизма для concurrent-запусков.
GitLab 13.3 выпустил встроенный Terraform HTTP state backend с locking через GitLab API и интеграцией в Merge Request
GitLab 13.3 вышел в конце августа, и среди нескольких неплохих нововведений есть одно, которое нас зацепило практически: встроенный backend для Terraform state прямо в GitLab. Не Terraform Cloud, не отдельный S3 бакет с DynamoDB для локов - а просто GitLab, который уже есть в инфраструктуре у большинства клиентов.
Мы держим Terraform state в S3-совместимом хранилище с таблицей для локов. Схема рабочая, но обрастает инфраструктурой: нужны bucket, IAM-пользователь с узкими правами, переменные в CI, таблица локов отдельно. У команды из двух-трёх человек это ещё терпимо. Когда проект разрастается и к инфре начинают прикасаться пятеро - access control к state становится отдельной задачей, которую надо синхронизировать с IAM.
GitLab backend закрывает это в одном месте.
Как устроен backend
Технически это стандартный Terraform HTTP backend. Terraform умеет хранить state на любом HTTP-эндпоинте, который реализует несложный REST-контракт: GET чтобы прочитать, POST чтобы записать, DELETE чтобы удалить, и отдельные эндпоинты для LOCK/UNLOCK. GitLab 13.3 реализовал именно этот протокол.
Конфигурация в backend.tf выглядит так:
terraform {
backend "http" {
address = "https://gitlab.company.com/api/v4/projects/42/terraform/state/production"
lock_address = "https://gitlab.company.com/api/v4/projects/42/terraform/state/production/lock"
unlock_address = "https://gitlab.company.com/api/v4/projects/42/terraform/state/production/lock"
lock_method = "POST"
unlock_method = "DELETE"
username = "gitlab-ci-token"
password = "${TF_HTTP_PASSWORD}"
retry_wait_min = 5
}
}
TF_HTTP_PASSWORD - это Personal Access Token или CI Job Token. В CI передаём через переменную, в локальном запуске - через .env или export. Токен с правами api достаточен для записи state.
Доступ к state наследует GitLab permissions проекта: developer может читать и писать, maintainer управляет настройками. Кто имеет доступ к проекту в GitLab - тот имеет доступ к state. Это и есть главный плюс: единая точка управления правами.
Миграция с S3
У нас был state в MinIO (S3-совместимый). Перенос не сложный, но надо делать аккуратно.
Шаги, которые мы прошли:
- Экспорт текущего state.
terraform state pull > prod.tfstate. Файл содержит полный снэпшот, с ним можно работать офлайн. - Переключение backend. Правим
backend.tf, меняемs3наhttpс новым адресом. Коммитим. terraform init. Terraform спрашивает подтверждение на миграцию, загружает state с S3 и пушит в GitLab. Если что-то пошло не так - файлprod.tfstateостался, можно откатиться.- Проверка.
terraform planдолжен показать «No changes» - state совпадает с реальной инфрой. Если показывает изменения, которых нет - что-то пошло не так при миграции, разбираемся.
Старый S3 бакет после успешной проверки не удаляем сразу. Оставили на неделю как страховку, потом убрали. IAM-пользователь, который имел доступ только к этому бакету, тоже удалили - одна сущность с управлением правами исчезла из картины.
Lock-механизм: что надо знать командам с concurrent-запусками
Это самое практически важное. Terraform lock - это механизм, который не даёт двум terraform apply работать одновременно с одним state. Без него два человека могут запустить apply параллельно, state будет перезаписан один поверх другого, и инфраструктура окажется в непредсказуемом состоянии.
GitLab реализует lock через API: при старте apply Terraform делает POST на lock_address, получает lock ID, и при записи state включает его в запрос. Если lock уже занят - Terraform возвращает ошибку:
Error: Error locking state: Error acquiring the state lock
Lock Info:
ID: <uuid>
Path: production
Operation: OperationTypeApply
Who: user@host
Version: 0.13.0
Created: 2020-09-05 10:23:41.123456789 +0000 UTC
Info:
Поле Who показывает кто держит лок. Если это CI job - там будет имя runner-а. Если человек запустил локально и упал с ошибкой до завершения - лок остался висеть. Тогда нужен terraform force-unlock <lock-id> - команда снимает лок вручную. Делать это надо убедившись, что apply действительно не выполняется: принудительный анлок во время реальной работы terraform - это именно тот сценарий, который лок и предотвращает.
В нашей CI-конфигурации при параллельных MR это выглядело так: две ветки, обе добавляют ресурс. Первый pipeline добрался до apply, взял лок. Второй в это же время тоже запустил apply и встал на retry_wait_min = 5 секунд в ожидании. GitLab backend держит лок до завершения первого apply, потом второй продолжает. Это ровно то поведение, которое нужно.
Главное не путать lock с правами: лок не запрещает запустить plan. terraform plan state не блокирует, он его только читает. Параллельные планы работают без конфликтов - конкурируют только apply.
История state в MR
GitLab 13.3 показывает изменения Terraform state прямо в Merge Request: вкладка «Terraform» появляется если pipeline запускал terraform show -json. Для этого нужно сгенерировать артефакт:
plan:
script:
- terraform plan -out=tfplan
- terraform show -json tfplan > plan.json
artifacts:
reports:
terraform: plan.json
После этого в MR видна сводка: сколько ресурсов добавляется, меняется, удаляется. Не полный diff, но достаточно чтобы ревьюер понял масштаб изменений не читая raw plan output. Для инфра-изменений, которые ревьюируют не только инженеры - это удобно.
Где сейчас
Один проект переведён на GitLab backend, работает несколько дней. Права к state автоматически совпадают с правами к проекту - это убрало отдельный IAM-пользователь и бакет из картины. Lock работает как ожидается. Plan output в MR видят все.
S3-схема была рабочей, но это ещё одна вещь, которую надо администрировать отдельно. В рамках managed-сопровождения наличие встроенного backend в GitLab упрощает стандартную конфигурацию: меньше внешних зависимостей, меньше мест где может разойтись access control.
Смотрим как это покажет себя на проектах с более активным CI - несколько apply в день, несколько разработчиков. Ожидаем что lock-механизм справится, но на старых S3-конфигурациях у нас уже был опыт с зависшими локами после сбоев runner-а, и там нужен был ручной force-unlock. Посмотрим, отличается ли поведение GitLab backend в таких ситуациях.