lesson ── terraform-garden ── ~18 мин ── 3 шагов
В ~/tf-garden лежит готовый HCL. terraform init пройдёт, plan, нет:
Cycle Error. Один ресурс ссылается на другой, тот на третий, третий
обратно на первый. Без распутывания граф не строится, plan не запустится.
Твоя задача, найти цикл, понять зачем он там, разорвать его так, чтобы смысл остался, и доказать plan'ом что граф стал DAG.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
Войди в директорию, проинициализируй и попробуй сделать plan.
cd /home/student/tf-garden
terraform init
terraform plan 2>&1 | tee /tmp/cycle.log
В cycle.log будет блок вида:
Error: Cycle: random_id.suffix, aws_s3_bucket.demo, aws_s3_bucket_policy.demo
Запиши себе три имени ресурсов. Это твой цикл.
✓ Цикл подтверждён. Теперь, почему он там.
Самый частый сценарий: keepers или triggers в utility-ресурсе
ссылается на ресурс, который сам зависит от utility-ресурса.
Тут random_id.keepers.bucket_arn = aws_s3_bucket.demo.arn,
а aws_s3_bucket.demo.bucket, garden-cycle-${random_id.suffix.hex}.
Получили зависимость в обе стороны.
Второй частый сценарий: depends_on руками поверх implicit-deps.
Terraform строит граф автоматически, если ты пишешь depends_on
туда же, можно получить «А зависит от B, B зависит от A через
ручной depends_on».
Решение: убери обратную зависимость random_id.keepers (она бесполезна,
потому что random_id и так пересоздаётся через -replace), и вынеси
условие из политики в locals:
locals {suffix_hex = random_id.suffix.hex
}
resource "random_id" "suffix" {byte_length = 4
}
resource "aws_s3_bucket" "demo" { bucket = "garden-cycle-${local.suffix_hex}"}
resource "aws_s3_bucket_policy" "demo" {bucket = aws_s3_bucket.demo.id
policy = jsonencode({Version = "2012-10-17"
Statement = [{Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.demo.arn}/*" Condition = { StringEquals = {"aws:RequestTag/SuffixHex" = local.suffix_hex
}
}
}]
})
}
Положи это в ~/tf-garden/main.tf поверх старого. После, проверь
что блока keepers нет.
В новом main.tf не должно быть слова `keepers`. Не пытайся «починить» политику добавлением depends_on, это не туда.
✓ HCL переписан. Проверим граф.
cd /home/student/tf-garden
terraform plan -out=plan.tfplan
Теперь plan должен пройти: «Plan: 3 to add, 0 to change, 0 to destroy». Apply в этом уроке не обязателен, главное что граф собрался.
Если plan всё ещё ругается, посмотри terraform graph -draw-cycles,
может остался ещё один цикл, который ты не заметил.
✓ Plan собрался, граф стал DAG. Цикл распутан.
OpenTofu повторяет ту же логику Cycle-проверки 1:1, те же сообщения, тот же graph-командой. Это часть наследия HCL, не отличие реализаций. Полная статья про различия и совместимость, tf-opentofu-parity.
Cycle, это всегда дизайн-ошибка, не баг tf. Видишь цикл? Кто-то написал зависимости в две стороны. random_id.keepers ссылающийся на ресурс, который зависит от того же random_id, классика. Решение: убрать обратную ссылку и переложить логику в data-block или local, или признать что зависимость в одну сторону.
команды
terraform graph -draw-cycles | dot -Tsvg > /tmp/g.svgграфическая подсветка цикла; если dot не доступен, читай ASCII-выводterraform graph -type=plan-destroyещё одна оптика на тот же граф; иногда видно лучшеконцепции