4. 使用terraform plan进行漂移预检
在上一节中,我们已经理解了配置漂移的成因与危害。真正进入修复流程时,第一步并不是急着改代码或改云资源,而是先做一次"体检"——用 terraform plan 把当前真实基础设施与 Terraform 状态、配置三者之间的差异完整地暴露出来。plan 的输出本质上是一份"变更提案",它既包含我们主动修改配置后产生的预期变更,也包含别人在控制台手动改动资源后产生的意外漂移。能否读懂这份提案,直接决定了后续修复策略是否正确。
plan 的三方对比模型
要理解 plan 的输出,必须先明确它比较的是哪三方:配置文件(.tf)描述期望状态,状态文件(terraform.tfstate)记录上一次 apply 后的已知状态,真实云资源是实际存在的对象。Terraform 在 plan 阶段会先通过 refresh 把真实资源的最新属性回写到内存中的状态视图,再拿这份"刷新后的状态"与配置文件对比,最终生成变更计划。因此,漂移检测的关键就在于:refresh 让真实资源与状态文件之间的差异浮出水面,而 plan 的 diff 又让这份差异与配置期望之间的差距变得可见。
案例背景与初始状态
假设我们维护一套电商演示环境,其中一台 Web 服务器由如下配置管理:
resource "aws_instance" "web" {
ami = "ami-0abc123"
instance_type = "t3.small"
tags = {
Name = "web-prod"
Environment = "production"
}
}
resource "aws_security_group" "web_sg" {
name = "web-sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
上周 apply 之后一切正常。本周运维同事为了临时压测,在控制台把实例规格改成了 t3.medium,并给安全组额外开放了 8080 端口,事后忘记还原。同时,我们自己为了统一命名规范,把配置文件里的 Name 标签从 web-prod 改成了 web-prod-01。现在执行 plan,会同时看到两类变更。
解读 plan 输出
$ terraform plan
Terraform will perform the following actions:
# aws_instance.web will be updated in-place
~ resource "aws_instance" "web" {
~ instance_type = "t3.medium" -> "t3.small"
~ tags = {
~ "Name" = "web-prod" -> "web-prod-01"
}
# (其他属性不变)
}
# aws_security_group.web_sg will be updated in-place
~ resource "aws_security_group" "web_sg" {
~ ingress = [
- {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
+ {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
]
}
Plan: 0 to add, 2 to change, 0 to destroy.
逐条分析这份输出:
- instance_type 的
~变更:当前真实值是t3.medium,配置期望是t3.small。配置从未改过规格,所以这是典型的意外漂移——有人手动改了资源。plan 给出的动作是把实例改回t3.small。 - Name 标签的
~变更:真实值是web-prod,配置期望是web-prod-01。这是我们主动修改配置造成的预期变更,应当保留。 - 安全组 ingress 的增删:真实规则里多了一条 8080 端口,配置里没有。plan 会删除这条多余规则并恢复 80 端口规则。8080 是手动添加的,属于意外漂移。
这里有一个关键判断点:同一个 ~ 符号既可能代表预期变更,也可能代表意外漂移,不能只看符号,必须结合"这个属性最近有没有被我们改过配置"来判断。如果团队有 Git 提交记录,可以对照最近一次 commit 的 .tf 变更;如果没有,就要靠对资源用途的理解来推断。
制定初步修复策略
识别出两类变更后,修复策略要分开处理,不能一把梭 terraform apply 了事。
对于预期变更(Name 标签):直接保留,apply 时会正常写入。这类变更不需要额外操作,但要注意它可能和漂移变更混在同一个资源里,apply 时会一起执行。
对于意外漂移(instance_type、8080 端口):需要先判断"手动改动是否应该保留"。这里有两种情况:
- 改动是临时的、应该回退:比如压测结束后忘记改回规格,8080 端口是调试时临时开的。此时策略是让 Terraform 把资源改回配置期望值,即直接 apply。但要注意,如果实例规格从 t3.medium 改回 t3.small 需要停机,应安排在维护窗口执行。
- 改动是合理的、应该固化到配置:比如团队决定长期使用 t3.medium,或 8080 端口确实需要长期开放。此时不能直接 apply(否则会把合理改动冲掉),而应该先修改 .tf 文件,把新值写进配置,再 apply。这样配置与真实资源重新对齐,漂移被"吸收"为正式配置。
在本案例中,压测已结束、8080 是临时调试端口,因此选择回退策略。但回退前建议先做一次 terraform plan -out=tfplan 把计划保存下来,便于复核和审计:
terraform plan -out=drift-fix.tfplan
terraform show -json drift-fix.tfplan | jq '.resource_changes[] | {address, actions: .change.actions}'
用 terraform show -json 可以把 plan 转成结构化 JSON,配合 jq 快速筛出所有 actions 为 ["update"] 的资源,方便在 CI 中做自动化漂移告警。例如在流水线里加一条判断:如果 plan 输出中出现了非预期的 update,就阻断部署并通知负责人。
修复前的检查清单
| 检查项 | 本案例情况 | 处理动作 |
|---|---|---|
| 变更是否来自配置修改 | Name 标签来自配置修改 | 保留,正常 apply |
| 变更是否来自控制台手动操作 | instance_type、8080 端口 | 确认后回退或固化 |
| 回退是否会造成停机 | 实例规格变更需重启 | 安排维护窗口 |
| 是否有依赖该漂移的上下游 | 8080 端口可能被监控引用 | 先确认再删除 |
| plan 是否已保存备查 | 尚未保存 | 执行 plan -out 保存 |
terraform apply -auto-approve。自动批准会跳过人工确认,一旦漂移中包含应该保留的合理改动,就会被静默覆盖,造成生产事故。漂移修复场景下,始终建议人工审阅 plan 输出后再执行。
完成上述判断后,我们就得到了一个明确的修复方向:保留 Name 标签变更,回退 instance_type 和 8080 端口。下一步才是真正执行 apply 并验证结果。plan 预检的价值就在于,它把"改什么、为什么改、改了会怎样"提前摊开在面前,让修复从盲目操作变成有据可依的决策。