Зачем remote state
Локальный state живёт рядом с HCL. Это работает пока ты один. Как только команда, начинаются проблемы:
- Кто-то делает apply со своего ноутбука. State у него на диске. У второго коллеги, другой state. Они применяют разные планы к одной инфре. Хаос.
- Ноутбук падает / форматируется, state потерян. Восстанавливать через
terraform importкаждый ресурс руками. - CI/CD не имеет state'а вообще. Каждый run думает что инфра не создана.
Remote backend, это общий state, доступный всем (по правам), с блокировками против параллельных apply. S3+DynamoDB. Это самая частая комбинация в AWS-проектах.
Архитектура
+----------+ +-----------+ +-----------+
| dev | | S3 bucket | | DynamoDB |
| laptop A | ───→ | tfstate | ←─── | lock |
+----------+ +-----------+ +-----------+
↑ ↑
│ │
+----------+ плюс │ проверь │ перед
| dev | запись │ записью
| laptop B | ──────────────────────────────┘
+----------+
- S3 хранит файл
terraform.tfstate. Версионирование (S3 versioning) обязательно включить, это спасает от случайного перезаписи. - DynamoDB хранит lock-record. Перед write Terraform пишет lock, после apply удаляет. Если кто-то параллельно пытается, видит lock и падает с понятной ошибкой.
- Encryption. S3 server-side encryption по умолчанию. Дополнительно можно AWS KMS со своим ключом для compliance.
Минимальная конфигурация backend
Создать ресурсы (S3 + DynamoDB) можно отдельным root-модулем «bootstrap» или руками через AWS CLI. Один раз. Потом сослаться:
terraform { backend "s3" {bucket = "myorg-terraform-state"
key = "billing/prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "myorg-terraform-locks"
encrypt = true
}
}
Ключевые поля:
| Поле | Что |
|---|---|
bucket | Имя S3-бакета для state'ов. Один бакет, много state'ов через разные key. |
key | Путь внутри бакета. Конвенция: <проект>/<env>/terraform.tfstate. |
region | Регион бакета (не путать с регионом ресурсов твоего HCL). |
dynamodb_table | Имя DynamoDB-таблицы для lock. Hash key = LockID, тип string. |
encrypt | SSE для state. Всегда true. |
kms_key_id | Опциональный KMS ключ. По умолчанию SSE-S3. |
Backend нельзя интерполировать
# НЕ РАБОТАЕТ
terraform { backend "s3" {bucket = var.state_bucket # ОШИБКА: переменные не разрешены
}
}
Backend конфигурируется до того как Terraform читает variables. Если нужна параметризация между env, используй partial backend config:
# terraform { backend "s3" {} } , пустой блокИ передавай при init:
terraform init -backend-config="bucket=myorg-terraform-state" \
-backend-config="key=billing/prod/terraform.tfstate" \
-backend-config="region=us-east-1" \
-backend-config="dynamodb_table=myorg-terraform-locks"
Или через -backend-config=prod.hcl файл. Это стандартная техника для
multi-env layout.
Bootstrap: создание самих бакета и таблицы
Курица-яйцо: чтобы state был в S3, S3-бакет должен существовать. Решений два:
Вариант 1: руками через AWS CLI
aws s3api create-bucket --bucket myorg-terraform-state --region us-east-1
aws s3api put-bucket-versioning --bucket myorg-terraform-state \
--versioning-configuration Status=Enabled
aws s3api put-bucket-encryption --bucket myorg-terraform-state \
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'aws dynamodb create-table --table-name myorg-terraform-locks \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
Один раз на организацию. State этих ресурсов не хранится в terraform, они создаются руками, документируются, никто не трогает.
Вариант 2: отдельный bootstrap-модуль с local state
Один root-модуль создаёт бакет+таблицу через aws_s3_bucket+aws_dynamodb_table,
его state лежит локально (или в git как bootstrap.tfstate, для аудита).
Все остальные root'ы используют S3-backend.
Это аккуратнее (всё описано в HCL), но bootstrap-state нужно где-то держать и защищать. Часто кладут в отдельный приватный git-репо.
Миграция с local на S3
Был файл terraform.tfstate. Хотим перевести в S3.
-
Добавь в HCL блок
terraform { backend "s3" { ... } }. -
Запусти:
bashterraform init -migrate-state
Terraform спросит: «нашёл local state и новый remote backend, перенести?» Отвечаешь
yes. -
Локальный
terraform.tfstateостаётся на диске какterraform.tfstate.backup, не удаляй сразу, дай неделю на проверку.
Обратная миграция (с S3 на local): тот же init -migrate-state, только в
HCL убрать backend блок. Тоже спросит подтверждение.
Locking в действии
Когда ты делаешь apply, Terraform пишет в DynamoDB:
LockID: myorg-terraform-state/billing/prod/terraform.tfstate-md5
Operation: OperationTypeApply
Who: alice@laptop
Created: 2026-05-26 14:00:00 UTC
Info:
Параллельный apply увидит lock и упадёт:
Error: Error acquiring the state lock
Lock Info:
ID: ...
Operation: OperationTypeApply
Who: alice@laptop
Created: 2026-05-26 14:00:00 UTC
См. tf-common-errors про Error acquiring the state lock.
force-unlock
Если процесс умер (Ctrl+C, краш), lock остался, terraform force-unlock <ID>.
Опасно: если процесс жив и продолжает работу, ты разрешишь второй apply
и сломаешь state. Используй только когда уверен.
Подводные камни
-
Без версионирования бакета, потерял state, потерял всю инфру. Случайный
terraform state rmна root + последующий apply = пересоздание всего. Версионирование S3 даёт roll-back на предыдущую версию state. Обязательно. -
Один S3-бакет на всю организацию. Не плодить «по бакету на проект», permissions сложнее, бухгалтерия запутанная. Один бакет, разные
key. IAM-policy ограничивает кто к каким key может писать. -
encrypt = true, это minimum. Для compliance. SSE-KMS с customer-managed key, чтобы доступ к state требовал KMS-permission отдельно от S3-permission. Двойной слой. -
DynamoDB-таблица должна быть в том же регионе что бакет. Иначе блокировка работает медленнее (cross-region call). Best practice, оба в одном регионе, обычно «головном» региона организации.
-
LocalStack эмулирует S3-backend, но не идеально. В наших уроках мы используем LocalStack, это работает для обучения и интеграционных тестов, но в проде ты столкнёшься с реальным S3+DynamoDB. Поведение в edge cases (большой state, медленные сети, eventual consistency) отличается.
-
keyнельзя интерполировать на стадии apply. Это часть backend config, разрешается на init. Если хочешь переключаться между prod и dev state'ами, две разные конфигурации init, или Terraform workspaces (см. tf-workspace). -
Бекапы поверх S3-versioning не помешают. Versioning защищает от случайной перезаписи. Не защищает от удаления бакета. Cross-account backup через S3 replication или AWS Backup, для серьёзных проектов обязателен.