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

基础设施即代码

两个仓库在来回回滚同一个配置文件

在 TT Lab 中继续学习

目标

重现两份状态争夺同一个资源的情形,用 removed 块整理所有权;再用 import 块、配置生成和 CLI import 把已有的值接管过来而不删除它,让计划为空,开始进行管理。

为什么重要

引入 IaC 时,并不是从一张白纸开始。手工创建的资源已经存在,在把它们迁移到代码的过程中,如果只是照着样子写一遍,工具就会新建一份,把原本使用旧值的地方弄坏。所以必须先用 import 把它登记到状态中,并把代码打磨到计划为空,才算完成。反过来,如果一个资源由两个代码库管理,它们会在没有任何错误提示的情况下相互改回对方的内容,所以移交所有权时,不应删除资源块(即删除资源),而应使用只把它从状态中移出的方法。

步骤

  1. 在 /root/iac-own/team-a/main.tf 和 /root/iac-own/team-b/main.tf 中分别放置 local_file.conf。两者都管理 /root/iac-own/shared/app.conf,内容分别是一行 owner=team-a 和一行 owner=team-b。对各个目录执行 init 之后,按 team-a、team-b、team-a、team-b 的顺序 apply,并在每次 apply 时把文件内容逐行追加到 /root/iac-own/flip.txt。
  2. team-b 决定移交所有权。删除 team-b 的资源块,并放置 removed { from = local_file.conf } 块。在 team-b 中把 tofu plan 的输出保存到 /root/iac-own/forget.txt 后 apply,接着 apply team-a。文件必须保留(team-a 的内容),team-b 的状态中不得有 local_file,两个目录的计划都必须是干净的。
  3. 在 /root/iac-own/legacy.env 中写入手工创建的资源的记录:DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N、PORT=8123、PORT_RANGE=8000,8999、INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f、TOKEN=abcDEF123(每行一个)。然后在 /root/iac-own/naive/main.tf 中不使用 import,写下 random_password.db(长度 20)、random_integer.port(从 8000 到 8999)、random_uuid.instance,并用 tofu plan -out=/root/iac-own/naive/naive.tfplan 只保存而不应用。
  4. 在 /root/iac-own/adopt/main.tf 中只放置对 random provider 的要求和三个 import 块:random_password.db 使用 legacy.env 中的密码,random_integer.port 使用 <값>,<최소>,<최대>(占位符依次为值、最小值、最大值)的格式,random_uuid.instance 以 UUID 作为标识符。不写资源块,用 tofu plan -generate-config-out=generated.tf 生成配置。计划必须是 3 to import, 0 to add, 0 to change, 0 to destroy(不要应用)。
  5. 把 generated.tf 中的三个资源移到 adopt/main.tf,去掉值为 null 的行,也去掉与默认值相同的参数,把它精简(保留密码长度 20、端口范围 8000·8999)。删除 generated.tf。确认计划为 3 to import, 0 to add, 0 to change, 0 to destroy 之后再 apply。评分器会检查状态中的值是否与 legacy.env 相同,以及之后的计划是否干净。
  6. 把 legacy.env 中的 TOKEN 导入为 random_string.token。这次不要使用 import 块,而是先在 /root/iac-own/adopt/token.tf 中放置资源块(长度 9),再用 tofu import 命令登记到状态中。之后计划必须是干净的。
  7. 创建 /root/iac-own/owners.sh <작업디렉터리>...(占位符为工作目录,可以有多个)。从各目录的状态中以绝对路径汇总 local_file·local_sensitive_file 所管理的文件路径,如果同一路径存在于两个以上目录中,就对每个路径各输出一行 CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)>(占位符依次为路径、以空格分隔的已排序绝对目录路径)并以 3 结束;没有则输出 OK 并以 0 结束。相对路径的 filename 以其工作目录为基准解析。评分器会用 team-a·team-b(现在应为 OK)以及它自己创建的冲突目录来检查。

参考

两个团队轮流把同一个文件改回去

在 /root/iac-own/team-a/main.tf 和 /root/iac-own/team-b/main.tf 中分别放置 local_file.conf。两者都管理 /root/iac-own/shared/app.conf,内容分别是一行 owner=team-a 和一行 owner=team-b。对各个目录执行 init 之后,按 team-a、team-b、team-a、team-b 的顺序 apply,并在每次 apply 时把文件内容逐行追加到 /root/iac-own/flip.txt。

两份状态互不知道对方的存在。一方写入之后,在另一方的下一次计划中,这个文件会被看作“在外部被改动”(在 local_file 中是“已删除”),应用之后又会改回自己的内容。最后以 team-b 结束,是为了让下一步中 team-b 在自己的记录仍然有效的状态下放下所有权。这种争夺会悄无声息地一直持续,不产生任何错误。

一方放下所有权——不删除,只从状态中移出

team-b 决定移交所有权。删除 team-b 的资源块,并放置 removed { from = local_file.conf } 块。在 team-b 中把 tofu plan 的输出保存到 /root/iac-own/forget.txt 后 apply,接着 apply team-a。文件必须保留(team-a 的内容),team-b 的状态中不得有 local_file,两个目录的计划都必须是干净的。

只删除资源块的话,工具会把它计划为删除——那样 team-b 就会删掉 team-a 在使用的文件。removed 块的意思是“只是不再管理”,所以实物保持原样。阅读计划输出中的语句,确认两者的区别。

只照样子写已有的值,就会新建一份

在 /root/iac-own/legacy.env 中写入手工创建的资源的记录:DB_PASSWORD=Lb7qK2mZx9Wc4Rt8Yp3N、PORT=8123、PORT_RANGE=8000,8999、INSTANCE_ID=6f1c2a4e-3b7d-4c9a-8e2f-1a2b3c4d5e6f、TOKEN=abcDEF123(每行一个)。然后在 /root/iac-own/naive/main.tf 中不使用 import,写下 random_password.db(长度 20)、random_integer.port(从 8000 到 8999)、random_uuid.instance,并用 tofu plan -out=/root/iac-own/naive/naive.tfplan 只保存而不应用。

即使配置的样子相同,如果状态中没有记录,对工具来说它就是“尚不存在的东西”。应用之后会生成与原有密码不同的新值,使用该值的地方都会坏掉。在计划的 Plan 那一行看到 import 为 0。

使用 import 块,让工具来生成配置

在 /root/iac-own/adopt/main.tf 中只放置对 random provider 的要求和三个 import 块:random_password.db 使用 legacy.env 中的密码,random_integer.port 使用 <값>,<최소>,<최대>(占位符依次为值、最小值、最大值)的格式,random_uuid.instance 以 UUID 作为标识符。不写资源块,用 tofu plan -generate-config-out=generated.tf 生成配置。计划必须是 3 to import, 0 to add, 0 to change, 0 to destroy(不要应用)。

每种资源的 import 标识符格式都不同——查看 provider 文档的 Import 一节。配置生成是实验性功能,如果文件已经存在,不会覆盖,而是报错。

打磨生成的配置,在计划为空的前提下导入

把 generated.tf 中的三个资源移到 adopt/main.tf,去掉值为 null 的行,也去掉与默认值相同的参数,把它精简(保留密码长度 20、端口范围 8000·8999)。删除 generated.tf。确认计划为 3 to import, 0 to add, 0 to change, 0 to destroy 之后再 apply。评分器会检查状态中的值是否与 legacy.env 相同,以及之后的计划是否干净。

在打磨时,如果改动了会强制替换的参数(例如范围的最大值),计划就会变成“导入之后替换”——如果直接应用,导入的值会立刻消失。判断标准是 Plan 那一行中的 add·destroy 是否为 0。

不用块,而用命令再导入一个

把 legacy.env 中的 TOKEN 导入为 random_string.token。这次不要使用 import 块,而是先在 /root/iac-own/adopt/token.tf 中放置资源块(长度 9),再用 tofu import 命令登记到状态中。之后计划必须是干净的。

命令式 import 不经过计划,直接修改状态。所以必须先有配置,也没有可供评审的计划留下来——这正是 import 块被设计成要经过代码评审和计划的原因。

找出有两个所有者的文件的检查

创建 /root/iac-own/owners.sh <작업디렉터리>...(占位符为工作目录,可以有多个)。从各目录的状态中以绝对路径汇总 local_file·local_sensitive_file 所管理的文件路径,如果同一路径存在于两个以上目录中,就对每个路径各输出一行 CONFLICT <경로> <디렉터리들(절대 경로, 정렬, 공백 구분)>(占位符依次为路径、以空格分隔的已排序绝对目录路径)并以 3 结束;没有则输出 OK 并以 0 结束。相对路径的 filename 以其工作目录为基准解析。评分器会用 team-a·team-b(现在应为 OK)以及它自己创建的冲突目录来检查。

从 tofu show -json 的 values 中获得 filename。即使相对路径名称相同,只要目录不同,就是不同的文件。只挑出重复项,用 sort | uniq -d 最方便。