Зачем нужен граф
Terraform, декларативный. Вы пишете «должен быть бакет, должен быть EC2 с user_data ссылающимся на этот бакет», а Terraform сам понимает порядок: сначала бакет, потом EC2.
Этот «сам понимает», внутри directed acyclic graph (DAG),
направленного ациклического графа. Каждый ресурс, узел. Каждая
ссылка aws_s3_bucket.demo.arn в коде другого ресурса, стрелка от
потребителя к источнику. Terraform строит этот граф, ищет topological
sort и применяет ресурсы в этом порядке.
terraform graph показывает этот граф. Это окно во внутренности
Terraform.
Базовый вывод
cd ~/myproject
terraform graph
В stdout, текст в формате Graphviz dot:
digraph {compound = "true"
newrank = "true"
subgraph "root" {"[root] aws_s3_bucket.demo (expand)" [label = "aws_s3_bucket.demo", shape = "box"]
"[root] random_id.suffix (expand)" [label = "random_id.suffix", shape = "box"]
"[root] aws_s3_bucket.demo (expand)" -> "[root] random_id.suffix (expand)"
...
}
}
Этот текст не для людей. Чтобы посмотреть. Нужно сконвертировать в картинку.
Конвертация в PNG/SVG
Если установлен graphviz:
terraform graph | dot -Tpng > graph.png
terraform graph | dot -Tsvg > graph.svg
Откройте картинку, увидите боксы-ресурсы со стрелками. Стрелка от
aws_s3_bucket.demo к random_id.suffix означает «бакет зависит от
random_id».
В Linux/macOS:
sudo apt install graphviz # Ubuntu/Debian
brew install graphviz # macOS
На Windows: установите Graphviz,
добавьте dot.exe в PATH.
Режимы графа
По умолчанию terraform graph показывает плановый граф (что будет
при apply). Есть и другие:
terraform graph -type=plan # default, для будущего apply
terraform graph -type=plan-destroy # для terraform destroy
terraform graph -type=apply # пост-apply: что уже создано
Большинство случаев, plan. apply-тип показывает только реально
изменённые узлы.
Что искать в графе
1. Узлы без стрелок, изолированные
Это ресурсы, у которых нет зависимостей. Они применятся первыми, параллельно. Если ожидали что ресурс ждёт другого, стрелки нет, значит зависимость не объявлена.
Решение: либо ссылаться на атрибут (implicit dependency), либо
depends_on = [...] (explicit). См. tf-depends-on.
2. Циклы
Если ресурс A зависит от B, а B, от A, граф циклический. Это невалидный DAG, и Terraform упадёт:
Error: Cycle: aws_iam_role.a, aws_iam_policy.b
В графе циклы видно глазами, две стрелки между узлами в обе стороны. См. tf-common-errors про разрешение циклов.
3. Длинные цепочки
Если 10 ресурсов идут последовательно (a → b → c → ...): параллелизм
Terraform теряется. Apply вместо 1 минуты будет 10. Иногда это
неизбежно (RDS должен быть до EC2 который к нему коннектится),
иногда, артефакт лишних depends_on.
Использование с jq для анализа
С Terraform 1.4+ есть terraform graph -draw-cycles, подсветка
циклов. Но для машинного анализа удобнее JSON через terraform show -json:
terraform plan -out=plan.bin
terraform show -json plan.bin | jq '.resource_changes[].address'
Список адресов всех изменяемых ресурсов. Можно строить графики и отчёты.
Rover, альтернатива
Для интерактивной визуализации есть Rover: поднимает локальный веб-сервер с графом, на который можно жмякать.
rover -tfPath /path/to/terraform-binary
Для крупных проектов (50+ ресурсов): намного удобнее, чем dot-PNG.
Подводные камни
-
Граф из
terraform graph, большой даже на маленьком проекте. Каждый провайдер, локал, выходящие данные тоже узлы. Не пугайтесь 50 нод на 5 ресурсов, большинство служебные. -
DOT-формат не всегда красиво ложится. Большие проекты, это спагетти на PNG. Альтернативы: Rover (интерактивно), Inframap (упрощённый cloud-граф),
terraform graph -type=plan+ ручная фильтрацияgrepпо интересующим типам. -
Граф зависит от текущего state. Если state пустой, граф показывает «всё добавить». Если что-то уже создано, только новое. Чтобы увидеть «полный граф» текущего проекта,
terraform refreshсначала. -
-type=plan-destroyпоказывает обратный порядок. Destroy идёт от листьев к корню (сначала зависимые, потом независимые). Если хочется понять что снесётся в каком порядке, этот режим. -
-draw-cyclesпоявился в TF 1.4. На старых версиях циклы придётся искать глазами в DOT-выводе или черезgrep '->'.