lesson ── terraform-production ── ~18 мин ── 5 шагов
Собираем всё в один pipeline. .github/workflows/terraform.yml с тремя
jobs: lint (fmt+validate+tflint+checkov), plan (с artifact upload), apply
(с download + apply). Локально гоняем через act, он эмулирует
GitHub Actions без полёта на github.com. Реальный AWS не используем
workflow читает env-переменные провайдера, которые указывают на
LocalStack.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-gha
cat > main.tf <<'EOF'
resource "aws_s3_bucket" "gha_demo" {bucket = "linuxlab-gha-demo"
tags = {ManagedBy = "terraform"
Environment = "dev"
}
}
EOF
cat > .github/workflows/terraform.yml <<'EOF'
name: Terraform CI
on:
push:
branches: [main]
pull_request:
env:
TF_IN_AUTOMATION: "true"
TF_INPUT: "false"
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
AWS_DEFAULT_REGION: us-east-1
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8
- name: terraform fmt -check
run: terraform fmt -check -recursive
- name: terraform validate
run: |
terraform init -backend=false -no-color
terraform validate -no-color
plan:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8
- name: terraform plan
run: |
terraform init -no-color
terraform plan -no-color -out=plan.tfplan
- name: show plan
run: terraform show -no-color plan.tfplan > plan.txt
- uses: actions/upload-artifact@v4
with:
name: tf-plan
path: |
plan.tfplan
plan.txt
apply:
needs: plan
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8
- uses: actions/download-artifact@v4
with:
name: tf-plan
- name: terraform apply
run: |
terraform init -no-color
terraform apply -no-color plan.tfplan
EOF
Структура, три job'а, sequential через needs:. apply гоняется
только на main (через if).
✓ Workflow готов. Запустим act.
act -l -W .github/workflows/terraform.yml
Должно показать три job'а: lint, plan, apply, с их dependencies.
act парсит workflow, понимает needs: и conditional if:. Для
нашего сэндбокса refs/heads/main подойдёт по defaultу, но проще
запускать конкретный job.
✓ act парсит workflow корректно.
act -j lint \
-W .github/workflows/terraform.yml \
--container-architecture linux/amd64 \
--bind 2>&1 | tail -20
Что флаги:
-j lint, только один job.--container-architecture linux/amd64, нужен на arm64-хостах.--bind, монтирует текущий каталог вместо клонирования (быстрее
для локальной dev-петли).В первом запуске act скачает Ubuntu runner-образ, может занять минуту. Дальше быстро.
Должно показать Success - lint (или подробные шаги fmt и validate).
✓ Lint-job отработал в эмулированном GHA.
act push \
-W .github/workflows/terraform.yml \
--container-architecture linux/amd64 \
--bind 2>&1 | tail -30
act push, событие push. По умолчанию act гоняет всё, что
триггерится этим событием. Job'ы выполнятся sequentially из-за
needs:.
Артефакты act хранит в /tmp/artifact внутри runner-container'а
или в .artifacts/ локально (зависит от version'и). Если pipeline
успешен, все три job'а зелёные.
✓ Полный pipeline отработал. lint → plan → apply.
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 для полной матрицы.
Добавим Checkov-job. Положим baseline:
cat > policies/check.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
checkov -d . --quiet --no-guide --soft-fail-on CKV_AWS_18
echo "Checkov gate passed."
EOF
chmod +x policies/check.sh
mkdir -p policies
Обнови workflow, добавь policy job после lint, до plan:
python3 - <<'EOF'
with open('.github/workflows/terraform.yml') as f:wf = f.read()
injection = """
policy:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: checkov
run: checkov -d . --quiet --no-guide --soft-fail-on CKV_AWS_18
"""
wf = wf.replace(" plan:\n needs: lint",injection + "\n plan:\n needs: [lint, policy]")
with open('.github/workflows/terraform.yml', 'w') as f:f.write(wf)
EOF
grep -A2 "needs:" .github/workflows/terraform.yml
plan теперь требует и lint, и policy. Если бакет в HCL
слабый, Checkov ловит, plan не стартует.
✓ Policy-gate в pipeline. Pipeline стал production-grade.
Чего act не делает или делает по-другому:
OIDC. Реальный GHA выдаёт OIDC-token через
actions/configure-aws-credentials. act, нет; локально
работают только access-keys или mock'и.
Permissions. permissions: id-token: write в act
игнорируется.
Environments. GitHub UI «environment: prod» с required reviewers act'ом не моделируется. Job просто стартует.
Secrets. GHA-secrets act берёт из .secrets локально или
--secret-file. Защиты как в реальном GitHub, нет.
Cache. actions/cache в act работает, но через локальный
путь. Расхождение в content иногда видно при первом запуске.
Когда act ценен: проверить «workflow вообще запустится». Когда не ценен: продакшен-debug security и OIDC-specifics, это надо проверять в реальном GitHub на feature-branch.
Workflow, YAML в .github/workflows/. Jobs: lint → plan (uploads
artifact) → apply (downloads artifact). act гоняет workflow локально
в Docker-контейнере, использует образ Ubuntu похожий на GHA-runner.
команды
act --container-architecture linux/amd64 -W .github/workflows/terraform.ymlзапустить workflow.act -lсписок job'ов в workflow.act -j lintзапустить только один job.act -ndry-run, что бы запустилось, без execution.концепции