linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/lessons/tf-intermediate-03-module-for-each

lesson ── terraform-intermediate ── ~15 мин ── 4 шагов

for_each над модулем. N экземпляров одним блоком

Один module "logs" создал один бакет. Что если нужно три бакета, для логов разных уровней (debug/info/error), у каждого свой versioning? Можно написать три module блока с copy-paste. Можно один module с for_each = var.buckets. Второе, лучше.

Этот урок про for_each поверх модуля: одна декларация, N экземпляров, стабильные ключи в state.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Возьми модуль из урока 1

    Используем тот же audited-bucket модуль, что писали в первом уроке. Скопируем его сюда:

    bash
    cd /home/student/tf-foreach
    mkdir -p modules/audited-bucket
    cat > modules/audited-bucket/variables.tf <<'EOF'
    variable "name" {
      type = string
    }
    variable "versioning_enabled" {
      type    = bool
      default = false
    }
    variable "tags" {
      type    = map(string)
      default = {}
    }
    EOF
    cat > modules/audited-bucket/main.tf <<'EOF'
    resource "aws_s3_bucket" "this" {
      bucket = var.name
      tags   = merge({ Module = "audited-bucket" }, var.tags)
    }
    resource "aws_s3_bucket_versioning" "this" {
      bucket = aws_s3_bucket.this.id
      versioning_configuration {
        status = var.versioning_enabled ? "Enabled" : "Suspended"
      }
    }
    EOF
    cat > modules/audited-bucket/outputs.tf <<'EOF'
    output "arn" {
      value = aws_s3_bucket.this.arn
    }
    EOF

    Можно копи-пастить блоками. Главное, структура и контракт.

    ✓ Модуль на месте. Теперь: вызов с for_each.

  2. 02

    Опиши map с параметрами для каждого бакета

    Создай main.tf в /home/student/tf-foreach/:

    hcl
    resource "random_id" "suffix" {
      byte_length = 4
    }
    variable "log_buckets" {
      type = map(object({
        versioning = bool
      }))
      default = {
        debug = { versioning = false }
        info  = { versioning = false }
        error = { versioning = true }
      }
    }
    module "log" {
      source   = "./modules/audited-bucket"
      for_each = var.log_buckets
      name               = "linuxlab-logs-${each.key}-${random_id.suffix.hex}"
      versioning_enabled = each.value.versioning
      tags = {
        Owner = "student"
        Level = each.key
      }
    }
    output "log_arns" {
      value = { for k, m in module.log : k => m.arn }
    }

    Главное:

    • for_each = var.log_buckets, это map, не set. Ключи, это твои имена (debug/info/error), значения, параметры.
    • each.key, текущий ключ ("debug"), each.value, текущее значение (object с versioning).
    • В output { for k, m in module.log : k => m.arn }: map-comprehension: собираем {debug: <arn>, info: <arn>, error: <arn>}.

    ✓ Декларация написана. Теперь init и apply.

  3. 03

    init + apply: должно создать 3 экземпляра

    bash
    cd /home/student/tf-foreach
    terraform init
    terraform apply -auto-approve

    Plan покажет «Plan: 7 to add», три бакета, три versioning'а, один random_id.

    Apply должен пройти. После, посмотри state:

    bash
    terraform state list

    Должно быть похожее:

    module.log["debug"].aws_s3_bucket.this
    module.log["debug"].aws_s3_bucket_versioning.this
    module.log["error"].aws_s3_bucket.this
    module.log["error"].aws_s3_bucket_versioning.this
    module.log["info"].aws_s3_bucket.this
    module.log["info"].aws_s3_bucket_versioning.this
    random_id.suffix

    Ключ, это твоё имя ("debug", "info", "error"). Не индекс. Это ключевое отличие от count.

    ✓ Три бакета созданы. Один HCL-блок, три инстанса.

    То же самое на OpenTofu

    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 для полной матрицы.

    • → OpenTofu parity
  4. 04

    Проверь стабильность ключей: удали один уровень

    Это главная причина почему for_each лучше count для модулей.

    Поменяй default в var.log_buckets, убери debug:

    hcl
    default = {
      info  = { versioning = false }
      error = { versioning = true }
    }

    Запусти plan:

    bash
    terraform plan

    Plan должен показать только удаление module.log["debug"]:

    # module.log["debug"].aws_s3_bucket.this will be destroyed
    # module.log["debug"].aws_s3_bucket_versioning.this will be destroyed

    info и error не трогаются. Их адреса не зависят от наличия debug, они привязаны к ключам, не к индексам.

    С count это было бы destroy на всех, recreate на двух, массовая работа на пустом месте. См. tf-count-for-each про count vs for_each.

    Apply (необязательно, можно вернуть debug через git checkout):

    bash
    terraform apply -auto-approve

    ✓ После удаления debug: info и error остались нетронутыми. Это стабильность ключей.

    for_each над модулем vs внутри модуля

    Две разные техники:

    for_each над модулем (этот урок):

    hcl
    module "log" {
      for_each = var.log_buckets
      source   = "./modules/audited-bucket"
      # ...
    }

    N экземпляров модуля. Каждый, своя «капсула». Хорошо когда каждый instance изолирован.

    for_each внутри модуля:

    hcl
    # modules/audited-bucket/main.tf
    resource "aws_s3_bucket" "this" {
      for_each = var.bucket_configs   # map передан в модуль
      # ...
    }

    Один модуль управляет N бакетами. Подходит когда «гроздь» бакетов, часть единого контракта (например, у tracker'а кампании всегда 3 бакета).

    Когда что:

    • Independent things → for_each над модулем.
    • Tightly coupled group → for_each внутри модуля.

    Если не уверен, for_each над модулем (так чаще правильно).

    • → Композиция модулей
    • → count vs for_each

Что ты узнал

for_each поверх module блока (TF 0.13+) принимает map или set. Каждый ключ → отдельный экземпляр модуля. Адрес в state, module.X["key"]. Главное преимущество над count, стабильность ключей при удалении.

команды

  • terraform state listвидишь module.X["key"] для каждого экземпляра
  • terraform plan -target='module.X["key1"]'точечный plan одного экземпляра

концепции

  • · for_each map → стабильные ключи; удаление одного не пересоздаёт остальных
  • · Внутри модуля доступа к each.key нет: оно остаётся в root
  • · Все экземпляры используют одинаковый source: невозможно «один из git, другой local»

← предыдущий

Troubleshooting Garden: import показывает на пустоту

следующий →

Нативные тесты, .tftest.hcl и assert

Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.