读 plan 并自动判定
目标
以文件和 JSON 两种形态处理计划,并以操作、退出码和漂移为依据,让脚本判定“这份计划必须由人来看”。
为什么重要
基础设施代码中最昂贵的错误,是把计划读错。把重新创建(-/+)误当成原地修改(~),资源就会被删除后重新创建,期间的数据一去不复返。然而计划输出很长,部署通常在夜间进行,所以只靠人的眼睛,迟早一定会失败。因此需要两种机制。第一,把计划保存为文件,并原样应用评审过的那份计划——如果不保存,评审过的和实际被应用的可能不同。第二,把计划以机器可读的格式提取出来,用规则来判定——-detailed-exitcode 的 0/1/2,以及 JSON 中的 resource_changes、resource_drift,就是它的素材。完成本实验后,你手里就有了可以放进流水线的素材,用来实现“只要有一个删除就需要人工批准”这类规则。
步骤
- 在
/root/tf/plan中声明两个以上的资源,其中包含一个local_file和一个terraform_data,并初始化。把计划保存为/root/tf/plan/plan.tfplan文件,然后把该计划文件转换为 JSON,保存到/root/tf/plan/out/plan.json。.resource_changes中必须有 2 个以上的条目。 - 统计
plan.json的.resource_changes[].change.actions,创建/root/tf/plan/out/actions.json。它是包含create、update、delete三个键的 JSON,数字必须与计划中统计的值完全相同。 - 在尚未应用之前,加上
-detailed-exitcode运行计划,并把它的退出码(仅退出码)写入/root/tf/plan/out/exitcode-before.txt。然后应用,再次运行同一条命令,把退出码写入/root/tf/plan/out/exitcode-after.txt。两个文件中各只有一个数字。 - 修改
local_file的参数,使其出现删除后重新创建;同时只修改terraform_data的input,使其出现原地修改。生成计划,让这两个变更同时出现在一份计划中,并以 JSON 形式保存到/root/tf/plan/out/replace.json。 - 应用以使状态一致之后,不经过 Terraform,直接在 shell 中修改正在管理的
local_file的内容。在该状态下生成计划,以 JSON 形式保存到/root/tf/plan/out/drift.json。.resource_drift中必须能捕捉到该local_file的地址。 - 制造出有两处以上变更在等待的状态,然后用
-target只针对其中一个生成计划,以 JSON 形式保存到/root/tf/plan/out/target.json(不是no-op的变更恰好有 1 个)。把同一次运行中工具给出的警告文字留存到/root/tf/plan/out/target-warning.txt。 - 把
/opt/lab/fixtures/terraform/broken/main.tf复制到/root/tf/plan/broken/main.tf并原样运行,把错误输出保存到/root/tf/plan/out/broken.txt(必须包含指出参数名contents有问题的消息)。然后把同一个文件复制到/root/tf/plan/fixed/main.tf,把contents改为正确的参数content,并把改好的配置的计划以 JSON 形式保存到/root/tf/plan/out/fixed.json。 - 最后,在
/root/tf/plan的配置中整个删除一个资源,生成包含删除的计划,以 JSON 形式保存到/root/tf/plan/out/final.json。读取该 JSON,创建/root/tf/plan/out/review.json。放入add(操作中含有 create 的变更数)、change(操作为["update"]的变更数)、destroy(操作中含有 delete 的变更数)这三个数字,包含全部删除对象地址的destructive_addresses数组,以及值为needs-review的verdict。
参考
- 计划文件不是供人阅读的格式。用
terraform show -json plan.tfplan转换后,再用jq处理。 -detailed-exitcode的退出码为 0(无变更)、1(错误)、2(有变更)。必须在命令刚结束后读取$?,在设置了set -e的脚本中,还要做处理,避免它先行停止。terraform_data是内置于核心、无需 provider 的资源,所以在这个离线环境中也能使用。只修改input是原地修改,修改triggers_replace则是重新创建。相反,local_file无论修改哪个参数都是重新创建。- 第 7 步的
broken/和fixed/各自是独立的工作目录。每个目录都要单独初始化才会有计划,而错误是输出到标准错误的,所以用2>&1一并存入文件。 - 常见错误 1:把
-target当成日常工作流来使用。工具发出警告,是因为只反映图的一部分,可能使状态残留不一致的情形。 - 常见错误 2:在汇总行中把重新创建统计为一条。重新创建在 add 和 destroy 两边各计 1,所以第 8 步的数字也必须按这个标准来统计才对。
把计划保存为文件并转换为 JSON
在 /root/tf/plan 中声明两个以上的资源,其中包含一个 local_file 和一个 terraform_data,并初始化。把计划保存为 /root/tf/plan/plan.tfplan 文件,然后把该计划文件转换为 JSON,保存到 /root/tf/plan/out/plan.json。.resource_changes 中必须有 2 个以上的条目。
有一个选项可以把计划保留为文件,而这个文件不是供人阅读的格式。找到转换为 JSON 的子命令,把标准输出重定向到文件。
按操作统计数量并记录
统计 plan.json 的 .resource_changes[].change.actions,创建 /root/tf/plan/out/actions.json。它是包含 create、update、delete 三个键的 JSON,数字必须与计划中统计的值完全相同。
每个变更的操作都是以数组形式存放的。用 jq 只挑出该数组中包含特定值的条目来统计即可。三个键必须全部具备。
确认 -detailed-exitcode 的两个值
在尚未应用之前,加上 -detailed-exitcode 运行计划,并把它的退出码(仅退出码)写入 /root/tf/plan/out/exitcode-before.txt。然后应用,再次运行同一条命令,把退出码写入 /root/tf/plan/out/exitcode-after.txt。两个文件中各只有一个数字。
退出码只能在命令刚结束之后读取。如果中间插入了其他命令,值就会改变,而且根据 shell 选项,脚本也可能先行停止。
把原地修改和重新创建放进同一份计划
修改 local_file 的参数,使其出现删除后重新创建;同时只修改 terraform_data 的 input,使其出现原地修改。生成计划,让这两个变更同时出现在一份计划中,并以 JSON 形式保存到 /root/tf/plan/out/replace.json。
local_file 无论修改哪个参数都是重新创建。原地修改需要另一种资源,有一个内置于核心、无需 provider 的资源可以用。
把代码之外的变更捕捉为漂移
应用以使状态一致之后,不经过 Terraform,直接在 shell 中修改正在管理的 local_file 的内容。在该状态下生成计划,以 JSON 形式保存到 /root/tf/plan/out/drift.json。.resource_drift 中必须能捕捉到该 local_file 的地址。
应用以使状态一致之后,只在 shell 中修改产出的文件。在计划 JSON 中,计划内的变更和在查询中发现的变更,会放在不同的字段里。
缩小对象范围并阅读警告
制造出有两处以上变更在等待的状态,然后用 -target 只针对其中一个生成计划,以 JSON 形式保存到 /root/tf/plan/out/target.json(不是 no-op 的变更恰好有 1 个)。把同一次运行中工具给出的警告文字留存到 /root/tf/plan/out/target-warning.txt。
在有两个以上变更在等待时,只针对其中一个。要针对的资源选一个不引用其他资源的——它所依赖的对象会被一并牵扯进来。
阅读有故障的配置的错误并修复
把 /opt/lab/fixtures/terraform/broken/main.tf 复制到 /root/tf/plan/broken/main.tf 并原样运行,把错误输出保存到 /root/tf/plan/out/broken.txt(必须包含指出参数名 contents 有问题的消息)。然后把同一个文件复制到 /root/tf/plan/fixed/main.tf,把 contents 改为正确的参数 content,并把改好的配置的计划以 JSON 形式保存到 /root/tf/plan/out/fixed.json。
错误消息会直接告知参数名。错误输出是输出到标准错误的,所以存入文件时必须一并传入。
创建破坏性变更的评审报告
最后,在 /root/tf/plan 的配置中整个删除一个资源,生成包含删除的计划,以 JSON 形式保存到 /root/tf/plan/out/final.json。读取该 JSON,创建 /root/tf/plan/out/review.json。放入 add(操作中含有 create 的变更数)、change(操作为 ["update"] 的变更数)、destroy(操作中含有 delete 的变更数)这三个数字,包含全部删除对象地址的 destructive_addresses 数组,以及值为 needs-review 的 verdict。
按与汇总行中的 add·change·destroy 相同的标准来统计即可。注意:重新创建会同时计入 add 和 destroy 两边,并且删除对象地址一个也不能漏。