lesson ── terraform-advanced ── ~16 мин ── 6 шагов
Один большой state с 1000 ресурсами, это lock-contention, медленный
refresh, риск catastrophic-failure. Решение, split на иерархию:
network, apps, и т.д. Между ними terraform_remote_state. На этом
уроке: создашь монолит, поделишь его на network и apps, увидишь
как они общаются.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/scale/monolith
cat > main.tf <<'EOF'
resource "aws_vpc" "main" {cidr_block = "10.0.0.0/16"
tags = { Name = "scale-vpc" }}
resource "aws_subnet" "private" {count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = "us-east-1${["a", "b"][count.index]}" tags = { Name = "private-${count.index}" }}
resource "aws_s3_bucket" "app_logs" {bucket = "scale-app-logs"
}
resource "aws_s3_bucket" "app_data" {bucket = "scale-app-data"
}
EOF
terraform init -no-color > /dev/null
terraform apply -auto-approve -no-color > /dev/null
terraform state list
Видишь VPC, 2 subnets, 2 buckets, всё в одном state'е.
✓ Монолит создан. Сейчас разделим.
Стратегия, создать новый state, перенести в него ресурсы:
cd /home/student/scale/network
cat > main.tf <<'EOF'
resource "aws_vpc" "main" {cidr_block = "10.0.0.0/16"
tags = { Name = "scale-vpc" }}
resource "aws_subnet" "private" {count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, count.index)
availability_zone = "us-east-1${["a", "b"][count.index]}" tags = { Name = "private-${count.index}" }}
output "vpc_id" {value = aws_vpc.main.id
}
output "private_subnet_ids" {value = aws_subnet.private[*].id
}
EOF
terraform init -no-color > /dev/null
Теперь перенесём ресурсы, state pull из монолита, state push
сюда:
# из монолита достаём только network ресурсы
cd ../monolith
terraform state pull > /tmp/full.tfstate
terraform state mv -state-out=/tmp/network.tfstate aws_vpc.main aws_vpc.main
terraform state mv -state-out=/tmp/network.tfstate 'aws_subnet.private[0]' 'aws_subnet.private[0]'
terraform state mv -state-out=/tmp/network.tfstate 'aws_subnet.private[1]' 'aws_subnet.private[1]'
terraform state list
# импортируем в network-state
cd ../network
terraform state push -force /tmp/network.tfstate
terraform state list
Network-state теперь содержит VPC + 2 subnet'а. Монолит, только bucket'ы.
✓ Network вынесен. State'ов теперь два.
cd /home/student/scale/apps
cat > main.tf <<'EOF'
data "terraform_remote_state" "network" {backend = "local"
config = {path = "../network/terraform.tfstate"
}
}
resource "aws_s3_bucket" "app_logs" {bucket = "scale-app-logs"
tags = {VPC = data.terraform_remote_state.network.outputs.vpc_id
}
}
resource "aws_s3_bucket" "app_data" {bucket = "scale-app-data"
tags = {SubnetCount = length(data.terraform_remote_state.network.outputs.private_subnet_ids)
}
}
EOF
terraform init -no-color > /dev/null
Перенесём buckets из монолита:
cd ../monolith
terraform state mv -state-out=/tmp/apps.tfstate aws_s3_bucket.app_logs aws_s3_bucket.app_logs
terraform state mv -state-out=/tmp/apps.tfstate aws_s3_bucket.app_data aws_s3_bucket.app_data
terraform state list
cd ../apps
terraform state push -force /tmp/apps.tfstate
terraform state list
Apps-state, 2 bucket'а. Monolith, пустой.
✓ Apps читает network через remote_state. Монолит разобран.
cd /home/student/scale/apps
terraform plan -no-color 2>&1 | tail -10
Apps plan показывает что bucket'ы хотят добавить тег с
VPC = <vpc-id>, взятый из network-state.
Применяем:
terraform apply -auto-approve -no-color > /dev/null
terraform state show aws_s3_bucket.app_logs | grep -E "VPC|tags"
Тег есть, vpc-id настоящий, apps реально прочитал output из network-state.
✓ Cross-state ссылка работает. Outputs network доступны в apps.
Сценарий: добавляем ещё один bucket в apps. Network не трогается.
cd /home/student/scale/apps
cat >> main.tf <<'EOF'
resource "aws_s3_bucket" "metrics" {bucket = "scale-metrics"
}
EOF
terraform plan -no-color 2>&1 | tail -10
terraform apply -auto-approve -no-color > /dev/null
# network-state не тронули
cd ../network
terraform plan -no-color 2>&1 | tail -5
Apps-plan показывает +1 ресурс. Network-plan, No changes. Изоляция
работает.
На большом проекте это значит: PR в apps не блокирует network-PR'ы, lock на разных state'ах разные, blast-radius ограничен.
✓ Изоляция доказана. Apps меняется без касания network.
OpenTofu держит CLI и state совместимыми с Terraform по командам
этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе
сделай backup state и прогон на feature-branch - расхождения
концентрируются в новых фичах (variables в backend,
state-encryption, OCI registry-backed модули). См.
tf-opentofu-parity для полной матрицы.
Симулируем разрушение apps-state'а (не делай так в prod без бэкапа):
cd /home/student/scale/apps
cp terraform.tfstate /tmp/apps-backup.tfstate
echo '{"corrupt": true}' > terraform.tfstateset +e
terraform plan -no-color 2>&1 | tail -5
code=$?
set -e
echo "apps plan exit: $code"
# восстанавливаем
cp /tmp/apps-backup.tfstate terraform.tfstate
Apps state corrupt, plan падает. Но network-state цел:
cd ../network
terraform plan -no-color 2>&1 | tail -5
echo "network plan ok"
VPC и subnets продолжают работать. Это и есть изолированный blast-radius, один stack может сгореть, остальное живёт.
✓ Network защищён от поломок apps. Это и есть blast-radius isolation.
Разделение, не всегда win. Накладные расходы:
Не разделять если:
Делать раздел осознанно, когда симптомы (медленный plan, lock-contention, страх перед apply) появились. Не «на будущее».
См. tf-large-scale-state.
terraform_remote_state, data-source, читает outputs из другого
state-файла. Только outputs видны cross-state, внутренние ресурсы
и locals нет. Каждый stack, свой backend-key, свой lock.
команды
terraform_remote_state.network.outputs.Xссылка на output из другого state'а.terraform state listчто в текущем state. Один state ≠ всё.terraform state pull > backup.jsonбекап перед split-операцией.концепции