LabHub
学习 学习路径 课程

Terraform 实战

生命周期与漂移 — 往哪一边对齐

在 LabHub 中继续学习

一句话总结

应对漂移时,核心问题不是“如何修复”,而是**“让代码符合实物,还是让实物符合代码”**。

循环图: “让代码符合实物,还是让实物符合代码” · 工具会尽职尽责地撤销凌晨做出的全部变更。 · 未被发现的漂移才是罪。 · 区分由代码修改造成的差异和代码之外产生的差异

为什么需要了解这一点

凌晨三点,流量激增导致服务宕机。负责人进入控制台,扩大实例规格,提高自动扩缩容上限,并临时开放调试端口。服务恢复了。到这里为止,这些判断都是正确的。

问题出在之后。这三项变更没有记录在任何地方。代码仍然描述旧值,状态文件也一样。几天后,有人因为完全不同的原因执行 apply,工具会尽职尽责地撤销凌晨做出的全部变更。 故障再次发生,却没人知道原因。

这就是漂移:代码(应有状态)、状态文件(最后已知状态)和实物(当前实际状态)三者不一致。漂移本身不是罪,未被发现的漂移才是罪。

工作原理

检测漂移的基本工具是 plan。计划会在执行前刷新实物并与状态比较,然后再比较状态与代码。它足够让人阅读,却不适合自动化;解析输出字符串的方式会在版本变化时失效。

因此有了 -detailed-exitcode

退出码 含义
0 无变更——代码、状态与实物一致
1 错误
2 有变更——漂移或尚未应用的修改

仅凭这三个值,就能把漂移检测直接加入 cron 或 CI。这里常见的陷阱是 set -e:它会把退出码 2 当成失败,使脚本立即终止,因此执行判定命令时必须明确进行例外处理。

需要更精确地检查时,可以用 plan -refresh-only 保存计划,再通过 show -json 导出。JSON 中有单独的 resource_drift 数组,可以区分由代码修改造成的差异和代码之外产生的差异。如果在同一条告警中混合“有人在控制台改过”和“有尚未部署的代码”,人们很快就会开始忽略告警。

lifecycle 块提供四个介入这一流程的控制项。

参数 作用 使用场景
create_before_destroy 先创建新资源,再删除旧资源 替换期间必须避免中断
prevent_destroy 直接把销毁计划阻止为错误 状态存储、生产数据库
ignore_changes 从计划中排除指定属性的差异 由外部系统管理的属性
replace_triggered_by 其他资源变化时重新创建本资源 强制替换没有更新手段的资源

ignore_changes 尤其重要。自动扩缩容器管理的副本数、控制台自动添加的标签等,都是有意在代码之外变化的值。若连这些也视为漂移,误报会大量出现,而数量过多的告警很快就会被忽略。但忽略列表必须精确到对应属性;若整体忽略,真正的事故也会一同悄然消失。

prevent_destroy 会在计划阶段报错并停止。这意味着误删资源块或改名时,在销毁获批前就会踩下刹车。另一方面,有意删除时必须先从代码中删除这一行,所以它也是强制删除操作接受评审的机制。

实际工作中的表现

第一,应对方向有三种。 (1) 通过 apply 让实物回到代码定义;(2) 如果变更本来就是有意的,则修改代码以符合实物;(3) 如果实物正确、代码也已经正确,则通过 apply -refresh-only 只更新状态。凌晨的应急措施若是正确判断,强行回滚反而会制造事故。不经过判断就运行自动修复最危险。

第二,按严重程度实施自动化。 单个标签不一致可以自动回滚,但安全组规则或实例规格应由人检查。实践中的标准模式是只自动修复低严重度问题,高严重度问题则发出告警后停止,也就是选择性修复。

第三,检测本身也有成本。 计划会针对所有受管资源调用提供商 API,大规模基础设施若频繁执行,可能触发 API 限流。检测时还会获取状态读取锁,与同时进行的部署冲突。此外,计划输出可能混入密码等敏感值,写入日志前必须过滤。

第四,提供商升级后的大规模漂移。 新版本改变属性默认值时,即使没有任何人做过修改,也可能突然出现数百条漂移。此时变化的不是实物,而是提供商;应先固定版本并检查变更日志。

下个练习将做什么

实际设置 create_before_destroyprevent_destroy,确认删除被阻止;再通过 ignore_changes 观察即使修改代码值,计划仍为空。使用 replace_triggered_by 让其他资源的变化触发重新创建,然后绕过代码手动修改实物,制造漂移。通过 -refresh-only 计划和退出码 2 检测漂移:一次按代码基准回滚,另一次沿相反方向把实物变更吸收到代码中。最后编写一个以 JSON 输出检测结果的脚本,让它具备自动化形式。