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

Terraform 实战

从 count 到 for_each,不销毁地迁过去

在 TT Lab 中继续学习

目标

通过状态地址确认 count 与 for_each 的区别,并掌握如何在不销毁的前提下,把已经应用的资源迁移为 for_each。

为什么重要

count 和 for_each 看起来像是语法选择,实际上是在决定以什么作为每个实例的标识符。count 用顺序作标识符,for_each 用名称作标识符。从列表中删掉中间的一项,差异就会显现——count 会让后面的实例依次前移一位,连本应保留的资源也被重新创建,而 for_each 只会让对应的键消失。在真实的云环境中,这个差异会表现为“只删了一行配置,两台服务器却被重新创建了”。需要修改已应用资源的地址时,请使用 moved 块。moved 只迁移状态,不动实际资源,而且重构记录会留在代码中,其他人只要运行 plan 就会自动跟进。判断迁移是否正确只有一个标准——计划中不能有任何一项销毁。

步骤

  1. 在 /root/tf/foreach/main.tf 中用 count = 3 声明 local_file 资源 node,执行 tofu init 后应用。然后在迁移地址之前,把 tofu state list 的结果保存为 /root/tf/foreach/out/count-state.txt。这个文件中必须有 local_file.node[0]、local_file.node[1]、local_file.node[2] 三行(不能出现字符串键的形式),它是第 7 步迁移前的快照。
  2. 把 node 的文件名设为 /root/tf/foreach/out/node-${count.index}.txt,内容设为 node-${count.index}。不要手写三遍名称,必须用循环索引生成。out 下的 node-*.txt 必须恰好有 3 个,每个文件的第一行依次为 node-0、node-1、node-2。
  3. 用 for_each = toset(["kr", "us", "eu"]) 创建 local_file 资源 zone,使其生成 /root/tf/foreach/out/zone-<키>.txt(占位符为键)共三个文件。状态中的键必须是 eu、kr、us。
  4. 遍历映射来创建 local_file 资源 service。键为 api、web、worker,每个值中包含端口(web 的端口必须是 8080)。/root/tf/foreach/out/service-web.txt 中必须分别用一行写入 name=web 和 port=8080。
  5. 用 for 表达式定义输出 service_ports,以服务名称为键、端口为值,并把 tofu output -json 的结果保存为 /root/tf/foreach/out/outputs.json。条目必须为 3 个,web 必须是 8080。
  6. 创建 variable "enable_debug"(bool,默认值 false),并在 local_file 资源 debug 中使用 count = var.enable_debug ? 1 : 0。文件路径为 /root/tf/foreach/out/debug.txt,内容任意(开关最多只有一个实例,不会出现索引错位,这就是仍然可以使用 count 的原因)。把打开开关的计划(tofu plan -var enable_debug=true)的输出保存为 /root/tf/foreach/out/toggle.txt(其中必须能看到 1 to add),但不要应用。最终状态中 debug 实例必须为 0 个,/root/tf/foreach/out/debug.txt 也不能存在。
  7. 把 node 改为 for_each = { n0 = 0, n1 = 1, n2 = 2 }。映射的键用于新地址,值是旧索引,因此文件名和内容必须用 each.value 生成,而不是 each.key,使 node-0.txt、node-1.txt、node-2.txt 及其内容与第 1–2 步完全一致(改了名称就不是迁移而是重新创建,计划中会出现销毁)。然后用 3 个 moved 块把旧地址与新地址连接起来,例如把 local_file.node[0] 迁移到 local_file.node["n0"]。应用之后,把 tofu plan 的输出保存为 /root/tf/foreach/out/after-plan.txt,其内容必须是 No changes,且不能有任何一项销毁。
  8. 创建 /root/tf/foreach/out/addresses.json。在 addresses 键中以数组形式放入 tofu state list 的地址(node 3 个 + zone 3 个 + service 3 个 = 至少 9 个),带数字索引的地址一个都不能有。在 numeric_index_count 键中写入带数字索引的地址个数,迁移完成的话是 0。

参考

用 count 创建三个,并留下迁移前的地址

在 /root/tf/foreach/main.tf 中用 count = 3 声明 local_file 资源 node,执行 tofu init 后应用。然后在迁移地址之前,把 tofu state list 的结果保存为 /root/tf/foreach/out/count-state.txt。这个文件中必须有 local_file.node[0]、local_file.node[1]、local_file.node[2] 三行(不能出现字符串键的形式),它是第 7 步迁移前的快照。

在资源块中写入 count = 3,就会产生三个实例。稍后要迁移地址,所以请现在就把地址列表保存为文件——没有迁移前的记录,就无法证明什么被迁移成了什么。

用 count.index 区分实例

把 node 的文件名设为 /root/tf/foreach/out/node-${count.index}.txt,内容设为 node-${count.index}。不要手写三遍名称,必须用循环索引生成。out 下的 node-*.txt 必须恰好有 3 个,每个文件的第一行依次为 node-0、node-1、node-2。

每个实例的文件名必须不同,所以把索引嵌入名称。内容也用同样的方式区分。

使用遍历集合的 for_each

用 for_each = toset(["kr", "us", "eu"]) 创建 local_file 资源 zone,使其生成 /root/tf/foreach/out/zone-<키>.txt(占位符为键)共三个文件。状态中的键必须是 eu、kr、us。

列表不能直接传给 for_each。必须转换为集合,此时键就是元素值本身。

遍历映射的 for_each 与 each.value

遍历映射来创建 local_file 资源 service。键为 api、web、worker,每个值中包含端口(web 的端口必须是 8080)。/root/tf/foreach/out/service-web.txt 中必须分别用一行写入 name=web 和 port=8080。

传入映射时,each.key 是映射的键,each.value 是对应的值。值是对象时,可以继续取它的字段。

用 for 表达式构造输出映射

用 for 表达式定义输出 service_ports,以服务名称为键、端口为值,并把 tofu output -json 的结果保存为 /root/tf/foreach/out/outputs.json。条目必须为 3 个,web 必须是 8080。

for_each 是用来增加资源的元参数,for 是把值转换为另一种形状的表达式。请使用 { for k, v in ... : k => ... } 的形式。

用条件 count 做功能开关

创建 variable "enable_debug"(bool,默认值 false),并在 local_file 资源 debug 中使用 count = var.enable_debug ? 1 : 0。文件路径为 /root/tf/foreach/out/debug.txt,内容任意(开关最多只有一个实例,不会出现索引错位,这就是仍然可以使用 count 的原因)。把打开开关的计划(tofu plan -var enable_debug=true)的输出保存为 /root/tf/foreach/out/toggle.txt(其中必须能看到 1 to add),但不要应用。最终状态中 debug 实例必须为 0 个,/root/tf/foreach/out/debug.txt 也不能存在。

있으면 1, 없으면 0(韩文,意为“有则为 1,无则为 0”)是开关的惯用写法。开启后的状态只通过计划来确认,不要真正应用。

用 moved 块把 count 迁移为 for_each

把 node 改为 for_each = { n0 = 0, n1 = 1, n2 = 2 }。映射的键用于新地址,值是旧索引,因此文件名和内容必须用 each.value 生成,而不是 each.key,使 node-0.txt、node-1.txt、node-2.txt 及其内容与第 1–2 步完全一致(改了名称就不是迁移而是重新创建,计划中会出现销毁)。然后用 3 个 moved 块把旧地址与新地址连接起来,例如把 local_file.node[0] 迁移到 local_file.node["n0"]。应用之后,把 tofu plan 的输出保存为 /root/tf/foreach/out/after-plan.txt,其内容必须是 No changes,且不能有任何一项销毁。

地址一变,工具就会把它读成销毁加创建。用 moved 连接旧地址和新地址,并保持文件名和内容不变,才不会重新创建。

制作完整的地址列表和迁移完成报告

创建 /root/tf/foreach/out/addresses.json。在 addresses 键中以数组形式放入 tofu state list 的地址(node 3 个 + zone 3 个 + service 3 个 = 至少 9 个),带数字索引的地址一个都不能有。在 numeric_index_count 键中写入带数字索引的地址个数,迁移完成的话是 0。

把状态列表转换成 JSON 数组放进去。请自己数一数,确认没有任何数字索引地址残留。