TT Lab
开始
学习 学习路径 课程

Terraform/OpenTofu 基础

只改状态的命令和销毁的顺序

在 TT Lab 中继续学习

一句话总结

计划同时做两种比较:代码与状态之间,以及状态与实物之间。刷新类命令只把后一种比较单独拿出来,而销毁则是把那张图倒着走一遍。

为什么需要拆开

一次 apply 其实包含三件事:读取实物,把状态更新到最新;把这个状态与代码比较,确定要做什么;执行确定好的事。大多数情况下,这样打包在一起很方便。但也有因为打包在一起而麻烦的情况。

第一,只有状态是错的。假设有人在控制台中手工删掉了某个资源,或者改了名称。代码完好无损,实物(如果承认那次变更的话)也完好无损,只有状态在说过时的话。这时如果直接应用,工具会重新创建不存在的东西,或者覆盖已经存在的东西。需要的只是“让状态与实物一致”。

第二,反过来,读取实物的成本可能很高。在有数千个资源的仓库中,光是刷新就要花好几分钟。着急的时候,会很想跳过这一步直接生成计划。

于是就有了把两种比较分开处理的选项。

工作原理

刷新专用的计划根本不看代码。它只比较状态和实物,并显示应如何修改状态。

Note: Objects have changed outside of OpenTofu

  # local_file.conf has been deleted
  - resource "local_file" "conf" {

在这里应用,改变的只有状态。实物一点也不会被触碰,之前的状态会保留在备份文件中。状态的序列号会增加,消失的条目会从状态中移除。

不读取实物的计划则正好相反。它把状态中记录的值当作事实,所以无论外部发生了什么变化,它都不知道。

No changes. Your infrastructure matches the configuration.

在同一时刻如果直接运行计划,会得到完全不同的说法:重新创建消失的东西,并把引用了该值的东西也一并替换。同一份代码、同一个状态、同一个时刻,结论却不同——差别仅在于是否读取了实物。所以不应该为了速度而使用这个选项,再拿这样生成的计划去获得批准。

再来看销毁。创建时,工具沿着依赖图从前往后创建:先有网络才有数据库,有了数据库才有应用。删除时必须正好是它的逆序。如果应用仍然指向数据库,却先删掉了数据库,剩下的一方就会指向一个不存在的东西。

销毁计划也可以像普通计划一样保存为文件。越是无法撤销的操作,越适合先保存并评审,然后只应用那个文件。保存下来的计划文件还可以用机器可读的格式查看,把将要删除的东西提取成列表,附在审批上。

缩小对象范围的选项则不同。用了它,就意味着由人来承担工具平时所守护的整张图的一致性,所以会附带警告。

Warning: Resource targeting is in effect
Warning: Applied changes may be incomplete

最后,即使全部删除,状态文件本身也不会消失。只是条目列表和输出变空,序列号增加,而该状态的谱系编号不变。在同一个位置再次应用,就会延续同一份记录。删除状态文件与删除资源完全是两回事——删掉它,实物仍在,只有记录消失,工具就会把这些东西当作不认识的。

在现场相遇的样子

代价最高的事故,是在只有状态不一致的情形下直接应用。有人在生产环境中手工修改过的资源,被工具重新创建,期间流量中断。养成先运行刷新专用计划、判断“是代码的问题还是状态的问题”的习惯,就能防止这种事故。

第二是快速计划的陷阱。在大型仓库中,如果把关闭刷新的计划放进 CI,评审虽然变快了,但外部产生的变更要到批准之后才会暴露。如果需要速度,折中的办法是在应用之前再运行一次读取实物的计划。

第三是缩小对象范围的应用成了习惯。紧急时用过一次的选项,一旦成了团队的默认流程,就会变成一个整张图从未保持一致的仓库。用过之后,一定要运行一次不缩小范围的计划,确认它是干净的。

第四是一行命令就结束销毁的习惯。删除是无法撤销的,如果只经过一次交互确认就结束,以后就没有人能重建出究竟删掉了什么。如果把计划保存为文件,并用机器可读的格式提取出列表,就留下了审批依据,出事故时也能一次得出需要恢复什么。应用保存为文件的计划,评审的内容与实际操作之间也不会有出入。

下一项实验要做什么

走完八个步骤。先创建基线,不经过工具直接修改文件,然后生成刷新专用计划,并把不读取实物的计划与读取实物的计划并排保存以作比较。接着只应用刷新,确认状态的序列号和条目数如何变化,以及手工修改过的文件是否保持原样。后面四个步骤,创建三级链条,把销毁计划保存为文件,应用这个文件,通过日志确认删除的顺序;阅读缩小对象范围的销毁所给出的警告;最后对比备份,看看全部删除之后状态中还剩下什么。