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

Terraform/OpenTofu 基础

依赖图与状态文件的解剖

在 TT Lab 中继续学习

目标

在状态文件中亲自确认引用是如何构成依赖图的,并学会以人和机器两种方式读取状态,提取出元数据。

为什么重要

用了几天 Terraform 之后,“顺序为什么是这样”这个问题必然会出现。答案永远是图。工具不看文件中写的顺序,只看哪个资源使用了哪个资源的值。所以引用值就会产生顺序,抄写值顺序就消失了。depends_on 是用来表达无法通过值体现的顺序的逃生口,但一旦成了习惯,图就会变粗,执行会变慢,重新创建也会向旁边扩散。另一方面,这种关系会在状态文件中以 dependencies 记录下来,这就是即使从代码中删除了资源,也能按正确的逆序删除的依据。由此也能明白,为什么手工编辑状态是危险的。

步骤

  1. 在 /root/tf/state 中创建配置并初始化。声明名称为 seed 的 random_pet 和名称为 child 的 local_file,并让 child 的内容引用 random_pet.seed 的值。不要给 child 使用 depends_on。应用之后,状态中 local_file.child 的 dependencies 里必须有 random_pet.seed。
  2. 添加名称为 marker 的 local_file,并在 /root/tf/state/main.tf 中写入 depends_on = [local_file.child] 来固定顺序。应用之后,状态中 local_file.marker 的 dependencies 里必须能看到 local_file.child。
  3. 把状态中登记的资源地址列表保存到 /root/tf/state/out/state-list.txt。random_pet.seed、local_file.child、local_file.marker 这三行必须各自原样包含该字符串。
  4. 把状态的 JSON 表示保存到 /root/tf/state/out/state.json。.values.root_module.resources 之下必须有 3 个以上的资源,并且其中必须有 address 为 local_file.child 的条目。
  5. 创建 /root/tf/state/out/meta.json。它有 serial、lineage、version、resource_count 四个键,前三个必须与 /root/tf/state/terraform.tfstate 中的值相同,resource_count 必须与状态中 resources 数组的长度相同。
  6. 不经过 Terraform,在 shell 中直接删除 child 所创建的文件,然后在该状态下生成计划,保存到 /root/tf/state/out/drift-plan.txt。输出中必须有重新创建 local_file.child 的内容,不能是“无变更”。
  7. 阅读 /root/tf/state/.terraform.lock.hcl,把其中固定的 provider 名称和版本整理成一行,保存到 /root/tf/state/out/lock-note.txt。必须同时包含名称 local 和类似 2.5.3 的三段版本号。
  8. 添加名称为 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。

参考

仅靠引用建立依赖

在 /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。

让后一步引用前一步的结果,就成了链。要记录的顺序不是代码中书写的顺序,而是创建的顺序。