周五晚上手动改的规则,周一的 apply 想把它删掉
目标
用 plan -refresh-only 单独读取在代码之外产生的变更,在计划 JSON 中找出原样应用就会消失的内容,亲手完成吸收、回退、排除这三种处理,并创建每天运行的检测脚本。
为什么重要
声明式工具以代码为真相,所以即使是在控制台中做的正确修改,也会在下一次应用时被改回去。因此发现漂移后,必须先读出将要消失的内容,再由人来决定是吸收还是回退。ignore_changes 并不是能隐藏所有漂移的开关,provider 不读取的属性发生变更,计划中根本不会出现。只有了解这一局限,才能在检测结果为空时分清“一致”和“看不见”。
步骤
- 在
/root/iac-drift/main.tf中放置rules变量(默认值["allow 443"])、按每条规则一行写入out/firewall.rules的local_file.fw,以及向out/motd.txt写入一行maintenance window: sun 02:00的local_file.motd,然后执行 init·apply。 - 在故障响应过程中,有人手工在
/root/iac-drift/out/firewall.rules末尾加了一行allow 8443(自己加上)。不要动代码,用tofu plan -refresh-only只查看在外部产生的变更,把输出保存到/root/iac-drift/drift.txt。 - 不要修改代码,用
tofu plan -out=/root/iac-drift/monday.tfplan保存计划(不要应用)。把该计划将要写入firewall.rules的内容与当前文件比较,只把应用后会消失的行写入/root/iac-drift/lost.txt。 - 判断这次紧急修改是正确的。把
rules的默认值改为["allow 443", "allow 8443"]并 apply。之后firewall.rules必须是两行,且计划必须是干净的。 - 这次有人把
out/motd.txt改成了maintenance window: none(自己改)。这是错误的变更。在回退之前,把tofu plan -refresh-only的输出保存到/root/iac-drift/revert-drift.txt,然后保持代码不变,执行 apply,把公告改回代码中的内容。 - 在
main.tf中加入写入out/cache.conf的cache v1、并设置了lifecycle { ignore_changes = [content] }的local_file.cache,然后 apply。(a)用手把文件改成cache tampered后,把tofu plan的输出保存到/root/iac-drift/ignore.txt,并用 apply 改回去。(b)把代码中的内容改为cache v2,查看计划是否干净(不要应用)。(c)执行chmod 600 out/firewall.rules之后,把tofu plan -detailed-exitcode的退出码用perm=<코드>(占位符为退出码)写入/root/iac-drift/perm.txt。 - 创建
/root/iac-drift/detect.sh <작업디렉터리>(占位符为工作目录)。把plan -refresh-only保存为计划文件,汇总在外部发生变化的资源的地址:没有漂移则输出OK并以 0 结束;有漂移则输出DRIFT <주소들(공백 구분, 정렬)>(占位符为按空格分隔、已排序的资源地址)一行并以 2 结束;失败则输出ERROR并以 1 结束。不要在工作目录中留下文件。评分器会在/root/iac-drift的副本中不断修改文件来检查。
参考
- Pod 中有 OpenTofu 1.9.0 和 local provider 镜像源,无需互联网即可运行。
- 当内容与记录不同时,local_file 会把文件报告为“已删除”,并生成重新创建的计划(在这个 Pod 中实测)。云资源的 provider 通常会按属性级别的差异(原地修改)来报告,所以界面的样子不同,但吸收、回退、排除的判断是一样的。
- 只查看在外部产生的变更:
tofu plan -refresh-only;只把它接受到状态里:tofu apply -refresh-only。 - 常见错误:一看到漂移就立刻 apply,把正确的紧急修改抹掉了。常见错误:以为加上 ignore_changes 之后就会从检测中排除。
- tofu plan (-refresh-only) · tofu refresh · lifecycle (ignore_changes) · JSON Output Format (resource_drift)
基线:用代码应用防火墙规则和公告
在 /root/iac-drift/main.tf 中放置 rules 变量(默认值 ["allow 443"])、按每条规则一行写入 out/firewall.rules 的 local_file.fw,以及向 out/motd.txt 写入一行 maintenance window: sun 02:00 的 local_file.motd,然后执行 init·apply。
用 for 表达式给每条规则加上换行,再用 join 连接起来即可。应用之后计划为空,才算基线。
在计划中查看周五晚上的紧急修改
在故障响应过程中,有人手工在 /root/iac-drift/out/firewall.rules 末尾加了一行 allow 8443(自己加上)。不要动代码,用 tofu plan -refresh-only 只查看在外部产生的变更,把输出保存到 /root/iac-drift/drift.txt。
-refresh-only 不会计划代码的变更,只显示真实情况与状态之间的差异。如果文件内容与记录不同,local_file provider 会把这个文件报告为“已删除”——在输出中确认,它并不会显示成某一个属性被改变。
周一如果直接 apply,会有什么消失
不要修改代码,用 tofu plan -out=/root/iac-drift/monday.tfplan 保存计划(不要应用)。把该计划将要写入 firewall.rules 的内容与当前文件比较,只把应用后会消失的行写入 /root/iac-drift/lost.txt。
计划 JSON 的 resource_changes 中,local_file.fw 的 change.after.content 就是应用之后的文件内容。只存在于当前文件中的行,就是会消失的行。可以用 comm 或 grep -vxF -f 求出按行的差异。
修改是正确的——吸收进代码
判断这次紧急修改是正确的。把 rules 的默认值改为 ["allow 443", "allow 8443"] 并 apply。之后 firewall.rules 必须是两行,且计划必须是干净的。
吸收,就是让代码符合现实。由于这个 provider 把文件视为“已删除”,所以修改代码之后的计划也是创建而不是修改——写入的内容与当前文件相同,所以结果是一样的。
修改是错误的——按代码改回去
这次有人把 out/motd.txt 改成了 maintenance window: none(自己改)。这是错误的变更。在回退之前,把 tofu plan -refresh-only 的输出保存到 /root/iac-drift/revert-drift.txt,然后保持代码不变,执行 apply,把公告改回代码中的内容。
回退,就是让现实符合代码。回退之前先确认为什么会有这样的变更,本身就是流程的一部分——这里通过保存 drift 输出来留下这份痕迹。
ignore_changes 能拦住的、拦不住的,以及根本看不到的
在 main.tf 中加入写入 out/cache.conf 的 cache v1、并设置了 lifecycle { ignore_changes = [content] } 的 local_file.cache,然后 apply。(a)用手把文件改成 cache tampered 后,把 tofu plan 的输出保存到 /root/iac-drift/ignore.txt,并用 apply 改回去。(b)把代码中的内容改为 cache v2,查看计划是否干净(不要应用)。(c)执行 chmod 600 out/firewall.rules 之后,把 tofu plan -detailed-exitcode 的退出码用 perm=<코드>(占位符为退出码)写入 /root/iac-drift/perm.txt。
ignore_changes 的意思是“即使配置中所写的值发生变化,也不要生成修改计划”。它拦不住 provider 读取实物后判断它已经不存在。而且,provider 不读取的属性发生变更,不会被当作漂移捕捉到。
每天早上运行的漂移检测
创建 /root/iac-drift/detect.sh <작업디렉터리>(占位符为工作目录)。把 plan -refresh-only 保存为计划文件,汇总在外部发生变化的资源的地址:没有漂移则输出 OK 并以 0 结束;有漂移则输出 DRIFT <주소들(공백 구분, 정렬)>(占位符为按空格分隔、已排序的资源地址)一行并以 2 结束;失败则输出 ERROR 并以 1 结束。不要在工作目录中留下文件。评分器会在 /root/iac-drift 的副本中不断修改文件来检查。
计划 JSON 中除了 resource_changes 之外,还有 resource_drift——在外部产生的变更会单独放在那里。-detailed-exitcode 可以与 -refresh-only 一起使用。