4. 资源生命周期与依赖管理

Terraform 的核心价值不只是“把资源写出来”,而是让资源之间的创建、更新、销毁顺序可预测、可复现。要做到这一点,必须理解依赖关系如何被 Terraform 推导、如何被人工干预,以及生命周期元参数如何改变默认行为。本节围绕依赖图、生命周期参数和漂移处理三条主线展开。

一、依赖关系:隐式依赖与显式 depends_on

Terraform 在执行 plan 和 apply 时,会先把配置解析成一张有向无环图(DAG)。图中的节点是资源、数据源、模块和 provider,边表示“谁依赖谁”。依赖关系决定了操作顺序:被依赖者先创建,依赖者后创建;销毁时顺序相反。

隐式依赖来自表达式引用。只要一个资源在参数中引用了另一个资源的属性,Terraform 就能自动识别依赖。例如 VPC 与子网:

resource "aws_vpc" "main" {
  cidr_block = "10.20.0.0/16"
  tags = { Name = "prod-vpc" }
}

resource "aws_subnet" "app" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.20.1.0/24"
  tags       = { Name = "prod-app-subnet" }
}

这里 aws_subnet.app 通过 aws_vpc.main.id 建立了隐式依赖,Terraform 会先创建 VPC,再创建子网。隐式依赖是首选方式,因为它随代码变化自动更新,不需要人工维护。

显式依赖使用 depends_on,用于表达“没有属性引用、但存在真实先后关系”的场景。典型例子是 IAM 策略与业务资源:策略本身不引用实例,但实例启动前必须已具备权限。

resource "aws_iam_role_policy_attachment" "app" {
  role       = aws_iam_role.app.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}

resource "aws_instance" "app" {
  ami           = "ami-0example1234567890"
  instance_type = "t3.medium"
  depends_on    = [aws_iam_role_policy_attachment.app]
}

使用 depends_on 的原则是“能隐式就不显式”。显式依赖会强制整条链路串行,降低并行度,也容易掩盖本应通过属性引用表达的真实关系。它更适合处理权限生效、外部系统就绪、跨模块副作用等无法用属性表达的约束。

依赖图与创建/销毁顺序 创建顺序 → aws_vpc.main 10.20.0.0/16 aws_subnet.app 10.20.1.0/24 aws_iam_role_policy 策略附加 aws_instance.app t3.medium 隐式 depends_on ← 销毁顺序 生命周期元参数 create_before_destroy prevent_destroy ignore_changes replace_triggered_by timeouts

二、生命周期元参数:改变默认行为

Terraform 为每个资源提供 lifecycle 块,用于覆盖默认的创建、更新、销毁策略。常用参数如下:

  • create_before_destroy:先创建新资源,再销毁旧资源。适用于负载均衡器、证书、实例等“先有替代品再下线”的场景,可减少服务中断。
  • prevent_destroy:阻止销毁。对生产数据库、核心存储桶等关键资源设置后,误删配置会直接报错,而不是静默删除。
  • ignore_changes:忽略指定属性的外部变更。例如实例标签被运维脚本修改后,不希望 Terraform 每次 plan 都提示回滚。
  • replace_triggered_by:当指定资源或属性变化时,强制替换当前资源。适合“配置变更必须重建实例”的场景。
  • timeouts:自定义创建、更新、删除的超时时间,适配慢速资源。
resource "aws_instance" "app" {
  ami           = "ami-0example1234567890"
  instance_type = "t3.medium"

  lifecycle {
    create_before_destroy = true
    ignore_changes        = [tags["LastScanned"]]
    prevent_destroy       = false
  }
}

需要注意:create_before_destroy 与唯一性约束可能冲突。例如安全组名称、固定 IP 等资源若不允许重名,先创建会失败。此时应改用可生成唯一名称的 name_prefix,或调整依赖顺序。

三、创建与销毁顺序的判定

顺序由依赖图决定,而不是由代码书写顺序决定。创建时,Terraform 从无依赖的节点开始,逐层向后推进;销毁时反向执行。若两个资源之间没有依赖,Terraform 会并行处理,因此不能假设“写在后面的先删”。

当出现循环依赖时,terraform plan 会直接报错并指出环路。常见诱因是两个资源互相引用属性,或模块之间形成双向依赖。解决方式通常是引入中间资源、拆分模块,或用 depends_on 替代属性引用,但后者要谨慎,避免把本可并行的链路串行化。

四、资源漂移:识别与处理

资源漂移指真实云上状态与 Terraform state 不一致。常见来源包括:控制台手工修改、其他自动化工具变更、云厂商侧默认值调整、资源被外部删除。漂移不会自动消失,必须通过 plan 暴露出来。

处理漂移的标准流程是:

  1. 执行 terraform plan,观察哪些资源被标记为 update、replace 或 destroy。
  2. 判断漂移是否符合预期。若是人为误操作,优先让 Terraform 把资源拉回配置描述的状态。
  3. 若外部变更才是正确状态,则更新代码,使配置与真实状态一致,再执行 apply。
  4. 对无法通过配置表达的外部变更,使用 ignore_changes 明确声明“不管理该属性”。
  5. 对已被外部删除的资源,执行 terraform apply 重建,或用 terraform state rm 解除管理后再决定是否重新导入。
定期在 CI 中执行 terraform plan -detailed-exitcode,退出码为 2 表示存在差异,可把漂移检测纳入日常流水线,而不是等到发布时才发现。
不要用 terraform refresh 或 apply -refresh-only 盲目覆盖漂移。刷新只更新 state,不改变真实资源;若真实资源已被误删,刷新后 state 会丢失该资源,后续 apply 可能触发重建,需先确认影响范围。

五、实践检查清单

检查项判断标准处理建议
依赖是否隐式表达参数中是否引用了上游资源属性能引用就不写 depends_on
depends_on 是否必要是否存在无属性引用的真实先后约束仅用于权限、外部就绪等场景
关键资源是否防误删数据库、存储桶是否设置 prevent_destroy生产环境建议开启
替换是否平滑实例、证书是否使用 create_before_destroy注意唯一性约束冲突
漂移是否可控plan 是否出现非预期变更先判断再 apply,必要时 ignore_changes

依赖管理和生命周期控制是 Terraform 从“能跑”走向“可运维”的分水岭。隐式依赖保证图正确,显式依赖补齐语义,生命周期参数控制风险,漂移处理维持配置与现实的同步。把这四件事纳入日常流程,资源生命周期才真正可预期。