CI/CD и автоматизация Terraform

Зрелая команда не запускает apply с ноутбука. Terraform живёт в CI/CD: на pull request показывается plan, при merge выполняется apply, а прод защищён ручным подтверждением.

Локальный apply в проде — это «доверьтесь, я проверил». CI/CD заменяет доверие процессом: план виден всей команде в PR, применяет машина, история сохранена. Так инфраструктура становится по-настоящему ревьюируемой.

Идея простая: terraform plan запускается автоматически на каждый pull request, и его вывод попадает в обсуждение — ревьюеры видят точный diff инфраструктуры. После одобрения и merge пайплайн делает apply. Прод обычно требует дополнительного ручного подтверждения (manual approval).

Типичный пайплайн

# стадия на pull request
terraform init -input=false
terraform fmt -check          # стиль
terraform validate            # синтаксис
terraform plan -out=tfplan    # план в файл, виден в PR

# стадия на merge в main (после approve)
terraform apply -input=false tfplan   # применяем ИМЕННО тот план

Ключевой приём — plan -out=tfplan на ревью и apply tfplan при merge: применяется ровно тот план, который видели и одобрили, без сюрпризов от изменившейся за это время реальности. Секреты приходят из хранилища CI (TF_VAR_), блокировка state защищает от параллельных пайплайнов.

  PR открыт         ревью           merge в main
  --------          -----           ------------
  fmt+validate  ->  plan виден  ->  approve  ->  apply tfplan
                    в PR                          (тот же план)
  машина, не ноутбук; история и аудит сохранены

Как работает под капотом

Сохранённый план — это «замороженный» diff. Между ревью и применением реальность может измениться; Terraform проверяет, что сохранённый план всё ещё валиден. Смоделируем gate-логику пайплайна:

def pipeline(stage, checks):
    print(f"== стадия: {stage} ==")
    for name, ok in checks:
        mark = "PASS" if ok else "FAIL"
        print(f"  [{mark}] {name}")
        if not ok:
            print("  -> пайплайн остановлен, apply НЕ будет")
            return False
    return True

pr_ok = pipeline("pull_request", [
    ("fmt -check", True), ("validate", True), ("plan", True),
])
if pr_ok:
    pipeline("merge_to_main", [
        ("manual approval (prod)", True),
        ("state lock получен", True),
        ("apply saved plan", True),
    ])

«Попробуй сам ▶» — поставьте любой чек в False: пайплайн остановится до apply. Так процесс не пускает ошибки в прод.

Частые ошибки

  • Apply с ноутбука в обход CI. Изменения мимо ревью и истории; никто не видел план.
  • Plan и apply без сохранённого файла. Применяете не тот diff, что ревьюили, — реальность могла измениться.
  • Секреты в логах CI. Несекретные переменные плюс отсутствие sensitive = утечка в публичных логах сборки.

Best practices

  • plan на каждый PR, apply сохранённого плана после merge — и только машиной.
  • Прод за ручным подтверждением; dev можно применять автоматически.
  • Секреты — через защищённые переменные CI и менеджеры секретов, никогда в коде и логах.

Разбор глубже

Между ревью и применением есть тонкость, которую важно осознавать: сохранённый план устаревает. Если между моментом, когда вы сделали plan -out=tfplan, и моментом apply tfplan кто-то изменил реальность (или другой пайплайн применил свои изменения), Terraform при apply обнаружит, что состояние больше не то, на котором строился план, и откажется применять устаревший план. Это защита от гонок: лучше пересобрать план заново, чем применить diff, рассчитанный от уже неактуальной реальности. Поэтому окно между plan и apply стараются делать коротким, а state защищают блокировкой, чтобы между этими шагами никто не вклинился.

Зрелые пайплайны добавляют ещё несколько слоёв поверх базового plan/apply. Комментарий в PR с человекочитаемым выводом плана — чтобы ревьюер не лез в логи CI. Статус-чек, блокирующий merge, пока plan не прошёл успешно. Ручное одобрение (manual approval gate) перед прод-apply — чтобы финальное «да» нажимал человек. И аудит: кто, когда и какой план применил, сохраняется в истории. Всё это превращает изменение инфраструктуры из «инженер что-то накатил с ноутбука» в прозрачный, отслеживаемый и обратимый процесс — ровно ту дисциплину, ради которой и затевалась инфраструктура как код.

Итог: CI/CD превращает Terraform в ревьюируемый процесс: plan виден в PR, машина применяет сохранённый план, прод защищён. Дальше — финальный взгляд на безопасность и зрелость.

Проверьте себя
1. Зачем применять сохранённый план (apply tfplan) вместо нового apply?
AЭто быстрее
BЧтобы применить ровно тот diff, что был отревьюен, без сюрпризов от изменившейся реальности
CЧтобы зашифровать state
DСохранённый план не нужен
2. Где в зрелом процессе обычно запускается apply для прода?
AНа ноутбуке инженера
BВ CI/CD после ревью plan в PR и ручного подтверждения
CАвтоматически без ревью
DВообще не запускается