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

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-совместимый). Перенос не сложный, но надо делать аккуратно.

Шаги, которые мы прошли:

  1. Экспорт текущего state. terraform state pull > prod.tfstate. Файл содержит полный снэпшот, с ним можно работать офлайн.
  2. Переключение backend. Правим backend.tf, меняем s3 на http с новым адресом. Коммитим.
  3. terraform init. Terraform спрашивает подтверждение на миграцию, загружает state с S3 и пушит в GitLab. Если что-то пошло не так - файл prod.tfstate остался, можно откатиться.
  4. Проверка. 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 в таких ситуациях.

Контакт

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

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