lesson ── terraform-production ── ~14 мин ── 5 шагов
В production-pipeline плана и applя, разные jobs. Plan-job делает
plan -out=plan.tfplan, грузит как артефакт; apply-job скачивает
ровно тот файл и применяет. Цель, чтобы apply накатил тот самый
plan, который видел ревьювер, а не новый; гарантия держится пока
артефакт в защищённом storage и ревьювер смотрел show именно от
этого файла. На этом уроке сэмулируешь pipeline shell-ом, поймёшь
-input=false и detailed-exitcode.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-artifact
cat > main.tf <<'EOF'
resource "aws_s3_bucket" "artifact_demo" {bucket = "linuxlab-artifact-demo"
tags = {ManagedBy = "terraform"
}
}
output "name" {value = aws_s3_bucket.artifact_demo.bucket
}
EOF
terraform init -input=false -no-color > /dev/null
terraform plan \
-input=false \
-no-color \
-lock-timeout=2m \
-out=plan.tfplan
ls -la plan.tfplan
Файл plan.tfplan, бинарный, не редактируется руками. Это
сериализованное состояние «что Terraform хочет сделать».
✓ Plan-файл создан. Это и есть артефакт.
Для ревьюера, human:
terraform show -no-color plan.tfplan > plan.txt
head -30 plan.txt
Это то, что вставляешь в PR-комментарий, diff +/~/-.
Для policy-gate'а, машина:
terraform show -json plan.tfplan > plan.json
jq '.resource_changes[].address' plan.json
JSON содержит структуру resource_changes, в которой OPA и Checkov
ищут нарушения. Один plan.tfplan, два представления.
✓ Plan в трёх форматах: бинарь для apply, txt для review, json для policy.
set +e
terraform plan -detailed-exitcode -no-color -out=plan.tfplan
code=$?
set -e
echo "exit: $code"
Apply ещё не делали, должно быть 2 (есть changes). Применяем:
terraform apply -input=false -no-color plan.tfplan
Теперь снова detailed-exitcode:
set +e
terraform plan -detailed-exitcode -no-color -out=plan.tfplan
code=$?
set -e
echo "exit: $code"
Должно быть 0, нет changes.
Шаблон CI:
✓ detailed-exitcode даёт три состояния. CI ветвится на них.
Сделаем изменение, повторим plan, передадим plan в «apply-job» (другой shell, как если бы другая VM):
sed -i 's|"linuxlab-artifact-demo"|"linuxlab-artifact-v2"|' main.tf
cat main.tf
# plan-job
terraform plan \
-input=false \
-no-color \
-out=plan.tfplan
# сэмулируем upload-artifact / download-artifact
mkdir -p /tmp/ci-artifacts/
cp plan.tfplan /tmp/ci-artifacts/
# apply-job, представь это другая машина в pipeline
cp /tmp/ci-artifacts/plan.tfplan ./plan.tfplan
terraform apply -input=false -no-color plan.tfplan
Apply сделал ровно то, что было в plan'е. В реальном GitHub Actions
этот шаг, actions/upload-artifact + actions/download-artifact.
✓ Pipeline-цикл работает. Bucket переименован через сохранённый plan.
OpenTofu держит CLI и state совместимыми с Terraform по командам
этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе
сделай backup state и прогон на feature-branch - расхождения
концентрируются в новых фичах (variables в backend,
state-encryption, OCI registry-backed модули). См.
tf-opentofu-parity для полной матрицы.
Apply на «протухший» plan должен падать. Создаём stale-сценарий:
# plan один раз
terraform plan -input=false -out=stale.tfplan
# делаем apply через другой plan (другие changes)
sed -i 's|"linuxlab-artifact-v2"|"linuxlab-artifact-v3"|' main.tf
terraform plan -input=false -out=current.tfplan
terraform apply -input=false current.tfplan
# теперь пытаемся apply'нуть СТАРЫЙ stale.tfplan
set +e
terraform apply -input=false stale.tfplan 2>&1 | head -20
code=$?
set -e
echo "exit: $code"
Apply упал, потому что stale plan ссылается на состояние, которое устарело. Terraform защищает от race condition между plan и apply.
Это критично: ловит ситуации, когда между «reviewer одобрил» и «CI apply'нул» state ушёл вперёд. Если кто-то pushed что-то в main и apply сделал, твой stale plan уже не релевантен; apply падает.
✓ Stale plan защищает от race-condition. Это safety-net.
plan.tfplan содержит:
aws_db_instance).Это значит:
retention минимум. В GitHub Actions
actions/upload-artifact retention-days: 1 хватит для
plan-then-apply pipeline.
Не публиковать. Public repository = публичный артефакт = уfile секретов.
terraform show plan.tfplan развернёт всё. Любой с доступом
к артефакту фактически видит state-секции, которые в HCL
sensitive.
terraform show -json ещё хуже, машинно-читаемая
структура без redaction'а.
Для prod-pipeline'а: plan-файлы шифруются (sops, age) либо artifact-encryption фичей CI. См. tf-secrets-in-state.
terraform plan -input=false -out=plan.tfplan, бинарный план.
terraform show -json plan.tfplan > plan.json, машинно-читаемый.
terraform apply plan.tfplan, apply ровно того, что в файле.
-detailed-exitcode 0=clean, 1=err, 2=changes, gate для CI.
команды
terraform plan -input=false -no-color -out=plan.tfplanстандартный automation-режим, plan в файл.terraform show -no-color plan.tfplan > plan.txthuman-readable для ревью.terraform show -json plan.tfplan > plan.jsonдля OPA/Checkov/terraform-compliance.terraform apply -input=false -no-color plan.tfplanapply сохранённого плана. Без fresh-plan.концепции