3. 漂移检测的核心命令与输出解读

在 Terraform 的日常运维中,配置漂移(Configuration Drift)指的是真实基础设施的状态与 Terraform 状态文件(terraform.tfstate)中记录的状态不一致。这种不一致可能来自控制台手工修改、其他自动化工具的操作、云厂商侧的自动变更,或者资源被意外删除。要发现漂移,核心手段就是围绕 terraform plan、terraform refresh、terraform show 这三条命令构建检测链路。理解它们各自读取什么、输出什么、如何配合,是漂移治理的基本功。

三条命令的定位与数据流

先厘清三者的职责边界。terraform refresh 的职责是"对齐状态":它读取配置(.tf 文件)和当前状态文件,调用云 API 查询每个资源的真实属性,然后把真实值写回状态文件。它不修改真实资源,只修改状态记录。terraform plan 的职责是"计算差异":它先隐式执行一次刷新(除非加 -refresh=false),再把刷新后的状态与配置期望值对比,输出一份变更计划。terraform show 的职责是"呈现内容":它把状态文件或计划文件以人类可读的形式打印出来,本身不做任何计算。

三者的数据流关系可以用下图表示:配置与真实资源是两端,状态文件是中间记录,refresh 负责把真实值同步进状态,plan 负责把状态与配置对比出差异,show 负责把状态或计划渲染成可读文本。

漂移检测三命令的数据流与职责 配置期望 *.tf 文件 声明式目标状态 真实基础设施 云 API 实际值 可能已被手工改动 状态文件 terraform.tfstate 上次记录的资源属性 terraform refresh 拉取真实值写回状态 变更计划 plan 输出 + 创建 / ~ 修改 / - 删除 terraform plan 对比配置与状态 可读输出 terraform show show 也可直接读状态 漂移信号 plan 中出现非预期 ~ / - / +

terraform plan:漂移检测的主入口

日常检测漂移,最常用的就是 terraform plan。它默认会先刷新状态,再计算差异。如果真实资源被手工改过,刷新后的状态就会带上真实值,plan 对比配置后就会显示出"要把资源改回配置声明值"的计划。这正是漂移的典型信号。

执行时建议加上 -out 参数把计划保存下来,便于后续用 show 查看和审阅:

terraform plan -out=drift.tfplan

# 输出片段示例(假设某安全组被手工放开了 8080 端口)
Terraform will perform the following actions:

  # aws_security_group.web will be updated in-place
  ~ resource "aws_security_group" "web" {
        id          = "sg-0a1b2c3d4e5f67890"
        name        = "web-sg"
      ~ ingress     = [
          - {
              - from_port   = 8080
              - to_port     = 8080
              - protocol    = "tcp"
              - cidr_blocks = ["0.0.0.0/0"]
            },
            # (原有 80/443 规则保持不变)
        ]
    }

Plan: 0 to add, 1 to change, 0 to destroy.

解读这份输出,关键看三点。第一,看动作符号:~ 表示原地修改(in-place update),- 表示删除,+ 表示新建,-/+ 表示先删后建(replace)。第二,看资源地址:aws_security_group.web 告诉你漂移发生在哪个资源。第三,看属性差异:- 开头的行是"当前真实状态里有、但配置里没有"的内容,也就是被手工加进去的 8080 规则。plan 的意图是把它移除,使资源回到配置声明的样子。

如果计划里出现大量 -/+ 替换,且涉及有状态资源(如数据库、磁盘),要格外警惕——这可能是漂移触发了不可逆的重建。此时不要直接 apply,应先确认漂移来源。

terraform refresh:只对齐状态,不动资源

terraform refresh 是一个容易被误解的命令。它不会把真实资源改成配置的样子,它只是把真实资源的当前属性读回来,覆盖状态文件里的旧记录。换句话说,它让"状态文件"追上"真实世界",而不是让"真实世界"追上"配置"。

它的典型用途有两个。一是单独查看真实状态:先 refresh,再 show,就能看到资源当前的真实属性,而不触发任何变更计划。二是在 plan 之前手动对齐状态,避免 plan 基于过期状态做出误判。需要注意的是,在较新的 Terraform 版本中,terraform refresh 已被标记为不推荐单独使用,官方建议用 terraform plan -refresh-only 或 terraform apply -refresh-only 来替代,因为后者会明确告诉你"状态将被更新成什么",更安全可控。

# 传统方式:直接刷新状态
terraform refresh

# 推荐方式:先看刷新会带来什么变化,再决定是否应用
terraform plan -refresh-only
terraform apply -refresh-only

-refresh-only 模式的输出只包含状态更新,不会包含任何资源创建、修改或删除。如果输出里出现了属性变化,说明真实资源确实与状态记录不一致,这就是漂移的直接证据。

terraform show:把状态和计划读出来

terraform show 本身不检测漂移,但它是解读漂移的放大镜。它有两种主要用法:读取状态文件,或读取 plan 生成的计划文件。

# 查看当前状态中所有资源的属性
terraform show

# 查看之前保存的计划文件内容
terraform show drift.tfplan

# 以 JSON 格式输出,便于脚本解析和比对
terraform show -json drift.tfplan

当你怀疑漂移但 plan 输出太长时,可以用 terraform show -json 把计划导出为 JSON,再用 jq 之类的工具筛选出 resource_changes 中 actions 不为 ["no-op"] 的条目。这样能快速定位到底哪些资源发生了变更,而不必在成百上千行文本里翻找。

terraform show -json drift.tfplan | \
  jq '.resource_changes[] | select(.change.actions != ["no-op"]) | {address, actions: .change.actions}'

输出中的变更信号速查

把 plan 输出里的符号和含义对应清楚,是快速判断漂移性质的关键。下表整理了常见信号及其解读方向。

符号 含义 漂移解读
~ 原地修改 属性被手工改动,plan 要改回配置值
- 删除 配置中已移除但真实资源仍存在,或资源被标记为待销毁
+ 新建 配置中声明了但状态里没有,可能是资源被手工删除
-/+ 先删后建 不可变属性被改动,触发替换,需重点确认
<= 仅读取数据源 数据源刷新,通常不涉及资源漂移
实用建议:在 CI 流水线中定期执行 terraform plan -detailed-exitcode。该参数会让 plan 在"有变更"时返回退出码 2,"无变更"时返回 0,"出错"时返回 1。这样就能用退出码自动判断是否存在漂移,无需人工阅读输出。
注意:不要用 terraform apply 来"顺便"检测漂移。apply 会真实修改基础设施,如果漂移判断有误,可能造成生产事故。检测阶段应始终停留在 plan / refresh / show,确认清楚后再决定是否 apply。

总结来说,terraform plan 是发现漂移的主入口,terraform refresh(或 -refresh-only)负责把真实值同步进状态,terraform show 负责把状态和计划渲染成可读、可解析的形式。三者配合,就能形成一条从"发现差异"到"确认差异"再到"定位差异"的完整检测链路。真正重要的是养成先看 plan、再动手的习惯,让每一次变更都有据可依。