# Remote state в S3: бакет, DynamoDB lock, encryption _State · TerraformLab Knowledge Base_ **TL;DR:** S3-backend хранит `terraform.tfstate` в бакете, DynamoDB-таблица даёт блокировку одного-apply-за-раз. Конфигурация, в блоке `backend "s3"` внутри `terraform { ... }`. State в S3. Это единственный source of truth, локального файла больше нет. Переезд с local на S3, через `terraform init -migrate-state`. ## Зачем 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. Один раз. Потом сослаться: ```hcl 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` | Путь внутри бакета. Конвенция: `<проект>//terraform.tfstate`. | | `region` | Регион бакета (не путать с регионом ресурсов твоего HCL). | | `dynamodb_table` | Имя DynamoDB-таблицы для lock. Hash key = LockID, тип string. | | `encrypt` | SSE для state. Всегда `true`. | | `kms_key_id` | Опциональный KMS ключ. По умолчанию SSE-S3. | ### Backend нельзя интерполировать ```hcl # НЕ РАБОТАЕТ terraform { backend "s3" { bucket = var.state_bucket # ОШИБКА: переменные не разрешены } } ``` Backend конфигурируется **до** того как Terraform читает variables. Если нужна параметризация между env, используй partial backend config: ```hcl # terraform { backend "s3" {} } , пустой блок ``` И передавай при init: ```bash 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 ```bash 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. 1. Добавь в HCL блок `terraform { backend "s3" { ... } }`. 2. Запусти: ```bash terraform init -migrate-state ``` Terraform спросит: «нашёл local state и новый remote backend, перенести?» Отвечаешь `yes`. 3. Локальный `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](/terraform/kb/tf-common-errors.md) про `Error acquiring the state lock`. ### force-unlock Если процесс умер (Ctrl+C, краш), lock остался, `terraform force-unlock `. **Опасно**: если процесс жив и продолжает работу, ты разрешишь второй 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](/terraform/kb/tf-workspace.md)). - **Бекапы поверх S3-versioning не помешают.** Versioning защищает от случайной перезаписи. Не защищает от удаления бакета. Cross-account backup через S3 replication или AWS Backup, для серьёзных проектов обязателен. ## Команды ```bash terraform init -migrate-state ``` Переехать со старого backend на новый (или наоборот). Спросит подтверждение. ```bash terraform init -backend-config=prod.hcl ``` Partial backend config. Используется когда parameters различаются между env. ```bash terraform force-unlock ``` Снять lock вручную. Только когда уверен что процесс умер. ```bash aws s3 cp s3://myorg-terraform-state/billing/prod/terraform.tfstate - | jq .serial ``` Прочитать serial из state напрямую. Полезно для дебага 'кто последний писал'. ```bash aws dynamodb scan --table-name myorg-terraform-locks ``` Видно все активные lock'и. Если что-то висит давно: кандидат на force-unlock. ## См. также - [State: память Terraform о созданном](/terraform/kb/tf-state.md) - [Backend в Terraform: где живёт state](/terraform/kb/tf-init-backends.md) - [state mv, state rm, state pull/push: ручные операции](/terraform/kb/tf-state-manipulation.md) - [LocalStack: учебный AWS, который живёт в Docker](/terraform/kb/localstack-provider.md) - [.terraform.lock.hcl: фиксация версий провайдеров](/terraform/kb/tf-lockfile.md)