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

Terraform 实战

把状态挪走、删掉、再导进来

在 TT Lab 中继续学习

目标

安全地迁移已经应用的资源的地址,把处于管理之外的资源纳入状态,并用退出码证明最终代码与状态没有出现偏差。

为什么重要

状态文件是工具的记忆。代码说明“应该有什么”,实际资源说明“现在有什么”,状态则说明“我最后知道的是什么”。重构只会改变这三者中的代码,如果不同时迁移状态,工具就会判断旧资源消失、新资源出现,从而规划销毁与创建。所以像重命名这样的小事,在生产环境中也会变成危险的操作。反过来,state rm 与它的名字不同,并不会删除实际资源——它只是从账簿中抹掉的管理放弃声明,如果代码中仍保留着声明,下一次计划就会试图重新创建该资源。只有准确理解这种不对称,状态手术才不可怕。此外,每次手术之前都要先备份副本。能撤销的操作与不能撤销的操作,区别就在这一个文件。

步骤

  1. 在 /root/tf/surgery/main.tf 中声明三个资源并在 tofu init 后应用——local_file.legacy(文件 /root/tf/surgery/files/legacy.txt)、local_file.notes(文件 /root/tf/surgery/files/notes.txt)、random_pet.orphan。然后把状态文件复制为 /root/tf/surgery/backup/terraform.tfstate.bak。副本必须是合法的 JSON,lineage 必须与当前状态相同,且资源至少有 3 个。
  2. 把 tofu state list 的结果保存为 /root/tf/surgery/out/state-list.txt(至少 3 行),把 tofu state show local_file.legacy 的结果保存为 /root/tf/surgery/out/show-legacy.txt。第二个文件中必须能看到 id 属性行。
  3. 用 tofu state mv local_file.legacy local_file.renamed 修改状态中的地址,并在同一次操作中把代码中的资源名称也改为 renamed。旧的 local_file.legacy 块不能只是注释掉,而要彻底删除,工作目录中任何 .tf 文件里都不能残留。如果只迁移状态而保留代码不动,下一次计划就会试图重新创建旧名称的资源。把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-rename.txt,其内容必须是 No changes。
  4. 在 /root/tf/surgery/modules/archive/ 目录中创建模块,以 renamed 为名声明同样的 local_file 资源(文件路径、内容等参数值必须与迁移前完全一致)。在根模块中添加 module "archive" 块并执行 tofu init,然后执行 tofu state mv local_file.renamed module.archive.local_file.renamed,并从根模块的 main.tf 中删除该资源块(这里同样是删除而不是注释掉——资源声明现在只能存在于模块文件中)。把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-module.txt,其内容必须是 No changes。
  5. 执行 tofu state rm random_pet.orphan 之后,把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-rm.txt。计划中必须有要重新创建 random_pet.orphan 的内容(1 to add)。确认之后,也把 main.tf 中该资源块删除,并在 /root/tf/surgery/out/rm-note.txt 中用一行写明 state rm 与 destroy 的区别。
  6. 在 main.tf 中把 resource "terraform_data" "adopted" 声明为空壳,并在它上方放置 import 块,指定 to = terraform_data.adopted 和 id = "labhub-adopted",然后应用。状态中 terraform_data.adopted 的 id 属性必须是 labhub-adopted。
  7. 在 main.tf 中声明 resource "terraform_data" "legacy_job",这次通过命令行执行 tofu import terraform_data.legacy_job labhub-legacy。把输出保存为 /root/tf/surgery/out/import.txt,其中必须能看到 Import successful 字样,状态中 terraform_data.legacy_job 的 id 必须是 labhub-legacy。
  8. 执行 tofu plan -detailed-exitcode,并把仅退出码写入 /root/tf/surgery/out/final-exit.txt(必须是 0)。然后创建 /root/tf/surgery/out/inventory.json,在 addresses 中以数组形式放入当前状态的所有地址,并在 backup_taken 中写入 true。地址个数必须与状态中的实例数完全相同,列表中必须包含以 module.archive. 开头的地址和导入的 adopted 地址。

参考

在手术前保存状态副本

在 /root/tf/surgery/main.tf 中声明三个资源并在 tofu init 后应用——local_file.legacy(文件 /root/tf/surgery/files/legacy.txt)、local_file.notes(文件 /root/tf/surgery/files/notes.txt)、random_pet.orphan。然后把状态文件复制为 /root/tf/surgery/backup/terraform.tfstate.bak。副本必须是合法的 JSON,lineage 必须与当前状态相同,且资源至少有 3 个。

凡是要动状态的操作,都要先复制文件。要确认副本属于同一个状态,请比较 lineage 值。

查看状态列表和单个资源

把 tofu state list 的结果保存为 /root/tf/surgery/out/state-list.txt(至少 3 行),把 tofu state show local_file.legacy 的结果保存为 /root/tf/surgery/out/show-legacy.txt。第二个文件中必须能看到 id 属性行。

列出地址的命令与完整显示某个地址全部属性的命令是不同的。后者的输出中必须能看到 id 属性。

用 state mv 只修改名称

用 tofu state mv local_file.legacy local_file.renamed 修改状态中的地址,并在同一次操作中把代码中的资源名称也改为 renamed。旧的 local_file.legacy 块不能只是注释掉,而要彻底删除,工作目录中任何 .tf 文件里都不能残留。如果只迁移状态而保留代码不动,下一次计划就会试图重新创建旧名称的资源。把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-rename.txt,其内容必须是 No changes。

代码和状态指向同一个名称,计划才会为空。只迁移状态,下一次计划会按旧名称重新创建;只修改代码,则会同时出现销毁和创建。旧名称的块要删除,而不是注释掉。

把资源移入模块

在 /root/tf/surgery/modules/archive/ 目录中创建模块,以 renamed 为名声明同样的 local_file 资源(文件路径、内容等参数值必须与迁移前完全一致)。在根模块中添加 module "archive" 块并执行 tofu init,然后执行 tofu state mv local_file.renamed module.archive.local_file.renamed,并从根模块的 main.tf 中删除该资源块(这里同样是删除而不是注释掉——资源声明现在只能存在于模块文件中)。把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-module.txt,其内容必须是 No changes。

先创建模块块并执行 init,再进行迁移。模块内资源的参数值必须与迁移前完全相同,计划才会为空。

证明 state rm 不会删除实际资源

执行 tofu state rm random_pet.orphan 之后,把 tofu plan 的输出保存为 /root/tf/surgery/out/plan-after-rm.txt。计划中必须有要重新创建 random_pet.orphan 的内容(1 to add)。确认之后,也把 main.tf 中该资源块删除,并在 /root/tf/surgery/out/rm-note.txt 中用一行写明 state rm 与 destroy 的区别。

这个命令只会从账簿中抹掉。请看看代码中仍保留声明时,下一次计划想做什么。

用 import 块纳入资源

在 main.tf 中把 resource "terraform_data" "adopted" 声明为空壳,并在它上方放置 import 块,指定 to = terraform_data.adopted 和 id = "labhub-adopted",然后应用。状态中 terraform_data.adopted 的 id 属性必须是 labhub-adopted。

必须先创建接收的位置(资源块)。在 import 块中写明导入到哪里(to)以及导入什么(id)。

用命令行 import 纳入资源

在 main.tf 中声明 resource "terraform_data" "legacy_job",这次通过命令行执行 tofu import terraform_data.legacy_job labhub-legacy。把输出保存为 /root/tf/surgery/out/import.txt,其中必须能看到 Import successful 字样,状态中 terraform_data.legacy_job 的 id 必须是 labhub-legacy。

这是立即执行同样任务的方式。输出中会留下成功提示,请把它保存为文件。

用退出码证明手术后状态一致

执行 tofu plan -detailed-exitcode,并把仅退出码写入 /root/tf/surgery/out/final-exit.txt(必须是 0)。然后创建 /root/tf/surgery/out/inventory.json,在 addresses 中以数组形式放入当前状态的所有地址,并在 backup_taken 中写入 true。地址个数必须与状态中的实例数完全相同,列表中必须包含以 module.archive. 开头的地址和导入的 adopted 地址。

靠人眼阅读来判断不是自动化。请用计划的退出码来判断,并把状态中的地址集中到一处核对个数。