做一个子模块并多次调用
目标
按标准结构创建一个小型子模块,多次调用同一个模块来增加实例,并熟练掌握把模块内部的值通过输出暴露到外部的流程。
为什么重要
如果只把“消除重复代码”当作使用模块的理由,很快就会碰壁。真正的理由是限制变更的影响范围。实现只有一份,需要修改的地方也就只有一处,环境之间的差异只体现在传入的值上,而不是代码上。因此模块设计中最重要的决定是“把哪些内容开放为变量”——开放得太多,模块就只是资源的另一个名字;开放得太少,又没有人能用。还有一点要记住:模块是一个封装。模块内部的资源无法从外部直接引用,只能通过 output 输出。这个限制看似麻烦,但正因如此,即使彻底改造模块内部,使用方的代码也不会损坏。最后,不要在子模块中放置 provider 块。它会导致日后无法从代码中删除这个模块调用,是难以挽回的事故成因。
步骤
- 把
/opt/lab/fixtures/terraform/modules-starter/modules/filebox复制到/root/tf/modules/modules/filebox。该目录中必须有main.tf、variables.tf、outputs.tf三个文件。variables.tf中要有variable "dir"和variable "name",并在新建的outputs.tf中写入output "path"(值为local_file.box.filename)。模块内部不能放置provider "..."块。 - 在
/root/tf/modules/main.tf中声明module "primary",并指定source = "./modules/filebox"。dir传入/root/tf/modules/out,name传入primary,body传入任意字符串。执行tofu init后再执行tofu apply,创建/root/tf/modules/out/primary.txt。 - 在
/root/tf/modules/outputs.tf中创建根模块输出primary_path,输出module.primary.path,并把tofu output -json的结果保存为/root/tf/modules/out/outputs.json。primary_path的值必须是/root/tf/modules/out/primary.txt。 - 再用
module "secondary"调用一次同一个 source。name传入secondary,body传入与 primary 不同的字符串,使/root/tf/modules/out/secondary.txt的内容与primary.txt不同。不要复制模块目录——/root/tf/modules/modules之下的目录数量,连同稍后要创建的bundle在内,不得超过 2 个。 - 把
tofu state list的结果保存为/root/tf/modules/out/state-list.txt。其中必须分别有以module.primary.和module.secondary.开头的地址,并且根模块中不能另有名为local_file.box的地址。 - 创建
/root/tf/modules/modules/bundle/main.tf,在其中用source = "../filebox"调用 filebox 两次(例如module "left"和module "right",名称为bundle-left和bundle-right)。在根模块中用module "bundle"调用并应用后,状态中会出现 2 个以上形如module.bundle.module.left....的地址。 - 在
/root/tf/modules/modules/filebox/variables.tf的name变量中添加validation块,并同时写上error_message(例如只允许小写字母、数字和连字符)。然后暂时给某个调用的name传入违反规则的值,并把tofu plan的输出连同标准错误一起保存为/root/tf/modules/out/module-error.txt。保存的内容中必须同时能看到验证失败的消息和filebox路径。确认之后,把值改回原样。 - 在
/root/tf/modules/outputs.tf中添加all_paths输出。它是包含primary、secondary、bundle三个键的映射,其中primary的值必须是/root/tf/modules/out/primary.txt。在 bundle 模块中也创建output "paths",放入内部的两个路径,并把它放进bundle键。最后再次把tofu output -json保存为/root/tf/modules/out/outputs.json。
参考
- 本实验的状态文件是
/root/tf/modules/terraform.tfstate。模块内部的资源也全部记录在这一个状态中。 - 在这个环境中,
tofu和terraform是同一个命令。provider 从镜像内文件系统的 mirror 中获取,因此无需联网也能执行tofu init。 - 请在根模块的
main.tf中写入terraform { required_providers { local = { source = "hashicorp/local" } } }。provider 配置只放在根模块中。 - 新增模块或修改
source之后,必须重新执行tofu init。“Module not installed” 错误有 90% 是因为这个原因。 - 常见错误 1:在根模块中以
module.primary.local_file.box的形式引用模块内部的资源。这样的地址并不存在。值必须经过output传出。 - 常见错误 2:为了增加实例而把整个模块目录复制一份。应该用不同的名称调用同一个
source。
把模块拆分为输入、实现、输出三个文件
把 /opt/lab/fixtures/terraform/modules-starter/modules/filebox 复制到 /root/tf/modules/modules/filebox。该目录中必须有 main.tf、variables.tf、outputs.tf 三个文件。variables.tf 中要有 variable "dir" 和 variable "name",并在新建的 outputs.tf 中写入 output "path"(值为 local_file.box.filename)。模块内部不能放置 provider "..." 块。
模块就是一个普通目录。按惯例,变量放在 variables.tf,资源放在 main.tf,要输出的值放在 outputs.tf。不要在子模块中放置 provider 配置块。
调用模块并应用
在 /root/tf/modules/main.tf 中声明 module "primary",并指定 source = "./modules/filebox"。dir 传入 /root/tf/modules/out,name 传入 primary,body 传入任意字符串。执行 tofu init 后再执行 tofu apply,创建 /root/tf/modules/out/primary.txt。
在 module "이름" 块(占位符为模块调用名称)中写入 source 和变量值。新增模块或修改 source 之后,必须重新执行 init。
把模块输出提升到根模块
在 /root/tf/modules/outputs.tf 中创建根模块输出 primary_path,输出 module.primary.path,并把 tofu output -json 的结果保存为 /root/tf/modules/out/outputs.json。primary_path 的值必须是 /root/tf/modules/out/primary.txt。
模块内部的资源无法从外部直接引用。必须由根模块的 output 再次接收子模块的 output,它才会出现在 tofu output 中。
用不同的输入调用同一个模块两次
再用 module "secondary" 调用一次同一个 source。name 传入 secondary,body 传入与 primary 不同的字符串,使 /root/tf/modules/out/secondary.txt 的内容与 primary.txt 不同。不要复制模块目录——/root/tf/modules/modules 之下的目录数量,连同稍后要创建的 bundle 在内,不得超过 2 个。
复制目录就回到了复制粘贴的问题。请只改名称来调用同一个 source,并让传入的值互不相同。
确认模块内资源的地址
把 tofu state list 的结果保存为 /root/tf/modules/out/state-list.txt。其中必须分别有以 module.primary. 和 module.secondary. 开头的地址,并且根模块中不能另有名为 local_file.box 的地址。
模块内的资源会带有 module.<호출이름>. 前缀(占位符为模块调用名称)。请导出状态列表,查看前缀是否真的加上了。
在模块内部调用模块
创建 /root/tf/modules/modules/bundle/main.tf,在其中用 source = "../filebox" 调用 filebox 两次(例如 module "left" 和 module "right",名称为 bundle-left 和 bundle-right)。在根模块中用 module "bundle" 调用并应用后,状态中会出现 2 个以上形如 module.bundle.module.left.... 的地址。
在子模块中调用同级模块时使用相对路径。嵌套之后,状态地址也会叠加成两层。
在模块边界拦截错误的值
在 /root/tf/modules/modules/filebox/variables.tf 的 name 变量中添加 validation 块,并同时写上 error_message(例如只允许小写字母、数字和连字符)。然后暂时给某个调用的 name 传入违反规则的值,并把 tofu plan 的输出连同标准错误一起保存为 /root/tf/modules/out/module-error.txt。保存的内容中必须同时能看到验证失败的消息和 filebox 路径。确认之后,把值改回原样。
variable 块内的 validation 要求同时提供 condition 和 error_message。请故意传入一次会被拒绝的值,亲眼确认错误。
把三个模块的输出汇总为一个映射
在 /root/tf/modules/outputs.tf 中添加 all_paths 输出。它是包含 primary、secondary、bundle 三个键的映射,其中 primary 的值必须是 /root/tf/modules/out/primary.txt。在 bundle 模块中也创建 output "paths",放入内部的两个路径,并把它放进 bundle 键。最后再次把 tofu output -json 保存为 /root/tf/modules/out/outputs.json。
可以直接用根模块 output 的值构造对象。键名和数量是评分标准,请准确对应。