linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-production-07-plan-as-artifact

lesson ── terraform-production ── ~14 мин ── 5 шагов

Plan как артефакт, между PR и apply

В 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 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    HCL и первый plan-в-файл

    bash
    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-файл создан. Это и есть артефакт.

  2. 02

    Превращение plan.tfplan в txt и json

    Для ревьюера, human:

    bash
    terraform show -no-color plan.tfplan > plan.txt
    head -30 plan.txt

    Это то, что вставляешь в PR-комментарий, diff +/~/-.

    Для policy-gate'а, машина:

    bash
    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.

  3. 03

    detailed-exitcode для условного pipeline

    bash
    set +e
    terraform plan -detailed-exitcode -no-color -out=plan.tfplan
    code=$?
    set -e
    echo "exit: $code"

    Apply ещё не делали, должно быть 2 (есть changes). Применяем:

    bash
    terraform apply -input=false -no-color plan.tfplan

    Теперь снова detailed-exitcode:

    bash
    set +e
    terraform plan -detailed-exitcode -no-color -out=plan.tfplan
    code=$?
    set -e
    echo "exit: $code"

    Должно быть 0, нет changes.

    Шаблон CI:

    • exit 0 → skip apply.
    • exit 2 → upload-artifact + apply-job ждёт approve.
    • exit 1 → fail pipeline, разбирайся.

    ✓ detailed-exitcode даёт три состояния. CI ветвится на них.

  4. 04

    Симулируем pipeline: plan-job → apply-job

    Сделаем изменение, повторим plan, передадим plan в «apply-job» (другой shell, как если бы другая VM):

    bash
    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

    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 для полной матрицы.

    • → OpenTofu parity
  5. 05

    Stale plan, что происходит если состояние ушло вперёд

    Apply на «протухший» plan должен падать. Создаём stale-сценарий:

    bash
    # 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 как секрет

    plan.tfplan содержит:

    • Все sensitive значения переменных и outputs.
    • Содержимое resources (включая password'ы у aws_db_instance).
    • Computed-атрибуты, которые провайдер вычислил при refresh (private endpoints, IP-адреса, ARN'ы).

    Это значит:

    1. retention минимум. В GitHub Actions actions/upload-artifact retention-days: 1 хватит для plan-then-apply pipeline.

    2. Не публиковать. Public repository = публичный артефакт = уfile секретов.

    3. terraform show plan.tfplan развернёт всё. Любой с доступом к артефакту фактически видит state-секции, которые в HCL sensitive.

    4. terraform show -json ещё хуже, машинно-читаемая структура без redaction'а.

    Для prod-pipeline'а: plan-файлы шифруются (sops, age) либо artifact-encryption фичей CI. См. tf-secrets-in-state.

    • → Plan-as-artifact подробно
    • → Секреты в plan и 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.

концепции

  • · plan.tfplan, бинарь, не читай руками
  • · stale plan: между plan и apply изменили state, apply упадёт
  • · plan.tfplan содержит sensitive, обращайся как с секретом

← предыдущий

state mv, state rm: операции над state

следующий →

Capstone, VPC + ALB + ECS Fargate + Lambda

Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки