把状态挪走、删掉、再导进来
目标
安全地迁移已经应用的资源的地址,把处于管理之外的资源纳入状态,并用退出码证明最终代码与状态没有出现偏差。
为什么重要
状态文件是工具的记忆。代码说明“应该有什么”,实际资源说明“现在有什么”,状态则说明“我最后知道的是什么”。重构只会改变这三者中的代码,如果不同时迁移状态,工具就会判断旧资源消失、新资源出现,从而规划销毁与创建。所以像重命名这样的小事,在生产环境中也会变成危险的操作。反过来,state rm 与它的名字不同,并不会删除实际资源——它只是从账簿中抹掉的管理放弃声明,如果代码中仍保留着声明,下一次计划就会试图重新创建该资源。只有准确理解这种不对称,状态手术才不可怕。此外,每次手术之前都要先备份副本。能撤销的操作与不能撤销的操作,区别就在这一个文件。
步骤
- 在
/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 个。 - 把
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属性行。 - 用
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。 - 执行
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的区别。 - 在
main.tf中把resource "terraform_data" "adopted"声明为空壳,并在它上方放置import块,指定to = terraform_data.adopted和id = "labhub-adopted",然后应用。状态中terraform_data.adopted的id属性必须是labhub-adopted。 - 在
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地址。
参考
- 本实验的状态文件是
/root/tf/surgery/terraform.tfstate。手术前的副本务必另外保存。 tofu state mv接收旧地址和新地址作为参数。迁移到模块中时,要在新地址前加上module.<이름>.(占位符为模块名称)。- 新增模块之后,在
state mv之前必须先执行tofu init。无法迁移到尚未安装的模块地址。 -detailed-exitcode在无变更时返回 0,出错时返回 1,有变更时返回 2。要获取退出码,必须在命令之后立即读取$?。state mv和代码修改是一对。只做其中一个,下一次计划就会立即出现偏差——只迁移状态,会按旧名称重新创建资源;只修改代码,则会同时出现销毁和创建。因此在实际工作中,更推荐用一个moved块同时表达这两者。- 常见错误 1:把
state rm误当成删除命令。它只会从账簿中抹掉,所以还要同时清理代码块,计划才会安静下来。 - 常见错误 2:在没有目标资源块的情况下尝试 import。工具会在代码中查找放置该 ID 的位置。
在手术前保存状态副本
在 /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 地址。
靠人眼阅读来判断不是自动化。请用计划的退出码来判断,并把状态中的地址集中到一处核对个数。