安全规则复制到三处,偏偏漏了 prod
目标
把已在运行的两个复制粘贴环境,用 moved 在不替换的情况下改为模块调用;用 git 标签固定模块,只让 dev 使用新版本;然后确认变量值传入路径的优先级,以及模块边界上的输入验证。
为什么重要
复制粘贴出来的环境,每修改一处,就制造一次漏掉其余环境的机会。但是,如果把已经应用的代码迁移到模块时资源地址发生变化,工具就会想删除再重新创建,所以重构必须同时让状态知道发生了迁移。模块没有版本,一修改模块,所有环境就会同时改变,出现“无关的环境坏掉了”;如果不了解值通过多条路径传入的规则,做实验时留下的一个文件就会悄悄改变生产值。
步骤
- 在
/root/iac-mod/dev/main.tf和/root/iac-mod/prod/main.tf中放置样子相同的两个资源。local_file.config向out/app.conf写入name=api、env=<환경>、replicas=<수>(占位符分别为环境名称、副本数)三行(dev 为 1,prod 为 3),local_file.banner向out/banner.txt写入一行api (<환경>)(占位符为环境名称)。对两个目录都执行 init·apply。 - 在
/root/iac-mod/modules/web/main.tf中创建模块:输入为env(字符串)·replicas(数字),包含同样的两个资源,输出为config_path。dev 的 main.tf 放置replicas变量(默认 1),用source = "../modules/web"调用module "web",并用两个moved块把旧地址迁移到module.web.local_file.config·module.web.local_file.banner。执行 init 之后,计划中必须没有创建·删除·替换,然后直接 apply。 - 也把
/root/iac-mod/prod/main.tf改为调用同一个模块(replicas默认 3,env 为prod,两个 moved)。计划中必须没有创建·删除·替换,然后 apply。现在要修改规则,只需改模块这一处。 - 把
/root/iac-mod/modules/web创建为 git 仓库,提交现在的内容,并打上标签v1.0.0。把 dev·prod 的 source 改为git::file:///root/iac-mod/modules/web?ref=v1.0.0,执行tofu init -upgrade之后,两份计划都必须是干净的。 - 给模块再加两项内容并提交,打上标签
v2.0.0:在 config 内容末尾加一行owner=platform,以及检查replicas是否在 1 以上、10 以下的 validation。只把 dev 升级到ref=v2.0.0并执行 init·apply,prod 保持在 v1.0.0,计划必须是干净的。 - 在 dev 中通过多条路径传入
replicas的值,用echo var.replicas | tofu console确认实际值,并写入/root/iac-mod/precedence.txt。所有情形下都存在环境变量TF_VAR_replicas=9。env_only(无文件)、with_tfvars(terraform.tfvars 中为 2)、with_auto(在此基础上追加 a.auto.tfvars 中的 3 和 b.auto.tfvars 中的 4)、with_var_file(在此基础上加-var-file=ops.tfvars,值为 6)、with_var(-var-file=ops.tfvars -var replicas=5的顺序)、var_then_file(-var replicas=5 -var-file=ops.tfvars的顺序)这六行,按이름=값(韩文,意为“名称=值”)的格式写出。结束后,删除 a·b.auto.tfvars 和 ops.tfvars,只留下 terraform.tfvars(2)并 apply。 - 在 dev 中运行
tofu plan -var replicas=0,把输出(包括错误)保存到/root/iac-mod/invalid.txt。评分器会在 dev 的副本中检查 0 和 11 被拒绝,并检查在 prod(v1.0.0,没有检查)中 0 能通过。dev 和 prod 的计划要保持干净。
参考
- Pod 中有 OpenTofu 1.9.0、local provider 镜像源和 git,无需互联网即可运行。模块仓库是
/root/iac-mod/modules/web中的本地 git 仓库。 - 更改模块的 source 或 ref 之后,必须重新执行
tofu init(升级版本时加上-upgrade)。 - 这个 Pod 的 git 中没有配置用户名和邮箱。提交时像
git -c user.name=… -c user.email=… commit这样传入。 - 常见错误:在模块内部用 path.module 指定文件路径,导致 moved 之后又跟着发生替换。常见错误:把优先级实验用的文件(*.auto.tfvars)留在那里。
- Refactoring(moved) · Module Sources(git, ref) · Input Variables(优先级·validation) · Terraform: Input Variables
先把复制粘贴的两个环境应用好
在 /root/iac-mod/dev/main.tf 和 /root/iac-mod/prod/main.tf 中放置样子相同的两个资源。local_file.config 向 out/app.conf 写入 name=api、env=<환경>、replicas=<수>(占位符分别为环境名称、副本数)三行(dev 为 1,prod 为 3),local_file.banner 向 out/banner.txt 写入一行 api (<환경>)(占位符为环境名称)。对两个目录都执行 init·apply。
这一步是在创建“复制粘贴的代码已经在运行”的起点。确认两个文件的差别只有环境名称和副本数——它们就是模块的输入。
把 dev 改为模块调用,但不重新创建任何东西
在 /root/iac-mod/modules/web/main.tf 中创建模块:输入为 env(字符串)·replicas(数字),包含同样的两个资源,输出为 config_path。dev 的 main.tf 放置 replicas 变量(默认 1),用 source = "../modules/web" 调用 module "web",并用两个 moved 块把旧地址迁移到 module.web.local_file.config·module.web.local_file.banner。执行 init 之后,计划中必须没有创建·删除·替换,然后直接 apply。
模块中的 path.module 是模块目录。由于文件必须位于调用它的环境目录之下,所以使用 path.root——只要这个路径与旧值有一个字符不同,迁移之后就会跟着发生替换。添加或修改模块之后,必须重新执行 init。
把 prod 也迁移到同一个模块
也把 /root/iac-mod/prod/main.tf 改为调用同一个模块(replicas 默认 3,env 为 prod,两个 moved)。计划中必须没有创建·删除·替换,然后 apply。现在要修改规则,只需改模块这一处。
步骤与 dev 相同。差别应该只有输入值——如果想在模块里加入 env == "prod" 这样的分支,说明这个值应该作为参数接收。
给模块加上版本,并把两个环境固定在该版本
把 /root/iac-mod/modules/web 创建为 git 仓库,提交现在的内容,并打上标签 v1.0.0。把 dev·prod 的 source 改为 git::file:///root/iac-mod/modules/web?ref=v1.0.0,执行 tofu init -upgrade 之后,两份计划都必须是干净的。
本地路径的 source 没有版本,所以一修改模块,调用它的所有环境都会一起改变。如果用 git 地址的 ref 固定标签,init 就会把那个时间点的副本下载到 .terraform/modules 中。这个 Pod 的 git 中没有用户信息,所以用 git -c user.name=... -c user.email=... 来提交。
新版本先在 dev 中使用,prod 保持不变
给模块再加两项内容并提交,打上标签 v2.0.0:在 config 内容末尾加一行 owner=platform,以及检查 replicas 是否在 1 以上、10 以下的 validation。只把 dev 升级到 ref=v2.0.0 并执行 init·apply,prod 保持在 v1.0.0,计划必须是干净的。
由于标签已固定,即使模块仓库的 main 发生变化,prod 也不受影响。确认 dev 的计划中 config 会如何变化(内容变更,因此是替换),然后应用。
同一个变量的值从多个地方传入时,谁会胜出
在 dev 中通过多条路径传入 replicas 的值,用 echo var.replicas | tofu console 确认实际值,并写入 /root/iac-mod/precedence.txt。所有情形下都存在环境变量 TF_VAR_replicas=9。env_only(无文件)、with_tfvars(terraform.tfvars 中为 2)、with_auto(在此基础上追加 a.auto.tfvars 中的 3 和 b.auto.tfvars 中的 4)、with_var_file(在此基础上加 -var-file=ops.tfvars,值为 6)、with_var(-var-file=ops.tfvars -var replicas=5 的顺序)、var_then_file(-var replicas=5 -var-file=ops.tfvars 的顺序)这六行,按 이름=값(韩文,意为“名称=值”)的格式写出。结束后,删除 a·b.auto.tfvars 和 ops.tfvars,只留下 terraform.tfvars(2)并 apply。
优先级可以记成“后读取的胜出”。环境变量最先,其次是 terraform.tfvars,auto.tfvars 按文件名顺序,命令行的 -var 和 -var-file 按书写顺序读取。如果把实验用的文件留下来,下一个人应用时的值会悄悄改变。
在模块边界拦住错误的值
在 dev 中运行 tofu plan -var replicas=0,把输出(包括错误)保存到 /root/iac-mod/invalid.txt。评分器会在 dev 的副本中检查 0 和 11 被拒绝,并检查在 prod(v1.0.0,没有检查)中 0 能通过。dev 和 prod 的计划要保持干净。
validation 附在模块的输入变量上,所以无论在根模块中通过哪条路径传入值,都会在模块边界被拦住。同样的输入在 prod 中能通过,是因为 v1 中没有检查,这就是版本固定的两面性。