依赖图与状态文件的解剖
目标
在状态文件中亲自确认引用是如何构成依赖图的,并学会以人和机器两种方式读取状态,提取出元数据。
为什么重要
用了几天 Terraform 之后,“顺序为什么是这样”这个问题必然会出现。答案永远是图。工具不看文件中写的顺序,只看哪个资源使用了哪个资源的值。所以引用值就会产生顺序,抄写值顺序就消失了。depends_on 是用来表达无法通过值体现的顺序的逃生口,但一旦成了习惯,图就会变粗,执行会变慢,重新创建也会向旁边扩散。另一方面,这种关系会在状态文件中以 dependencies 记录下来,这就是即使从代码中删除了资源,也能按正确的逆序删除的依据。由此也能明白,为什么手工编辑状态是危险的。
步骤
- 在
/root/tf/state中创建配置并初始化。声明名称为seed的random_pet和名称为child的local_file,并让child的内容引用random_pet.seed的值。不要给child使用depends_on。应用之后,状态中local_file.child的dependencies里必须有random_pet.seed。 - 添加名称为
marker的local_file,并在/root/tf/state/main.tf中写入depends_on = [local_file.child]来固定顺序。应用之后,状态中local_file.marker的dependencies里必须能看到local_file.child。 - 把状态中登记的资源地址列表保存到
/root/tf/state/out/state-list.txt。random_pet.seed、local_file.child、local_file.marker这三行必须各自原样包含该字符串。 - 把状态的 JSON 表示保存到
/root/tf/state/out/state.json。.values.root_module.resources之下必须有 3 个以上的资源,并且其中必须有address为local_file.child的条目。 - 创建
/root/tf/state/out/meta.json。它有serial、lineage、version、resource_count四个键,前三个必须与/root/tf/state/terraform.tfstate中的值相同,resource_count必须与状态中resources数组的长度相同。 - 不经过 Terraform,在 shell 中直接删除
child所创建的文件,然后在该状态下生成计划,保存到/root/tf/state/out/drift-plan.txt。输出中必须有重新创建local_file.child的内容,不能是“无变更”。 - 阅读
/root/tf/state/.terraform.lock.hcl,把其中固定的 provider 名称和版本整理成一行,保存到/root/tf/state/out/lock-note.txt。必须同时包含名称local和类似2.5.3的三段版本号。 - 添加名称为
stage_a、stage_b、stage_c的三个local_file,并让stage_b引用stage_a的值,stage_c引用stage_b的值。应用之后,按创建的顺序,把local_file.stage_a、local_file.stage_b、local_file.stage_c每行一个、共 3 行写入/root/tf/state/out/chain.txt。
参考
terraform state list只显示地址,terraform show -json则以机器可读的格式显示整个状态。后者与jq搭配使用。- 第 5 步可以用
jq提取.serial、.lineage、.version、(.resources|length)并组装成新的 JSON,这样能避免手抄的错误。 - 第 6 步的计划输出是标准输出。在重新应用之前保存为文件。
- 常见错误 1:为了对齐顺序,给所有资源都加上
depends_on。只要有引用,依赖就已经存在了,重复的depends_on只会让图变粗。 - 常见错误 2:在第 3 步和第 8 步中,在地址前后留下空格或引号。评分检查的是整行是否完全一致。
仅靠引用建立依赖
在 /root/tf/state 中创建配置并初始化。声明名称为 seed 的 random_pet 和名称为 child 的 local_file,并让 child 的内容引用 random_pet.seed 的值。不要给 child 使用 depends_on。应用之后,状态中 local_file.child 的 dependencies 里必须有 random_pet.seed。
用表达式使用其他资源的属性,本身就是在声明顺序。在这一步中不要使用 depends_on,而是通过状态的 dependencies 数组是否被填充来确认。
用 depends_on 固定顺序
添加名称为 marker 的 local_file,并在 /root/tf/state/main.tf 中写入 depends_on = [local_file.child] 来固定顺序。应用之后,状态中 local_file.marker 的 dependencies 里必须能看到 local_file.child。
有一个参数,用于不使用值、只需要顺序的情形。它的值是资源地址的列表,不要用引号括起来。
提取状态地址列表
把状态中登记的资源地址列表保存到 /root/tf/state/out/state-list.txt。random_pet.seed、local_file.child、local_file.marker 这三行必须各自原样包含该字符串。
有一个子命令,可以逐行只显示状态中登记的地址。文件中不得混入地址以外的修饰。
把状态导出为 JSON
把状态的 JSON 表示保存到 /root/tf/state/out/state.json。.values.root_module.resources 之下必须有 3 个以上的资源,并且其中必须有 address 为 local_file.child 的条目。
供人阅读的输出与供机器阅读的输出是不同的选项。用 jq 摸索一下,资源列表位于 JSON 中的哪条路径。
记录状态元数据
创建 /root/tf/state/out/meta.json。它有 serial、lineage、version、resource_count 四个键,前三个必须与 /root/tf/state/terraform.tfstate 中的值相同,resource_count 必须与状态中 resources 数组的长度相同。
需要四个键。不要用眼睛抄写值,而是直接从状态文件中提取并组装,就不会出错。资源数量就是数组的长度。
看看手工删除的文件能否在计划中被捕捉到
不经过 Terraform,在 shell 中直接删除 child 所创建的文件,然后在该状态下生成计划,保存到 /root/tf/state/out/drift-plan.txt。输出中必须有重新创建 local_file.child 的内容,不能是“无变更”。
代码保持不变,只去掉产出物,差异就会在查询阶段暴露出来。重新应用之前,先保存计划输出。
从锁文件中读取 provider 版本
阅读 /root/tf/state/.terraform.lock.hcl,把其中固定的 provider 名称和版本整理成一行,保存到 /root/tf/state/out/lock-note.txt。必须同时包含名称 local 和类似 2.5.3 的三段版本号。
锁文件中有 provider 块、确定的版本以及哈希。一边思考哈希保证的是什么,一边整理成一行。
创建三级依赖链并记录顺序
添加名称为 stage_a、stage_b、stage_c 的三个 local_file,并让 stage_b 引用 stage_a 的值,stage_c 引用 stage_b 的值。应用之后,按创建的顺序,把 local_file.stage_a、local_file.stage_b、local_file.stage_c 每行一个、共 3 行写入 /root/tf/state/out/chain.txt。
让后一步引用前一步的结果,就成了链。要记录的顺序不是代码中书写的顺序,而是创建的顺序。