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

Terraform 实战

做一个子模块并多次调用

在 TT Lab 中继续学习

目标

按标准结构创建一个小型子模块,多次调用同一个模块来增加实例,并熟练掌握把模块内部的值通过输出暴露到外部的流程。

为什么重要

如果只把“消除重复代码”当作使用模块的理由,很快就会碰壁。真正的理由是限制变更的影响范围。实现只有一份,需要修改的地方也就只有一处,环境之间的差异只体现在传入的值上,而不是代码上。因此模块设计中最重要的决定是“把哪些内容开放为变量”——开放得太多,模块就只是资源的另一个名字;开放得太少,又没有人能用。还有一点要记住:模块是一个封装。模块内部的资源无法从外部直接引用,只能通过 output 输出。这个限制看似麻烦,但正因如此,即使彻底改造模块内部,使用方的代码也不会损坏。最后,不要在子模块中放置 provider 块。它会导致日后无法从代码中删除这个模块调用,是难以挽回的事故成因。

步骤

  1. 把 /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 "..." 块。
  2. 在 /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。
  3. 在 /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。
  4. 再用 module "secondary" 调用一次同一个 source。name 传入 secondary,body 传入与 primary 不同的字符串,使 /root/tf/modules/out/secondary.txt 的内容与 primary.txt 不同。不要复制模块目录——/root/tf/modules/modules 之下的目录数量,连同稍后要创建的 bundle 在内,不得超过 2 个。
  5. 把 tofu state list 的结果保存为 /root/tf/modules/out/state-list.txt。其中必须分别有以 module.primary. 和 module.secondary. 开头的地址,并且根模块中不能另有名为 local_file.box 的地址。
  6. 创建 /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.... 的地址。
  7. 在 /root/tf/modules/modules/filebox/variables.tf 的 name 变量中添加 validation 块,并同时写上 error_message(例如只允许小写字母、数字和连字符)。然后暂时给某个调用的 name 传入违反规则的值,并把 tofu plan 的输出连同标准错误一起保存为 /root/tf/modules/out/module-error.txt。保存的内容中必须同时能看到验证失败的消息和 filebox 路径。确认之后,把值改回原样。
  8. 在 /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。

参考

把模块拆分为输入、实现、输出三个文件

把 /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 的值构造对象。键名和数量是评分标准,请准确对应。