Зачем нужен state
Представьте: вы написали HCL с одним S3-бакетом и сделали apply. Бакет создался, имя, my-bucket-12345. Запускаете apply ещё раз. Откуда Terraform узнает, что бакет: тот же самый бакет?
- Из HCL? Нет, в HCL только желаемое описание.
- Из API AWS? Нет, в AWS миллион бакетов, и какой из них «ваш». Terraform не знает.
Ответ: из state-файла. После первого apply Terraform запомнил в terraform.tfstate:
«Ресурс aws_s3_bucket.demo → реальный бакет с именем my-bucket-12345, ARN такой-то, создан тогда-то».
Без этой записи Terraform каждый раз пытался бы создавать всё заново, и падал бы на «bucket already exists».
Где живёт state
По умолчанию, файл terraform.tfstate в той же директории, что HCL. Это называется local backend.
my-project/
├── main.tf
├── terraform.tfstate # ← вот он
└── terraform.tfstate.backup # копия с предыдущего apply
В реальных проектах state выносят в remote backend. S3, Terraform Cloud, и т.п. Чтобы команда могла работать с одним state'ом и не конфликтовать. См. tf-init-backends.
Что внутри
Это JSON. Можно посмотреть руками, но не редактируйте:
{"version": 4,
"terraform_version": "1.9.8",
"serial": 5,
"lineage": "abc-123-...",
"resources": [
{"mode": "managed",
"type": "aws_s3_bucket",
"name": "demo",
"instances": [
{ "attributes": {"id": "my-bucket-12345",
"bucket": "my-bucket-12345",
"arn": "arn:aws:s3:::my-bucket-12345",
"region": "us-east-1",
"tags": { "Owner": "student" }}
}
]
}
]
}
Ключевые поля:
serial, счётчик. Каждый apply увеличивает на 1. Для проверки актуальности.lineage, уникальный UUID этого state'а. Защита от случайной подмены файла.resources, массив всех ресурсов под управлением. Тип, имя, атрибуты.
Команды для работы со state
Сам файл руками не трогаем. Есть CLI:
# Список всех ресурсов в state
terraform state list
# Показать атрибуты одного ресурса
terraform state show aws_s3_bucket.demo
# Целиком state в JSON (для скриптов)
terraform show -json
Опасные (нужны в исключительных случаях):
# Удалить ресурс из state (НЕ из облака)
terraform state rm aws_s3_bucket.demo
# Переименовать ресурс в state
terraform state mv aws_s3_bucket.demo aws_s3_bucket.renamed
# Импортировать существующий ресурс в state (resource уже в HCL описан)
terraform import aws_s3_bucket.demo my-existing-bucket
Эти команды напрямую правят terraform.tfstate. Перед ними делайте бэкап файла.
Drift, расхождение state и реальности
State, это снапшот того, что Terraform думает. Реальность в облаке может уйти.
Примеры drift'а:
- Кто-то руками удалил бакет через AWS Console.
- Auto-scaling сам изменил
desired_capacity. - Включился ALB Auto-Scaling target.
При следующем plan Terraform читает state и сравнивает с HCL. Если реальность отличается от state, plan -refresh=true (default) сначала обновит state из API, потом покажет diff.
Это нормально, но требует внимания. См. tf-resource-lifecycle про ignore_changes для атрибутов, которые меняются вне Terraform.
Секреты в state
Это критично понять: state содержит все атрибуты, включая чувствительные.
- Пароль БД (
aws_db_instance.password): в state. - Токены, ключи, secret values, в state.
sensitive = trueмаскирует только в CLI-выводе. В JSON-файле, открытым текстом.
Поэтому:
- Local state в git, нельзя. Никогда. Даже если репозиторий приватный, рано или поздно утечёт.
- Local state на диске общего сервера, тоже плохо. Любой sudo-user прочитает.
- Remote backend с шифрованием, обязательно для продакшена. S3 + SSE-KMS, Terraform Cloud с шифрованием на стороне, и т.п.
Lock, защита от одновременного apply
Если два человека одновременно запустят apply на одном проекте, state может побиться. Решается locking:
- Local backend не имеет lock'а. Это риск даже на одном компьютере (открыли два терминала).
- S3 backend + DynamoDB для lock'а, стандарт. Один apply удерживает запись в DynamoDB, второй ждёт.
- Terraform Cloud делает lock автоматически.
При попытке параллельного apply вы увидите:
Error: Error acquiring the state lock
Lock Info:
ID: abc-123
Path: s3://my-bucket/terraform.tfstate
Operation: OperationTypeApply
Who: user@host
Created: 2026-05-20 14:00:00 +0000 UTC
Если знаете, что другой процесс мёртв, terraform force-unlock <lock-id>.
Подводные камни
-
Не редактируйте state руками. JSON выглядит простым, но Terraform проверяет внутренние инварианты (lineage, serial, hashes). Сломаете, будете долго чинить.
-
Не коммитьте state в git. Никогда. Даже local. Добавьте в
.gitignore:terraform.tfstate
terraform.tfstate.backup
.terraform/
.terraform.lock.hcl # ← а это, наоборот, коммитьте
-
State синхронизирован только с одной точкой времени. Между apply'ями он не «следит» за облаком. Если хотите свежие данные,
terraform refreshилиterraform plan(тоже делает refresh). -
state rmне удаляет ресурс в облаке. Только из state. Ресурс остаётся существовать «вне Terraform». Полезно при миграции, опасно при ошибке. -
state mvнужен при переименовании. Если в HCL поменяли"demo"на"main", безstate mvTerraform пересоздаст ресурс. Послеstate mv, только обновит запись, без обращения в облако. -
Lineage защищает от подмены, но можно сломать. Если случайно перетёрли state копией из другого проекта, lineage не совпадёт. Terraform откажется работать. Это защита, а не баг.