lesson ── terraform-beginner ── ~12 мин ── 3 шагов
Иногда нужно создать N одинаковых ресурсов: три бакета для разных
регионов, пять security-групп для разных портов. Дублировать
resource-блоки, больно и неустойчиво. Terraform умеет это через
count и for_each.
Эти два инструмента похожи, но имеют важное различие, которое всплывает при изменении списка. См. tf-count-for-each.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Создай main.tf:
resource "aws_s3_bucket" "many" {count = 3
bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}" tags = {Index = tostring(count.index)
}
}
resource "random_id" "suffix" {byte_length = 4
}
Здесь:
count = 3, создать три экземпляра.count.index, индекс текущего (0, 1, 2).tostring(count.index), преобразовать число в строку для тега.cd /home/student/tf-count
terraform init -input=false
terraform apply -auto-approve -input=false
В state увидишь:
aws_s3_bucket.many[0]
aws_s3_bucket.many[1]
aws_s3_bucket.many[2]
Если apply ругается «duplicate bucket name»: random_id.suffix должен подставиться. Проверь что random_id ресурс есть в HCL.
✓ Три бакета с индексами 0, 1, 2: count работает.
Добавь в main.tf ещё один ресурс:
resource "aws_s3_bucket" "regional" {for_each = toset(["us", "eu", "ap"])
bucket = "linuxlab-regional-${each.key}-${random_id.suffix.hex}" tags = {Region = each.key
}
}
Здесь:
for_each = toset([...]), создать для каждого элемента set.each.key, текущий ключ ("us", "eu" или "ap").each.value, то же самое для set; для map отличается.terraform apply -auto-approve
В state добавятся:
aws_s3_bucket.regional["us"]
aws_s3_bucket.regional["eu"]
aws_s3_bucket.regional["ap"]
Это не индексы, это именованные ключи. Гораздо стабильнее при изменениях списка.
toset обязательно: for_each не принимает обычный list, только set или map. Если убрать toset: упадёт.
✓ for_each создал три бакета по ключам us, eu, ap.
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 для полной матрицы.
Сейчас у тебя 6 бакетов: 3 через count, 3 через for_each. Удалим средний в каждом случае. Поправь HCL:
resource "aws_s3_bucket" "many" {count = 2 # было 3
bucket = "linuxlab-count-${count.index}-${random_id.suffix.hex}" tags = {Index = tostring(count.index)
}
}
resource "aws_s3_bucket" "regional" {for_each = toset(["us", "ap"]) # убрали "eu"
bucket = "linuxlab-regional-${each.key}-${random_id.suffix.hex}" tags = {Region = each.key
}
}
Запусти plan:
terraform plan
Что увидишь:
aws_s3_bucket.many[2]. Это последний по индексу, не средний.
Если бы тебе нужен был именно middle, count его не «понимает».aws_s3_bucket.regional["eu"]. Точно тот, что
ты убрал. us и ap не тронуты.В этом и есть главное различие: for_each стабильнее.
terraform apply -auto-approve
Если plan показывает destroy + create в count: это норма: count.index сдвигается. Для middle-удалений переходи на for_each.
✓ Удалили 2 бакета (по одному из каждого блока), осталось 4. for_each сделал это аккуратно.
Если ресурсы реально одинаковы и количество растёт/убывает
только с конца, count нормально. Если у каждого свой смысл
(регион, имя, окружение): for_each. Если сомневаешься, бери
for_each. Стоимость рефакторинга count → for_each болезненная:
нужны moved {} блоки или ручной state mv. Лучше сразу делать
правильно.
Ты создал три бакета через count (адресуются по индексу [0], [1], [2]) и три через for_each (адресуются по ключу ["us"], ["eu"], ["ap"]). Увидел что for_each стабильнее при изменении списка, он не сдвигает все остальные индексы.
команды
terraform state listувидеть индексы и ключи всех элементовterraform state show 'aws_s3_bucket.regional["us"]'одна штука из for_eachterraform plan -target='aws_s3_bucket.regional["eu"]'точечный plan для одного элементаконцепции