Что такое DAG в Terraform
DAG = Directed Acyclic Graph. Вершины, ресурсы, провайдеры, модули, переменные, outputs. Рёбра, зависимости. Граф строится для каждой операции: init, plan, apply, destroy.
Идея проста: чтобы создать ресурс B, который ссылается на ресурс A, сначала нужен A. Граф фиксирует это в виде стрелок.
Источники зависимостей
1. Implicit (через interpolation)
resource "aws_iam_role" "lambda" { ... }resource "aws_lambda_function" "demo" {role = aws_iam_role.lambda.arn # ← implicit dep
}
Terraform парсит выражения, видит ссылку, добавляет ребро:
aws_iam_role.lambda → aws_lambda_function.demo.
2. Explicit (depends_on)
resource "aws_lambda_function" "demo" {depends_on = [aws_iam_role_policy_attachment.lambda]
# ...
}
Используется когда дeps есть, но через значение не выражены например, IAM-policy создаётся параллельно, но Lambda должна стартовать только после неё. См. tf-depends-on.
3. Provider
provider "aws" { ... }resource "aws_s3_bucket" "x" {provider = aws.eu # ← dep на конкретный alias
}
Каждый ресурс зависит от своего провайдера. Это implicit.
4. Модули
module "network" { ... }module "app" {vpc_id = module.network.vpc_id # ← dep на network
}
Ресурсы внутри module.app гипотетически могут стартовать когда хотят,
но Terraform учитывает explicit dep между модулями через interpolation
outputs.
terraform graph, посмотреть DAG
terraform graph | dot -Tpng > graph.png
dot, Graphviz; в Alpine ставится через apk add graphviz.
Альтернатива, rover или tf-summarize, см. tf-rover-visualization.
Walk-порядок
Терраформ обходит граф в топологическом порядке. Ресурсы без зависимостей идут первыми, потом всё что от них зависит. Параллельно где возможно.
Пример:
aws_iam_role.lambda
├── aws_iam_role_policy_attachment.lambda
└── aws_lambda_function.demo
└── aws_lambda_alias.live
random_password.api_key
└── aws_lambda_function.demo (через env)
aws_s3_bucket.logs (нет deps на lambda)
Walk-порядок (упрощённо):
- Layer 1 (параллельно):
aws_iam_role.lambda,aws_s3_bucket.logs,random_password.api_key. - Layer 2 (после Layer 1):
aws_iam_role_policy_attachment.lambda,aws_lambda_function.demo. - Layer 3:
aws_lambda_alias.live.
Parallelism
По умолчанию Terraform делает до 10 операций параллельно. Каждый AWS-API вызов идёт в своём goroutine.
terraform apply -parallelism=20 # больше параллелизма
terraform apply -parallelism=1 # последовательно (debug)
Больше, быстрее, но рискуешь поймать AWS API throttling
(ThrottlingException). Меньше, медленнее, но безопаснее для API
с rate-limit'ами.
Optimal зависит от провайдера и масштаба state'а. 10, норм по умолчанию. Для огромных стеков иногда 20-30; для AWS Organizations manipulation, 1-2.
Циклы
Если два ресурса ссылаются друг на друга, Terraform не может выбрать порядок.
Пример (плохо):
resource "aws_security_group" "web" { ingress {from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id] # ← dep на alb
}
}
resource "aws_security_group" "alb" { ingress {from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.web.id] # ← dep на web
}
}
Error: Cycle: aws_security_group.web, aws_security_group.alb.
Решение, aws_security_group_rule resource'ы отдельно:
resource "aws_security_group" "web" {}resource "aws_security_group" "alb" {}resource "aws_security_group_rule" "web_from_alb" {type = "ingress"
security_group_id = aws_security_group.web.id
source_security_group_id = aws_security_group.alb.id
# ...
}
resource "aws_security_group_rule" "alb_from_web" {type = "ingress"
security_group_id = aws_security_group.alb.id
source_security_group_id = aws_security_group.web.id
# ...
}
Теперь два security_group'а независимы, rules, отдельные ресурсы
без cycle'а.
Debug cycle'а:
terraform graph -draw-cycles | dot -Tsvg > cycle.svg
Циклы подсвечены красным. См. tf-graph.
Walk-state vs plan vs apply
| Операция | Что в DAG |
|---|---|
init | Загрузка провайдеров и модулей. DAG строится частично. |
validate | Полный DAG, но без API-вызовов. |
plan | Полный DAG + refresh-фаза для каждого ресурса. Считает diff. |
apply | Walk DAG, для каждой ноды, provider call. Parallel где можно. |
destroy | Reverse DAG, топологический порядок наоборот. |
Destroy сложнее: нужно удалить child'ов до parent'а. Reverse-walk.
Какие зависимости НЕ ловятся автоматически
-
Side effects через external script.
data "external"исполняет скрипт, но Terraform не знает что внутри. Если скрипт читает из S3-бакета, который только что создан тем же plan'ом может race-condition'ить. -
Ресурсы, создаваемые через provisioners.
local-exec/remote-execзапускаются после ресурса, но если они что-то создают в облаке (через CLI), Terraform этого не видит. -
Через provider-default'ы.
default_tags { ... }, теги применяются ко всем ресурсам, но в DAG нет ребра «всё зависит от default_tags». -
terraform_remote_state. Зависимость на другой stack, это data source, читается на каждом plan. Граф этого не моделирует cross-stack.
Подводные камни
-
Цикл сложно вычислить визуально. На большом графе circular-deps через 5 ресурсов руками не найдёшь.
terraform graph -draw-cycles, единственный sane debug. -
depends_onсоздаёт «жёсткое» ребро. Использовать только когда implicit dep нет. Лишниеdepends_onзамедляют apply (parallelism падает). -
Module-level depends_on (TF 1.0+). Можно поставить на весь модуль , но тогда все ресурсы модуля зависят от него. Часто overkill.
-
count/for_each + dynamic, DAG строится по элементам. Каждый
aws_s3_bucket.x[0],[1],[2], своя нода в графе. -
Большой граф = долгий plan. Refresh каждой ноды, API call. 5000 ресурсов = 5000 GET-вызовов. См. tf-large-scale-state про разбиение.
-
Parallelism vs API throttling. На larger stacks
-parallelism=20может ускорить, но AWS отвечает 429. Эмпирически подбирай.