4. 资源依赖关系映射与可视化

在 Terraform 中,资源之间并不是彼此孤立的。一个 VPC 被创建后,子网才能引用它;一个安全组被定义后,实例才能挂载它;一个数据库实例被创建后,应用配置才能读取它的连接地址。Terraform 通过隐式依赖(引用表达式)和显式依赖(depends_on)构建出一张有向图,并据此决定资源的创建、更新与销毁顺序。理解这张图,是迁移与重构过程中判断“先动谁、后动谁、动错谁会连锁失败”的核心能力。

依赖关系的两种来源

隐式依赖来自表达式引用。例如子网引用 aws_vpc.main.id,Terraform 会自动推断子网依赖 VPC,无需额外声明。显式依赖则用于那些“逻辑上必须等待、但数据上没有直接引用”的场景,比如某个 IAM 策略需要在角色创建完成后再附加,或者某个应用需要在数据库初始化脚本执行完毕后再启动。迁移时,隐式依赖通常随代码一起搬迁,风险较低;而显式依赖往往是人为补丁,容易在重构中被遗漏,是重点排查对象。

用 terraform graph 生成依赖图

Terraform 内置了 terraform graph 命令,它会输出当前配置的依赖图。默认输出为 DOT 格式,可交给 Graphviz 渲染成图片。典型用法如下:

# 生成 DOT 文件
terraform graph -type=plan > graph.dot

# 使用 Graphviz 渲染为 PNG
dot -Tpng graph.dot -o graph.png

# 只关注特定资源及其依赖
terraform graph -type=plan | grep -A 5 "aws_subnet.app"

-type 参数可选 plan、apply、destroy,分别对应计划、应用和销毁阶段的依赖视图。迁移前建议至少生成 plan 和 destroy 两张图:前者用于确认创建顺序,后者用于识别销毁时的反向依赖,避免删除 VPC 时因子网未清理而失败。

DOT 输出中,节点代表资源,箭头代表依赖方向。箭头从“被依赖者”指向“依赖者”,即 A → B 表示 B 依赖 A,A 必须先创建。图中还会出现 provider、var、output 等特殊节点,迁移时可将它们视为边界输入,重点关注资源节点之间的连线密度。

第三方工具与可视化增强

terraform graph 的原始 DOT 图在资源较多时可读性较差。实践中常用以下工具增强:

  • Graphviz / dot:最基础的渲染工具,适合快速出图,但布局对大型图不够友好。
  • terraform-docs:侧重文档生成,可辅助梳理模块输入输出,间接暴露依赖边界。
  • Blast Radius:将 Terraform 状态或计划转换为交互式图,支持按模块、资源类型过滤,适合定位“改动一个资源会影响哪些下游”。
  • Rover:生成带资源属性的可视化图,支持在浏览器中查看依赖关系,适合迁移评审时共享。
  • Infracost / Checkov:虽非纯依赖图工具,但可结合依赖分析识别成本与合规风险点。

选择工具时,迁移场景优先考虑“能否按模块过滤”和“能否高亮关键路径”。如果团队已有 CI 流程,可将 terraform graph 输出作为构建产物存档,配合脚本自动检测依赖环或孤立节点。

识别关键路径与风险点

依赖图中的“关键路径”通常指从根节点到最深叶节点的最长链。迁移时,关键路径上的资源一旦延迟或失败,会阻塞后续所有资源。例如:VPC → 子网 → 安全组 → 实例 → 负载均衡 → DNS 记录,这条链上任何一环出错,整个应用都无法上线。识别方法是在图中寻找入度为 0 的根节点,然后沿最长分支向下追踪。

风险点则集中在以下几类结构:

  • 高扇出节点:一个资源被大量下游引用,如共享 VPC 或基础安全组。迁移时需优先保证其稳定,并考虑是否拆分。
  • 循环依赖:Terraform 会直接报错,但迁移中可能因模块拆分而意外引入。需用 terraform validate 和 graph 输出提前发现。
  • 跨模块隐式依赖:模块 A 通过 data source 读取模块 B 的资源,图中表现为间接连线。迁移时若只搬 A,B 的变更可能悄悄破坏 A。
  • 显式 depends_on 链:人为添加的等待关系,容易在重构中丢失,导致资源创建顺序错乱。
  • 销毁顺序敏感节点:如数据库、存储桶、日志组,删除顺序错误会导致数据丢失或删除失败。
迁移前建议对每个高风险节点标注“是否可重建”“是否有状态”“是否被外部引用”,这三项决定了迁移策略是原地导入、重建还是并行运行。

依赖图在迁移中的实际用法

假设需要将一个单体 Terraform 配置拆分为网络、数据、应用三个模块。第一步,用 terraform graph -type=plan 生成当前全量图,标出模块边界。第二步,观察跨边界连线:如果网络模块的输出被数据模块直接引用,则拆分后需通过 module 输出或 remote state 传递。第三步,对每条跨边界依赖评估“是否必须同步迁移”,若否,可先保留在旧配置中,用 data source 桥接,降低一次性迁移风险。

下图展示了一个典型三层应用的依赖关系映射,包含网络层、数据层、应用层以及外部边界,并标注了关键路径与高风险节点。图中箭头方向为“被依赖 → 依赖”,即箭头起点必须先于终点创建。

网络层 数据层 应用层 aws_vpc.main 根节点 · 高扇出 aws_subnet.app 关键路径 aws_sg.app 安全组 aws_lb.app 高风险 · 外部入口 aws_db_instance 有状态 · 销毁敏感 aws_s3_bucket 有状态 · 数据保留 aws_iam_role 显式依赖常见 aws_instance.app 计算节点 aws_route53_record DNS 出口 外部依赖 data source / 远程状态 关键路径起点 隐式依赖(引用) 高风险连线 外部/跨模块依赖 高风险节点

从图中可以读出几条迁移决策依据:aws_vpc.main 是根节点且扇出到子网、安全组、数据库,迁移时应优先保证其配置稳定,并考虑是否将 VPC 保留为共享资源;aws_db_instance 和 aws_s3_bucket 是有状态节点,销毁顺序敏感,迁移时建议先做快照或启用删除保护;aws_lb.app 是外部入口,变更会影响 DNS 解析,应安排在维护窗口操作;aws_iam_role 与外部依赖之间的虚线表示跨模块引用,拆分模块时需显式传递输出。

把依赖分析纳入迁移流程

依赖图不是一次性产物,而应随迁移阶段反复生成。建议在迁移前、模块拆分后、以及正式 apply 前各生成一次 terraform graph -type=plan,对比节点和连线的变化。若发现新增了跨模块连线或关键路径变长,说明拆分引入了额外耦合,需要调整模块边界。对于销毁阶段,单独生成 -type=destroy 图,确认有状态资源位于销毁链末端,避免误删上游导致数据不可恢复。

依赖图只反映 Terraform 管理的资源关系,不包含手动创建的资源、外部系统调用和运行时依赖。迁移时需结合 CMDB、监控拓扑和实际调用链交叉验证,避免“图上看没问题、上线后才发现漏了”。

最后,将依赖分析结果沉淀为迁移检查表:每个高风险节点标注负责人、迁移方式(导入/重建/并行)、回滚方案和验证命令。这样依赖图才真正从“一张图”变成迁移过程中的风险控制工具。