Представь картину. Понедельник, в команде три человека, у каждого свой terraform на ноуте. Петя катит apply из дома, Маша - из офиса, ты - из кофейни через мобильный интернет. У всех чуть разные версии Terraform, у кого-то старая копия state, у кого-то лишняя переменная в окружении. И вот в среду прод лежит, а в логах непонятно кто, когда и что катил. Знакомо? Это не выдуманный ужастик, это типичная боль команд, которые пришли к подходу "инфраструктура как код" (iac), но остановились на полпути.
Terraform решает половину проблемы: он описывает инфраструктуру декларативно, в коде. Но код, который катится руками с десяти разных машин, ничем не лучше кликанья мышкой в консоли. Воспроизводимости ноль, аудита ноль, а terraform state - общий ресурс, который эти десять рук рвут на части. Сегодня разберём, как из ручного apply сделать нормальный конвейер инфраструктурного кода: VCS как единый источник правды, plan на пулл-реквесте вместо code review на глаз, и apply строго из CI после merge. И главное - золотое правило, ради которого вся эта глава и затевалась.

Золотое правило Terraform
Сформулирую сразу, чтобы держал в голове весь урок:
Звучит банально, пока не начнёшь нарушать. А нарушают это правило двумя способами.Ветка master (или main) - это единственный и точный источник правды о том, что сейчас развёрнуто в проде. Никаких ручных изменений в облаке, никаких локальных apply в обход git. То, что в master, и есть твой прод. Точка.
Первый - кто-то зашёл в веб-консоль Yandex Cloud и руками поправил группу безопасности, "там же на минуточку". Теперь реальность разъехалась с кодом. Следующий terraform plan покажет дрейф (drift), а следующий apply откатит твою ручную правку назад, потому что в коде её нет. В лучшем случае ты удивишься. В худшем - снесёшь что-то важное в три часа ночи.
Второй способ - локальный apply из ветки, которая ещё не в master. Ты покатил, всё заработало, обрадовался и забыл сделать merge. Через неделю коллега катит master, в котором твоих изменений нет, и Terraform аккуратно их сносит. Потому что для него master - истина, а твои локальные подвиги он знать не знает.
Вывод простой: руки убираем от облака и от локального apply на проде. Меняем инфраструктуру только через git. Хочешь поправить - открой пулл-реквест.
Как устроен PR-flow для Terraform
Рабочий цикл выглядит так. Ты создаёшь ветку, правишь .tf-файлы, открываешь пулл-реквест (или merge request, смотря где живёшь). И вот тут начинается магия: CI автоматически запускает terraform plan на твоих изменениях и постит результат прямо в PR комментарием.
Этот plan и есть твоё ревью. Обычный diff кода показывает, что ты написал. А terraform plan показывает, что реально произойдёт с инфраструктурой: сколько ресурсов добавится, сколько изменится, сколько (внимание!) уничтожится. Строчка
Код: Выделить всё
Plan: 1 to add, 0 to change, 3 to destroyДальше связка terraform plan apply разносится по двум событиям:
- plan - на каждый push в PR, как проверка и предпросмотр. Безопасно, ничего не меняет.
- apply - только после merge в master, только из CI, и в идеале только после ручного аппрува.
Код: Выделить всё
# .gitlab-ci.yml, упрощённо
stages:
- validate
- plan
- apply
validate:
stage: validate
script:
- terraform init -input=false
- terraform fmt -check
- terraform validate
plan:
stage: plan
script:
- terraform init -input=false
- terraform plan -input=false -out=tfplan
artifacts:
paths:
- tfplan
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
apply:
stage: apply
script:
- terraform init -input=false
- terraform apply -input=false tfplan
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual # вот он, ручной аппрув на прод
Код: Выделить всё
-out=tfplanКод: Выделить всё
when: manualState и блокировки: почему CI обязан быть единственным
Теперь про terraform state - сердце всей системы. Это файл, где Terraform хранит соответствие между твоим кодом и реальными ресурсами в облаке. Локальный terraform.tfstate на ноутбуке в команде - это билет в ад: он у каждого свой, конфликтует, теряется, и в нём, между прочим, лежат секреты в открытом виде.
Решение - удалённый бэкенд. Для Yandex Cloud это Object Storage (S3-совместимый), а блокировки берёт на себя таблица в YDB. Выглядит так:
Код: Выделить всё
terraform {
backend "s3" {
endpoints = {
s3 = "https://storage.yandexcloud.net"
dynamodb = "https://docapi.serverless.yandexcloud.net/ru-central1/b1g.../etn..."
}
bucket = "my-tfstate-bucket"
region = "ru-central1"
key = "prod/terraform.tfstate"
dynamodb_table = "terraform-lock"
skip_region_validation = true
skip_credentials_validation = true
skip_requesting_account_id = true
skip_s3_checksum = true
}
}
Зачем нам один-единственный исполнитель в лице CI, теперь понятно: state лежит в защищённом бакете, доступ к нему - только у сервисного аккаунта пайплайна, а блокировка не даёт двум apply наступить друг другу на ноги. Никто с ноутбука к этому state даже не подключится без ключей, которые лежат в секретах CI, а не у людей в .bashrc.
Кстати, всё ровно то же справедливо и для opentofu - открытого форка Terraform. Конвейер, бэкенд, блокировки, золотое правило - один в один, меняется только имя бинарника. Если решишь мигрировать на OpenTofu, твой CI-процесс переживёт это почти без изменений.
Типичные грабли
- apply на master без plan-артефакта. Если apply пересчитывает план заново, а не берёт сохранённый, ты катишь не то, что ревьюили. Всегда apply из -out файла.
- Секреты в state и в логах. State содержит пароли и ключи. Бакет - приватный, версионирование включено, логи plan чистим от чувствительных значений (помечай переменные как sensitive).
- Нет блокировки. Удалённый state без dynamodb_table / YDB-локов - это гонка. Два apply, поехавший state, ручное восстановление. Не экономь на блокировках.
- Дрейф из-за ручных правок. Кто-то тронул облако руками - plan покажет неожиданные изменения. Лечится дисциплиной (золотое правило) и регулярным plan по расписанию как детектором дрейфа.
- Один state на всё. Прод и dev в одном ключе state - и тестовый apply трогает прод. Разделяй окружения по разным key/бакетам.
Руками, на тестовом проекте, без спешки:
- Заведи репозиторий, положи в него .tf с одной ВМ или бакетом в Yandex Cloud.
- Настрой backend "s3" на Object Storage и блокировку через YDB. Сделай terraform init, убедись что state уехал в облако.
- Создай ветку, поменяй один атрибут, открой PR. Напиши простейший пайплайн, чтобы plan постился в PR.
- Слей PR и сделай apply из CI с ручным аппрувом (when: manual). Почувствуй кнопку.
- Для эксперимента запусти два apply почти одновременно и посмотри, как второй упирается в блокировку.
- Сформулируй золотое правило Terraform своими словами. Что считается единственным источником правды о проде?
- Почему apply должен брать сохранённый plan-артефакт, а не пересчитывать план заново перед применением?
- Какой механизм защищает state от двух одновременных apply и где он живёт в случае Yandex Cloud?
- Чем грозит ручная правка ресурса в веб-консоли облака и как пайплайн это обнаружит?
Конвейер для terraform yandex cloud - это не бюрократия, а способ спать спокойно. master = прод, изменения только через git, plan на PR как настоящее ревью, apply из CI после аппрува, state в защищённом бэкенде с блокировкой. Убери руки от облака и от локального apply - и инфраструктура как код наконец станет предсказуемой. Модули terraform, переменные, окружения - всё это надстройки, но без дисциплины конвейера они тебя не спасут. Золотое правило простое до неприличия, и именно поэтому его так легко нарушить. Не нарушай.