4. Terraform plan深度解读

在 Terraform 大规模变更风险控制中,terraform plan 是唯一能在真正执行前把“将要发生什么”完整暴露出来的环节。但很多人只看了最后一行 Plan: 3 to add, 1 to change, 0 to destroy 就点确认,这在大规模环境下极其危险。本节把 plan 拆成四个可独立判读的层次:文件结构、动作符号、known after apply、敏感值与风险信号。

一、plan 文件的结构:从二进制到可读 JSON

执行 terraform plan -out=tfplan 生成的是二进制 plan 文件,它包含三部分:配置快照(当前 .tf 与变量解析结果)、状态快照(state 中已有资源)、变更计划(planned values 与 actions)。二进制文件不可直接阅读,但可以用 terraform show -json tfplan 转成结构化 JSON,这是做自动化风险扫描的基础。

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
jq '.resource_changes[] | {addr: .address, actions: .change.actions}' plan.json

JSON 中最关键的是 resource_changes 数组,每个元素包含 address(资源地址)、change.actions(动作数组)、change.before(当前值)、change.after(计划值)、change.after_unknown(执行后才知的值)。理解这五个字段,就掌握了 plan 的全部信息源。

二、资源动作符号:六种组合的真实含义

Terraform 用符号表示动作,但符号是数组而非单字符,必须按组合理解:

符号含义风险等级
+create,新建资源低(除非配额/成本敏感)
-destroy,删除资源高
~update in-place,原地更新中,取决于字段
-/+replace,先删后建极高,服务会中断
+/−create before destroy,先建后删中高,需双倍配额
−/+ 带 # forces replacement强制替换极高,需重点审查

特别注意 -/+ 与 +/- 的区别:前者是先销毁再创建,对数据库、负载均衡等有状态资源意味着数据丢失和停机;后者依赖 lifecycle { create_before_destroy = true },虽然不中断但会短暂出现两份实例。在 plan 输出中,替换动作会附带 # forces replacement 注释,指明是哪个字段触发的,这是定位风险根因的关键线索。

三、known after apply:为什么计划值不完整

当某个属性的值依赖尚未创建的资源(如自动生成的 ID、ARN、IP),Terraform 无法在 plan 阶段确定它,会标记为 (known after apply)。这在 JSON 中体现为 after_unknown 字段为 true。

它的风险在于:依赖这些未知值的下游资源,其变更范围在 plan 阶段是不完整的。例如安全组规则引用新创建的安全组 ID,plan 只能显示“将新增一条规则”,但无法确认规则内容是否正确。大规模场景下,如果大量资源出现 known after apply,说明模块间耦合过深,plan 的确定性下降,应通过拆分 apply 批次或使用 terraform plan -refresh-only 先固化状态来降低不确定性。

四、敏感值处理与风险信号提取

标记为 sensitive = true 的变量和输出,在 plan 中显示为 (sensitive value),JSON 中对应字段被替换为占位符。这带来一个矛盾:安全上必须脱敏,但风险审查又需要看到真实值。工程做法是:在 CI 中把 plan JSON 上传到受控的 artifact 存储,由具备权限的审查者用 terraform show -json 配合内部工具解密查看,而不是在日志里明文打印。

风险信号提取应聚焦以下模式,可直接用 jq 或 OPA 策略实现:

  • destroy 类动作:任何 - 或 -/+,尤其是 aws_db_instance、aws_s3_bucket、aws_iam_role 等有状态或权限资源。
  • 替换动作:actions 包含 delete 且 create,检查是否配置了 create_before_destroy。
  • 安全组/防火墙规则放宽:after 中出现 0.0.0.0/0 或端口范围扩大。
  • IAM 策略变更:Action: "*" 或 Resource: "*" 的新增。
  • known after apply 比例:统计 after_unknown 为 true 的资源数,超过阈值则提示分批 apply。
不要用 grep 直接匹配 plan 文本输出做风险判断,文本格式会随 Terraform 版本变化;应基于 terraform show -json 的结构化字段编写策略。
Terraform plan 风险信号提取链路 terraform plan -out=tfplan(二进制) terraform show -json plan.json(结构化) resource_changes[] address / actions / before / after after_unknown known after apply 标记 动作符号判读 + create 新建 ~ update 原地更新 - destroy 删除 -/+ replace 先删后建 +/- 先建后删 风险信号提取规则 高危:destroy / replace DB / S3 / IAM 有状态资源 检查 create_before_destroy 中危:安全组放宽 0.0.0.0/0 或端口扩大 IAM Action/Resource = * 不确定性:known after apply after_unknown = true 计数 超阈值提示分批 apply 敏感值:sensitive JSON 中为占位符 受控 artifact 中审查

五、把 plan 审查变成可执行的检查清单

结合上述分析,可以在 CI 流水线中固化一份 plan 审查清单,作为 apply 前的强制门禁:

  1. 执行 terraform plan -out=tfplan 并转 JSON,归档为构建产物。
  2. 统计 resource_changes 中 actions 含 delete 的资源,若涉及数据库、存储桶、IAM 角色,标记为阻断项,需人工审批。
  3. 检查所有 replace 动作是否配置了 create_before_destroy,未配置的标记为高风险。
  4. 扫描 after 中新增的 0.0.0.0/0、"*" 权限,标记为安全审查项。
  5. 统计 after_unknown 为 true 的资源比例,超过 30% 时建议拆分 apply 批次。
  6. 敏感值不落日志,plan.json 上传至受控存储,审查者按需解密查看。

这套流程的价值在于:把原本依赖个人经验的“看 plan”变成可重复、可审计、可自动阻断的工程动作。在大规模变更中,plan 不是一次性的输出,而是风险控制的核心数据源。