# Большой state, иерархия, blast-radius, naming _Advanced · TerraformLab Knowledge Base_ **TL;DR:** Один state на 5000 ресурсов = боль: refresh минуты, lock-contention, любой apply трогает всё. Решение, иерархия state'ов: network/iam отдельно, apps отдельно, между ними `terraform_remote_state`. Blast-radius, критерий разделения. Naming для бакетов и lock-таблиц предсказуемая структура. ## Признаки «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 ```hcl # 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: ```hcl 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 └── ... ``` Иерархия: `//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: ```hcl 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: 1. **Создай новый state** `network/terraform.tfstate` (пустой). 2. **`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 ``` 3. **`removed` блоки** в monolith HCL: ресурсы переехали, не нужно destroy. 4. **`terraform_remote_state`** в monolith: читает outputs из новой network. 5. **Apply обоих**: plan'ы должны быть `no changes`. Подробнее в [tf-state-manipulation](/terraform/kb/tf-state-manipulation.md) и [tf-blue-green-migration](/terraform/kb/tf-blue-green-migration.md). ## Когда 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, да; вручную, порядок запоминать. ## Команды ```bash terraform state pull > local-state.json ``` Скачать state локально, для inspect или backup. ```bash terraform state list | wc -l ``` Сколько ресурсов в state. > 500, задумайся о split'е. ```bash aws s3api list-objects --bucket company-tf-state --prefix prod/ ``` Все state-keys в prefix prod. Сводка структуры. ```bash terraform state mv -state-out=/tmp/X.tfstate aws_vpc.main aws_vpc.main ``` Вынести ресурс в другой state-файл. ## См. также - [Remote state в S3: бакет, DynamoDB lock, encryption](/terraform/kb/tf-remote-backend-s3.md) - [State: память Terraform о созданном](/terraform/kb/tf-state.md) - [Terragrunt, DRY-обёртка над Terraform](/terraform/kb/tf-terragrunt.md) - [DAG в Terraform, как строится граф зависимостей](/terraform/kb/tf-dag-internals.md)