lesson ── terraform-intermediate ── ~15 мин ── 4 шагов
Многие AWS-ресурсы имеют подблоки, которые повторяются: lifecycle-правила
в S3, ingress/egress в security group, statement в IAM policy. Если их
два-три, пишут руками. Если число зависит от переменной, без
dynamic блока пришлось бы дублировать ресурсы.
В этом уроке сделаешь S3-бакет с переменным количеством lifecycle-правил. Один input, list of objects, превращается в N подблоков ресурса.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Создай main.tf в /home/student/tf-dynamic/:
resource "random_id" "suffix" {byte_length = 4
}
variable "lifecycle_rules" { type = list(object({id = string
enabled = bool
prefix = string
expiration_days = number
}))
default = [
{id = "archive-old-logs"
enabled = true
prefix = "logs/"
expiration_days = 30
},
{id = "remove-tmp"
enabled = true
prefix = "tmp/"
expiration_days = 7
},
]
}
Тип, list(object(...)) с явной схемой каждого элемента. Это даёт
Terraform'у возможность проверить на этапе plan что все элементы
имеют нужные поля.
✓ Структура входа описана. Теперь: ресурс с dynamic блоком.
Добавь в main.tf:
resource "aws_s3_bucket" "demo" { bucket = "linuxlab-dynamic-${random_id.suffix.hex}"}
resource "aws_s3_bucket_lifecycle_configuration" "demo" {bucket = aws_s3_bucket.demo.id
dynamic "rule" {for_each = var.lifecycle_rules
content {id = rule.value.id
status = rule.value.enabled ? "Enabled" : "Disabled"
filter {prefix = rule.value.prefix
}
expiration {days = rule.value.expiration_days
}
}
}
}
Разбор:
dynamic "rule", название подблока в родительском ресурсе. Здесь
aws_s3_bucket_lifecycle_configuration принимает несколько rule { }.for_each = var.lifecycle_rules, итерируем по списку.content { }, содержимое одного подблока.rule.value, текущий элемент. rule.value.id, rule.value.prefix.rule.key тоже есть, но для list это индекс (0, 1, 2); для map,
ключ.✓ Dynamic блок написан. Init + apply.
cd /home/student/tf-dynamic
terraform init
terraform plan
В plan'е увидишь развёрнутые два правила внутри aws_s3_bucket_lifecycle_configuration:
+ rule {+ id = "archive-old-logs"
+ status = "Enabled"
+ filter { prefix = "logs/" } + expiration { days = 30 }}
+ rule {+ id = "remove-tmp"
+ status = "Enabled"
+ filter { prefix = "tmp/" } + expiration { days = 7 }}
dynamic раскрылся в два реальных подблока. На уровне ресурса
результат идентичен ручному написанию двух rule { } блоков.
terraform apply -auto-approve
✓ Lifecycle configuration создан с двумя правилами.
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 для полной матрицы.
Поменяй default переменной, сделай пустым:
variable "lifecycle_rules" { type = list(object({id = string
enabled = bool
prefix = string
expiration_days = number
}))
default = []
}
Запусти plan:
terraform plan
Это типичный паттерн: dynamic с пустым list = «правил нет». Особенно полезно в модулях: если пользователь модуля не передал правила, ресурс создаётся без них, не падает.
Можешь применить (apply) или вернуть прежнее значение через
редактор, это сейчас не критично.
Если хочешь вернуть как было: поправь default обратно на список с двумя элементами.
✓ Пустой список: валидное состояние. dynamic переживает edge case.
Иногда dynamic блок можно заменить сразу for-выражением:
# вариант с dynamic
resource "aws_security_group" "web" { dynamic "ingress" {for_each = var.ports
content {from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
}
}
}
vs
# вариант с for внутри атрибута (если ресурс принимает list атрибута)
#, некоторые ресурсы умеют так, например aws_vpc_security_group_ingress_rule
# принимает список напрямую.
Не каждый AWS-ресурс это позволяет. Атрибуты-списки доступны где
атрибуты, не подблоки. Если в HCL пишется xxx { ... } без =,
это подблок, нужно dynamic.
Правило большого пальца:
Если ресурс ругается «expected block, got expression», попробуй dynamic. Если «expected expression», for / list literal.
dynamic "X" { for_each = ..., content { ... } } разворачивает в N
блоков X внутри родительского ресурса. Используется когда количество
подблоков зависит от input'а. Не путать с for_each на самом ресурсе,
dynamic это внутри одного ресурса, for_each создаёт N ресурсов.
команды
terraform planвидишь развёрнутые подблоки в выводеterraform consoleпроверить структуру var до того как воткнул в dynamicконцепции