Адаптивный перевод статьи Terraform State Locking with S3 (devopscube). Изложение своими словами; конфиги и команды — как в оригинале. Все права на исходный материал принадлежат авторам devopscube. Диаграмма ниже — оригинальная (Mermaid), а не перенос картинок из статьи.
Что такое блокировка стейта и зачем она
Terraform хранит состояние инфраструктуры (state) в одном файле. Если два человека (или два CI-джоба) одновременно запустят terraform apply на одном и том же стейте — они начнут писать в него параллельно, и это гонка: state может побиться, ресурсы — рассинхронизироваться с реальностью.
Блокировка стейта (state locking) решает это: перед изменением стейта Terraform берёт эксклюзивную блокировку, а остальные процессы ждут или получают понятную ошибку «залочено».
Классический подход: DynamoDB
Исторически для S3-backend блокировку делали через отдельную таблицу DynamoDB:
- Terraform создаёт в таблице запись с lock ID → это и есть блокировка.
- Получив блокировку, процесс работает со стейтом в S3.
- TTL/таймаут в DynamoDB страхует от «вечной» блокировки, если процесс умер аварийно.
Подход проверенный временем и даёт более строгие гарантии консистентности, но требует поднимать и обслуживать лишний ресурс (таблицу DynamoDB) — и платить за неё.
Новый подход: нативный lockfile в S3
В Terraform 1.10 появилась (как экспериментальная) возможность блокировать стейт прямо в S3, без DynamoDB — через опцию use_lockfile.
Работает это на фиче S3 conditional writes (условная запись, доступна в AWS с августа 2024): S3 умеет атомарно создать объект «только если его ещё нет» (If-None-Match). Terraform использует это, чтобы создать рядом со стейтом файл-блокировку .tflock:
- перед изменением стейта Terraform пытается атомарно создать
<key>.tflock; - если файла не было — блокировка взята, идём дальше;
- если файл уже есть — значит кто-то держит блокировку, получаем ошибку;
- по завершении Terraform удаляет
.tflock.
⚠️ Официально это «eventually consistent» механизм — теоретически при очень высокой конкуренции возможны гонки. Для команд с интенсивными параллельными запусками это стоит учитывать (см. раздел «Что выбрать»).
Как это выглядит по шагам
sequenceDiagram
autonumber
participant U as terraform apply
participant S3 as S3 (bucket)
U->>S3: PUT state.tfstate.tflock (If-None-Match: *)
alt файла ещё нет
S3-->>U: 200 — блокировка взята
U->>S3: читает/пишет state.tfstate
U->>S3: DELETE state.tfstate.tflock
S3-->>U: блокировка снята
else .tflock уже существует
S3-->>U: 412 Precondition Failed
U-->>U: Error: state locked — ждём/выходим
end
Конфигурация backend
Всё, что нужно — добавить use_lockfile = true в блок S3-backend (подставьте свои bucket, key и region):
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "terraform/state.tfstate"
region = "us-west-1"
use_lockfile = true
}
}Никаких dynamodb_table больше не нужно. После правки:
terraform init
terraform applyВо время apply в бакете рядом со state.tfstate появится state.tfstate.tflock, а по завершении — исчезнет. Если в этот момент второй запустит apply, он получит ошибку блокировки.
Требования
- Terraform 1.10+ (опция
use_lockfile). - Актуальный AWS-провайдер.
- Бакет в регионе/аккаунте, где доступны S3 conditional writes.
IAM-права
Процессу Terraform нужны права на объект стейта и на lock-файл рядом с ним. Минимально:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateAndLock",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::my-terraform-state-bucket/terraform/state.tfstate",
"arn:aws:s3:::my-terraform-state-bucket/terraform/state.tfstate.tflock"
]
},
{
"Sid": "ListStateBucket",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-terraform-state-bucket"
}
]
}Best practices
- Включите versioning на бакете стейта — чтобы можно было откатиться к прежней версии state.
- Ограничьте доступ IAM-политиками — стейт содержит чувствительные данные.
- Включите шифрование S3 (SSE-S3 или SSE-KMS) для файла стейта.
- Аккуратно снимайте блокировку вручную, если процесс умер аварийно и
.tflockзавис (terraform force-unlock <LOCK_ID>или удаление объекта — осознанно). - Разносите стейты по проектам/окружениям (отдельный
key), а не держите всё в одном файле.
Что выбрать: use_lockfile или DynamoDB
Нативный S3 use_lockfile — когда:
- хочется простой настройки без лишних AWS-ресурсов;
- не хочется платить и обслуживать DynamoDB;
- запуски Terraform нечастые, без высокой конкуренции.
DynamoDB — когда:
- со стейтом работают несколько команд;
- операции частые и нужна максимально строгая гарантия блокировки;
- критична обработка пограничного случая «процесс упал в момент блокировки».
Итог
Нативная блокировка стейта в S3 через use_lockfile убирает необходимость в отдельной таблице DynamoDB — меньше ресурсов, проще setup, дешевле. Для небольших/средних сетапов с умеренной конкуренцией это отличный дефолт. Там, где несколько команд активно бьются за один стейт и нужны железные гарантии — DynamoDB пока остаётся более надёжным выбором.