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, машина применяет сохранённый план, прод защищён. Дальше — финальный взгляд на безопасность и зрелость.