Признаки «state слишком большой»
terraform planзанимает > 60 секунд (refresh-фаза).- На apply одного маленького изменения lock держится 5+ минут.
- 2+ команды дерутся за один lock.
- PR ревьювится 3 дня, никто не понимает, что в plan'е, потому что он на 2000 строк.
Это сигналы для разбиения state'а.
Blast-radius, главный критерий
Что упадёт если этот state corrupt'нется или его потеряют?
| Что в state | Blast-radius |
|---|---|
| Network (VPC, subnets) | Все приложения этого VPC. Катастрофа. |
| IAM (roles, policies) | Cross-account работа. Катастрофа. |
| One service app | Только этот сервис. Recoverable. |
| Dev-environment | Только dev. Annoying. |
Разделение по blast-radius:
state-account/ # account-level (org units, base IAM), мало меняется
state-network/ # VPC, subnets, transit gateway, редкие changes
state-platform/ # shared services (logging, monitoring)
state-apps/
├── web/
├── api/
└── worker/
Каждый в своём S3-bucket-key, со своим lock.
Cross-state references
# state-apps/web/main.tf
data "terraform_remote_state" "network" {backend = "s3"
config = {bucket = "company-tf-state"
key = "state-network/terraform.tfstate"
region = "us-east-1"
}
}
resource "aws_instance" "web" {subnet_id = data.terraform_remote_state.network.outputs.public_subnet_ids[0]
# ...
}
state-network должен иметь outputs:
output "public_subnet_ids" {value = aws_subnet.public[*].id
}
Web-state читает их. Только outputs видны cross-state, внутренние ресурсы и locals не доступны.
Это создаёт хрупкость: rename output'а в network = ломает все читающие state'ы. Версионируй outputs, не переименовывай легко.
Naming convention
Анти-паттерн: my-state-bucket/terraform.tfstate для всего.
Хорошо:
s3://company-tf-state/
├── prod/network/terraform.tfstate
├── prod/iam/terraform.tfstate
├── prod/apps/web/terraform.tfstate
├── prod/apps/api/terraform.tfstate
├── stage/network/terraform.tfstate
├── stage/iam/terraform.tfstate
└── ...
Иерархия: <env>/<component>/terraform.tfstate. Префикс env'а
первый, IAM-policy легко разрешает «доступ только в prod/*».
DynamoDB lock-table:
- Одна таблица на account, разные partition-keys для разных state'ов.
- Или таблица per env (для жёсткой изоляции). Просто, на 100 state'ов становится много таблиц.
Стандарт, одна таблица, partition-key = LockID, Terraform сам генерит. Конфликта нет.
Hierarchy в IAM
Cross-state references требуют IAM-доступа. Web-app's role должна читать network-state:
resource "aws_iam_role_policy" "web_read_network_state" {role = aws_iam_role.web_tf_runner.name
policy = jsonencode({ Statement = [{Effect = "Allow"
Action = ["s3:GetObject"]
Resource = ["arn:aws:s3:::company-tf-state/prod/network/terraform.tfstate"]
}]
})
}
Web НЕ должен иметь права на network-state-write. Это разделение иначе compromised web-CI может сломать VPC.
Split-стратегии
Когда монолит state большой, разделить. Способы:
1. По функции (layer)
iam/, network/, data/, apps/
Network редко меняется, apps часто. Lock-contention падает.
2. По environment
dev/, stage/, prod/
Не зависят друг от друга. Подходит когда env'ы изолированы.
3. По tenant
tenants/customer-A/, tenants/customer-B/, ...
SaaS-инфра с per-customer ресурсами. Каждый tenant, свой state.
Обычно комбинация: prod/network/, prod/tenant-X/, stage/network/,
и т.д. Три измерения: env × layer × tenant.
Split-миграция
Допустим, всё в monolith/terraform.tfstate. Хочешь вынести network:
-
Создай новый state
network/terraform.tfstate(пустой). -
terraform state mvс одного state в другой черезstate pull/state push(нативного cross-state mv нет, нужен манёвр):bash# из monolith
terraform state mv -state-out=/tmp/network.tfstate aws_vpc.main aws_vpc.main
# ... все network-ресурсы
# в network-state
terraform state push /tmp/network.tfstate
-
removedблоки в monolith HCL: ресурсы переехали, не нужно destroy. -
terraform_remote_stateв monolith: читает outputs из новой network. -
Apply обоих: plan'ы должны быть
no changes.
Подробнее в tf-state-manipulation и tf-blue-green-migration.
Когда split, не нужен
- Меньше 200 ресурсов в state.
- Один env, одна команда, нет lock-contention.
- Plan < 30s, apply разумный.
Преждевременный split добавляет orchestration-cost (terragrunt или ручной shell-script для apply'я в правильном порядке) и cross-state versioning.
Подводные камни
-
Cross-state coupling, хрупкое. Output удалили в network → web-state ломается на следующем apply. Версионируй outputs (
output "vpc_id_v2"). -
terraform_remote_stateгонит data на каждом refresh. Большой state- много reader'ов = много S3 GET'ов. Performance issue на 50+ reader'ах.
-
Lock-table должен быть рядом. DynamoDB-lock в us-east-1, state в eu-west-1, каждый lock-acquire через cross-region call. Замедление.
-
Backup восстановить сложнее. На один монолит, один state-restore. На 30 state'ов, 30 restore'ов, в правильном порядке.
-
Permissions для cross-state. IAM-роль каждого reader'а должна иметь
s3:GetObjectна конкретные state-keys. Без удачного naming-конвенции, police IAM-policy plate of spaghetti. -
Импорт обратно в монолит, дорого. Раз разделил, обратно объединять через
state mv/push, операция на часы. Делать разделение осознанно. -
Apply-orchestration. Если network должен applе'ться до apps, кто-то этим orchestrates. Terragrunt, да; вручную, порядок запоминать.