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-beginner-12-debugging

lesson ── terraform-beginner ── ~15 мин ── 5 шагов

Дебаг. TF_LOG, граф, чтение чужой ошибки

Терраформ молчалив по умолчанию. Apply упал, короткое сообщение. План странный, догадывайся. Это норма для CLI, но за этим есть полные диагностические логи и инструменты, которые их читают.

В этом уроке поработаешь с тремя сценариями: сломанная зависимость (cycle), непонятная ошибка от провайдера (нужны TF_LOG), и непредсказуемый план (нужен graph). К концу, будешь знать что делать когда «у меня что-то не работает и я не понимаю что».

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

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

запустить sandbox →

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

Шаги

  1. 01

    Создай конфигурацию с циклом

    Цикл, это когда A зависит от B, а B от A. Граф Terraform, ациклический по контракту, такой код упадёт на plan.

    Создай в ~/tf-debug файл main.tf:

    hcl
    resource "aws_s3_bucket" "first" {
      bucket = "linuxlab-cycle-first-${random_id.suffix.hex}"
      tags = {
        PairedWith = aws_s3_bucket.second.bucket
      }
    }
    resource "aws_s3_bucket" "second" {
      bucket = "linuxlab-cycle-second-${random_id.suffix.hex}"
      tags = {
        PairedWith = aws_s3_bucket.first.bucket
      }
    }
    resource "random_id" "suffix" {
      byte_length = 4
    }

    first ссылается на second.bucket, second, на first.bucket. Кто из них создавать первым? Никто не знает.

    Запусти plan:

    bash
    cd /home/student/tf-debug
    terraform init -input=false
    terraform plan

    Получишь:

    Error: Cycle: aws_s3_bucket.first, aws_s3_bucket.second

    Это защита, не баг. Terraform не угадывает, он отказывается.

    подсказка

    Если не получаешь Cycle: проверь что обе ссылки на месте (grep PairedWith main.tf).

    ✓ Cycle поймана. Теперь визуализируем граф чтобы увидеть проблему.

  2. 02

    Посмотри граф зависимостей

    Terraform умеет напечатать граф в формате Graphviz dot:

    bash
    terraform graph

    Это текст. Чтобы увидеть картинку. Нужно graphviz (dot команда). В sandbox он установлен:

    bash
    terraform graph | dot -Tpng > /tmp/graph.png

    Файл /tmp/graph.png содержит визуализацию. В нашем случае ты увидишь две стрелки между aws_s3_bucket.first и aws_s3_bucket.second, туда и обратно. Это и есть цикл.

    С TF 1.4+ есть подсветка циклов прямо в команде:

    bash
    terraform graph -draw-cycles

    Узлы в циклах помечаются красным в выводе dot.

    Подробнее, tf-graph.

    подсказка

    Если `dot: command not found`: graphviz не установлен. В sandbox должен быть, но если падает: пропусти этот шаг и иди дальше.

    ✓ Граф напечатан. Теперь починим цикл.

  3. 03

    Разорви цикл: добавь промежуточный ресурс

    Самый чистый способ разорвать цикл, вынести зависимое значение в третий ресурс или в local. Замени main.tf на:

    hcl
    resource "random_id" "suffix" {
      byte_length = 4
    }
    locals {
      first_name  = "linuxlab-cycle-first-${random_id.suffix.hex}"
      second_name = "linuxlab-cycle-second-${random_id.suffix.hex}"
    }
    resource "aws_s3_bucket" "first" {
      bucket = local.first_name
      tags = {
        PairedWith = local.second_name
      }
    }
    resource "aws_s3_bucket" "second" {
      bucket = local.second_name
      tags = {
        PairedWith = local.first_name
      }
    }

    Теперь оба бакета зависят от random_id, но не друг от друга. Имена парных бакетов в тегах берутся из locals, это просто строки, не атрибуты ресурсов.

    bash
    terraform plan
    terraform apply -auto-approve

    Plan покажет 2 to add (random_id уже создан, или будет), apply пройдёт.

    подсказка

    Не забудь убрать оригинальные перекрёстные ссылки `aws_s3_bucket.X.bucket`: они и были источником цикла.

    ✓ Цикл разорван, оба бакета созданы. Это типичный паттерн: вынести общее имя в local.

  4. 04

    Сделай ошибку и поймай её через TF_LOG

    Подкинь намеренную ошибку, попробуй создать бакет с невалидным именем. S3 не разрешает заглавные буквы и спецсимволы.

    Создай файл bad.tf рядом:

    hcl
    resource "aws_s3_bucket" "broken" {
      bucket = "LinuxLab-INVALID-${random_id.suffix.hex}"
      # ^^^^^^^^^^^^^^^^^^^^^^^^ заглавные буквы недопустимы в S3
    }

    Запусти apply с обычным выводом:

    bash
    terraform apply -auto-approve

    Получишь короткое сообщение об ошибке от провайдера, в духе "InvalidBucketName" или "BucketAlreadyExists" (LocalStack может выдать чуть другое). Что именно не так, не очевидно.

    Теперь то же самое, но с TF_LOG=DEBUG и сохранением:

    bash
    TF_LOG=DEBUG terraform apply -auto-approve 2>&1 | tee /tmp/tf.log

    Файл /tmp/tf.log содержит весь диагностический вывод. Ищем HTTP-ответ от облака:

    bash
    grep -A 5 'HTTP/1.1 4' /tmp/tf.log | head -30

    Увидишь оригинальное сообщение S3 API, обычно с XML-блоком <Error><Code>...</Code><Message>...</Message></Error>. Этот Message понятнее, чем то что Terraform показал в обычном выводе.

    См. tf-log-debug про уровни логирования и фильтрацию.

    подсказка

    Если grep ничего не находит. TF_LOG не сработал. Проверь что переменная установлена: `echo $TF_LOG`. Должно быть `DEBUG`.

    ✓ TF_LOG включился, дебаг-вывод сохранён. В реальной работе это первый шаг при странных ошибках.

    То же самое на 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
  5. 05

    Убери сломанное и оставь чистый state

    Удали bad.tf:

    bash
    rm /home/student/tf-debug/bad.tf

    Применим. Terraform увидит что broken нет в HCL и снесёт его из state (если он попал туда; обычно при ошибке создания не попадает):

    bash
    terraform apply -auto-approve
    terraform plan -detailed-exitcode
    echo "exit: $?"

    Должно быть exit: 0. Это главный инвариант: после apply повторный plan чистый.

    Закрепили: каждый раз когда apply падает или plan непонятный, TF_LOG=DEBUG, графа, поиск 4xx в логах. Это рутина, не героизм.

    подсказка

    Если plan не чистый: посмотри какие изменения он показывает (`terraform plan` без флагов). Скорее всего что-то осталось от broken.

    ✓ State и HCL совпадают. Beginner-трек закрыт. Можешь возвращаться, можешь ждать intermediate.

    Полный workflow дебага

    Когда что-то не работает и непонятно почему:

    1. terraform validate, синтаксис ли это? Опечатка?
    2. terraform fmt, может стиль косвенно мешает (редко, но бывает).
    3. terraform plan -detailed-exitcode, есть ли вообще изменения? Может ты уже всё применил.
    4. terraform graph -draw-cycles, может цикл?
    5. TF_LOG=DEBUG terraform apply 2>&1 | tee /tmp/tf.log, полный дебаг-лог.
    6. grep -A 5 'HTTP/1.1 4' /tmp/tf.log, что говорит облако?
    7. terraform console, какие реальные значения у переменных и выражений?
    8. Поиск ошибки в tf-common-errors, может она известная.

    Этот порядок, от дешёвого к дорогому. Не лезь сразу в TF_LOG если ошибка, простая опечатка которую validate бы нашёл за секунду.

    • → TF_LOG целиком
    • → Типичные ошибки
    • → Как читать plan

Что ты узнал

Три инструмента дебага. TF_LOG=DEBUG, для непонятных ошибок от провайдера. terraform graph, для разбора зависимостей и циклов. Чтение HTTP-запросов в логах, для случаев, когда сообщение Terraform скрывает суть, а облако даёт оригинальную ошибку.

команды

  • TF_LOG=DEBUG terraform apply 2>&1 | tee tf.logлог + сохранение для разбора
  • terraform graph | dot -Tpng > graph.pngвизуализация графа зависимостей
  • terraform graph -draw-cyclesTF 1.4+: подсветить циклы
  • grep -A 3 'HTTP/1.1 4' tf.logответы 4xx от облака: обычно понятнее

концепции

  • · TF_LOG=DEBUG включает диагностику без шума TRACE
  • · Cycle в графе: невалидный DAG, починка через разрыв ссылки
  • · Облако часто говорит понятнее, чем Terraform: смотри RESPONSE 4xx в логах

← предыдущий

Утилитарные провайдеры: random, time, archive, external

следующий →

Multi-env: workspaces vs директории

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