lesson ── terraform-intermediate ── ~18 мин ── 5 шагов
До сих пор state жил в terraform.tfstate рядом с HCL. Это работает
пока ты один. Команда из двух человек = два state'а, разъезжаются на
первой же неделе.
Сейчас сделаешь boilerplate setup: один root создаёт S3-бакет под state и DynamoDB-таблицу под lock. Второй root использует их как remote backend. Это типовой layout: bootstrap отдельно от приложения.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Создай файлы в /home/student/tf-backend/bootstrap/:
main.tf:
resource "aws_s3_bucket" "state" {bucket = "linuxlab-tfstate"
}
resource "aws_s3_bucket_versioning" "state" {bucket = aws_s3_bucket.state.id
versioning_configuration {status = "Enabled"
}
}
resource "aws_dynamodb_table" "lock" {name = "linuxlab-tflocks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {name = "LockID"
type = "S"
}
}
Это отдельный root. Его state локальный (по умолчанию). Бакет и таблица, единственные ресурсы, они существуют чтобы держать state остальных root'ов.
Версионирование S3 обязательно: спасает при случайном перезаписе state'а. См. tf-remote-backend-s3.
✓ Bootstrap описан. Init + apply создадут backend-инфру.
cd /home/student/tf-backend/bootstrap
terraform init
terraform apply -auto-approve
После apply:
aws --endpoint-url=http://localstack:4566 s3 ls
aws --endpoint-url=http://localstack:4566 dynamodb list-tables
Видишь linuxlab-tfstate и linuxlab-tflocks. Готово к использованию.
✓ Backend-инфра создана. Теперь app-root укажет на неё.
В /home/student/tf-backend/app/ создай main.tf:
terraform { backend "s3" {bucket = "linuxlab-tfstate"
key = "app/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
# Для LocalStack
endpoints = {s3 = "http://localstack:4566"
dynamodb = "http://localstack:4566"
}
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
use_path_style = true
encrypt = false
}
}
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-app-${random_id.suffix.hex}"}
Заметь:
backend "s3", внутри блока terraform { }.key = "app/terraform.tfstate", путь внутри бакета. Конвенция:
<проект>/<env>/terraform.tfstate.endpoints. Нужно только для LocalStack. В реальном AWS убираешь.encrypt = false, для LocalStack. В реальном AWS, true,
желательно с KMS.Параметры backend нельзя интерполировать через var.*. Это
статика или partial-backend.
✓ App-root описан с S3-backend. Init подхватит.
cd /home/student/tf-backend/app
terraform init
Вывод покажет:
Initializing the backend...
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Локального файла terraform.tfstate не будет. State идёт в
S3. Проверь:
ls -la /home/student/tf-backend/app/
aws --endpoint-url=http://localstack:4566 s3 ls s3://linuxlab-tfstate/
В директории, нет terraform.tfstate. В бакете, пока пусто (ещё
apply не был).
✓ App видит S3-backend. State будет писаться в бакет.
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 для полной матрицы.
terraform apply -auto-approve
После apply, посмотри что в бакете:
aws --endpoint-url=http://localstack:4566 s3 ls s3://linuxlab-tfstate/app/
Увидишь terraform.tfstate. Скачать и посмотреть:
aws --endpoint-url=http://localstack:4566 s3 cp s3://linuxlab-tfstate/app/terraform.tfstate /tmp/state.json
cat /tmp/state.json | jq '.serial, .lineage'
Это и есть твой state. Если коллега в другом терминале сделает
terraform init с такими же backend параметрами, получит тот же
state.
Теперь проверим lock через DynamoDB:
aws --endpoint-url=http://localstack:4566 dynamodb scan --table-name linuxlab-tflocks
После apply записей нет (lock отпущен). Если параллельно с твоим
apply кто-то попытается, увидит lock и упадёт с ошибкой
Error acquiring the state lock. См. tf-common-errors.
✓ State живёт в S3. Готова работа в команде, готов CI/CD.
Все 4-5 параметров backend дублируются между prod/stage/dev? Это плохо: правка одного параметра. N PR-ов.
Решение, partial backend:
terraform { backend "s3" {} # пустой блок}
А параметры передавай при init:
# backends/prod.hcl
bucket = "linuxlab-tfstate"
key = "app/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
# backends/dev.hcl
bucket = "linuxlab-tfstate"
key = "app/dev/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "linuxlab-tflocks"
terraform init -backend-config=backends/prod.hcl
# или
terraform init -backend-config=backends/dev.hcl
Один HCL. N окружений. Это типичный multi-env layout. Подробно в уроке 12.
S3-backend держит state-файл в бакете, DynamoDB, lock-запись.
Конфигурируется в terraform { backend "s3" { ... } }. Backend нельзя
интерполировать, параметры или статика, или через -backend-config
при init.
команды
terraform init -migrate-stateпереезд со старого backend на новыйterraform init -backend-config=prod.hclpartial backend, параметры из файлаaws --endpoint-url=$LS s3 ls s3://BUCKET/посмотреть state-файлы в бакетеконцепции