lesson ── terraform-production ── ~14 мин ── 6 шагов
Финал production-трека. Drift, расхождение между HCL и облаком (кто-то
правит руками, default-теги, чужой apply). Ловим scheduled-job'ой в CI:
terraform plan -detailed-exitcode, exit 2 = drift. На этом уроке
создаёшь baseline, ломаешь его через aws-cli, видишь как plan
обнаруживает drift.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-drift
cat > main.tf <<'EOF'
resource "aws_s3_bucket" "drift_demo" {bucket = "linuxlab-drift-demo"
tags = {ManagedBy = "terraform"
Owner = "student"
}
}
EOF
terraform init -no-color > /dev/null
terraform apply -auto-approve -no-color
Бакет создан, state совпадает с реальностью. Baseline.
✓ Baseline есть. State == облако.
set +e
terraform plan -detailed-exitcode -no-color > /dev/null 2>&1
code=$?
set -e
echo "exit: $code"
Должно быть 0, clean, нет drift'а. Это то что мы ожидаем в production когда ничего не сломано.
Если ты добавишь сейчас --refresh=false, exit код тот же, но
проверка слабее (см. подводные камни).
✓ Plan чистый. Без drift'а, clean cron-pass.
«Кто-то полез в Console и поправил теги»:
aws --endpoint-url=http://localstack:4566 \
s3api put-bucket-tagging \
--bucket linuxlab-drift-demo \
--tagging 'TagSet=[
{Key=ManagedBy,Value=manual}, {Key=Hacker,Value=was-here}]'
aws --endpoint-url=http://localstack:4566 \
s3api get-bucket-tagging --bucket linuxlab-drift-demo
Реальный бакет теперь:
ManagedBy: manual (был terraform)Hacker: was-here (новый)Owner пропалState Terraform этого не знает.
✓ Drift введён. Сейчас Terraform его обнаружит.
set +e
terraform plan -detailed-exitcode -no-color 2>&1 | tail -20
code=$?
set -e
echo "exit: $code"
Должно показать diff (теги отличаются) и exit 2. Это и есть drift-сигнал.
В CI:
Можно прочитать diff детально:
terraform plan -no-color -out=drift.tfplan 2>&1 | grep -A20 "drift_demo" | head -40
Видно конкретно что разъехалось, Terraform хочет вернуть теги к HCL-описанию.
✓ Drift пойман. exit 2, сигнал для cron-алерта.
Производственный shell-скрипт для scheduled-job'а:
cat > drift-check.sh <<'EOF'
#!/usr/bin/env bash
set -uo pipefail
cd /home/student/tf-drift
terraform init -input=false -no-color > /dev/null
set +e
terraform plan \
-detailed-exitcode \
-input=false \
-no-color \
-lock-timeout=2m \
-out=drift.tfplan
code=$?
set -e
case $code in
0)
echo "drift-check: clean, no changes"
exit 0
;;
2)
echo "drift-check: DRIFT DETECTED"
terraform show -no-color drift.tfplan > drift.txt
# Здесь был бы webhook в Slack:
# curl -X POST -H 'Content-Type: application/json' \
# --data "{\"text\": \"Drift detected:\n$(cat drift.txt | head -50)\"}" \# "$SLACK_WEBHOOK_URL"
echo "--- begin drift ---"
head -30 drift.txt
echo "--- end drift ---"
exit 1 # CI считает drift как failure
;;
*)
echo "drift-check: ERROR (exit $code)"
exit $code
;;
esac
EOF
chmod +x drift-check.sh
./drift-check.sh 2>&1 | tail -30
echo "script exit: $?"
Должно показать DRIFT DETECTED и exit 1 (потому что drift есть).
✓ Cron-script готов. В GitHub Actions это запускается через schedule cron.
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 для полной матрицы.
Есть две стратегии:
1. Reconcile, apply возвращает облако к HCL:
terraform apply -auto-approve drift.tfplan
Это уничтожит teг Hacker:was-here, восстановит ManagedBy:terraform
и Owner:student. Подходит когда drift, нежелательный.
2. Update HCL, облако правое:
# ничего не делать с облаком, а в HCL добавить:
# tags = { ManagedBy = "manual", Owner = "student" }Это когда «кто-то правил Console», но изменение нужное; ты
легализуешь его в HCL. Затем apply -refresh-only для синка state'а.
3. Ignore, drift есть, но не важен:
lifecycle {ignore_changes = [tags["Hacker"]]
}
Terraform перестанет рапортовать о теге Hacker. Используй когда
этот тег ставится другой системой (k8s-operator, AWS Config) и
не относится к Terraform.
Сделаем reconcile:
terraform apply -auto-approve -no-color > /dev/null
aws --endpoint-url=http://localstack:4566 \
s3api get-bucket-tagging --bucket linuxlab-drift-demo
Теги вернулись к HCL-версии.
✓ Drift отreconcile'ен. Production-трек закрыт.
terraform plan видит drift только для ресурсов, которые в
state. Если кто-то создал бакет руками, он в облаке есть,
в state нет, plan его не увидит.
Для этого, driftctl:
# установка
curl -L https://github.com/snyk/driftctl/releases/latest/download/driftctl_linux_amd64 \
-o /usr/local/bin/driftctl
chmod +x /usr/local/bin/driftctl
# сканирование
driftctl scan \
--from tfstate+file://terraform.tfstate \
--output console
Покажет:
Это шире, чем terraform plan. В production стек: cron с
terraform plan -detailed-exitcode (часто) + driftctl scan
(реже, например, раз в неделю).
Альтернатива от AWS, AWS Config: cloud-native сервис, который логит каждое изменение конфигурации. Использовать когда хочешь cross-account / cross-team visibility, и audit-trail важнее terraform-specific сигнала.
См. tf-drift-detection.
terraform plan -detailed-exitcode, exit 0 (clean), 1 (error),
2 (drift). Cron-job в CI гоняет это раз в день/час, на 2, алерт
в Slack/PD/issue. Plan-job читает state, не пишет, IAM-роль
read-only.
команды
terraform plan -detailed-exitcode -no-colorканонический drift-check. Exit код = вердикт.terraform plan -refresh-onlyтолько refresh без сравнения с HCL, что в state изменилось из облака.aws s3api put-bucket-tagging --bucket X --tagging '...'пример того что делает «чужой apply», создаёт drift.концепции