1.2.6.1. 本地子模块

我们在本地子模块 vs 拆分 .tf 代码文件当中提到过,有两种情况下我们可以在模块中建立子模块,它们分别是:

  • 旨在被模块中多处声明的一个(组)资源
  • 旨在作为独立模块引用地址发布的子模块

我们将在本章中分别讨论这两种情况。

1.2.6.1.1. 旨在被模块中多处声明的一个(组)资源

有时候我们会因为去重(DRY)与可维护性选择本地子模块。例如,在 avm-res-compute-virtualmachine 模块中,不但定义了 Azure 虚拟机,还允许模块调用者定义安装在虚拟机上的各种扩展(azurerm_virtual_machine_extension),但不同扩展只是在 publisher / type / settings 等字段上有差异。如果直接在根模块里写十几个几乎相同的资源块,代码会臃肿且难以维护;抽成本地子模块后,只需把公共逻辑放进 modules/extension,然后在 main.extensions.tf 里通过多次调用统一模板、配合 for_each 把每个扩展实例化即可,既保持阅读友好,也方便后期集中修改。

第二点:显式依赖(depends_onazurerm_virtual_machine_extension 必须等待虚拟机完全可用后才能执行,否则会出现扩展挂起或初始化失败的问题。虽然在扩展块里引用了 virtual_machine_id,Terraform 能隐式推断出“先建 VM、后装扩展”的顺序,但当 VM 旁边还有其它资源同样修改 VM 的属性(例如先给 VM 添加托管身份,再部署依赖身份的扩展)时,隐式依赖常常不够用。因此在每个 module "extension" 调用块里,作者显式写了类似:

depends_on = [
  azurerm_linux_virtual_machine.this,
  azurerm_windows_virtual_machine.this,
  module.run_command
]

这样可确保:

  • 扩展一定在 VM 资源完成后才会触发
  • 当 VM 被替换或 Run Command 改变时,扩展也会随之重新部署
  • 避免因 Azure RM Provider 未能识别的“隐藏依赖”导致部署次序错乱

另外,假设在依赖关系上,后者需要的是被依赖的一组相关资源必须全部创建完毕后才能开始创建,那么与其在 depends_on 里编写复杂的被依赖资源地址,不如将这组相关的资源封装为一个模块,这样在 depends_on 内的某个地址是一个模块时,Terraform 会在该模块所有资源全部创建完成后,才会触发依赖该模块的资源的创建,可以帮助我们把代码变得简洁和易懂,同时易于维护(往子模块中添加资源不需要修改根模块中的 depends_on)。

Terraform 官方文档明确指出,depends_on 用于“Terraform 无法自动推断的隐藏依赖”,并在 0.13 以后可直接作用于 module 块,所以这种写法既规范又必要。如果缺少这种显式依赖,某些要求先安装系统扩展才能执行的后置步骤(如自定义脚本或 GC 策略)就可能出现竞态,导致 terraform apply 失败的尴尬局面。

1.2.6.1.2. 旨在作为独立模块引用地址发布的子模块

有时候我们的可重用模块本身只是暴露了某一类云资源的各种可配置项,但要正确使用该资源,不同的场景往往有对应不同的最佳实践配置。任由模块调用者自行摸索这些最佳实践配置未免过于不便,并且无法确保不同水平的用户都能够写出正确、完备的配置。这时,我们可以选择创建一组本地子模块,封装了对根模块的调用,以及预先配置好的代表不同场景下最佳实践的参数配置。

一个形象的例子是网络安全组(azurerm_network_security_group)。网络安全组类似于防火墙,定义了虚拟网络中的访问规则,一般由协议、源 IP、源端口、目标 IP、目标端口、是否放行、规则优先级组成,不同的服务适用于不同的规则,大多是在目标端口和协议上会有不同。我们可以预先为各种服务配置对应的常见规则参数,以子模块的形式保存在 modules 下:

.
├── ActiveDirectory
├── Cassandra
├── Cassandra-JMX
├── Cassandra-Thrift
├── CouchDB
├── CouchDB-HTTPS
├── DNS-TCP
├── DNS-UDP
├── DynamicPorts
├── ElasticSearch
├── FTP
├── HTTP
├── HTTPS
├── IMAP
├── IMAPS
├── Kestrel
├── LDAP
├── MSSQL
├── Memcached
├── MongoDB
├── MySQL
├── Neo4J
├── POP3
├── POP3S
├── PostgreSQL
├── RDP
├── RabbitMQ
├── Redis
├── Riak
├── Riak-JMX
├── SMTP
├── SMTPS
├── SSH
├── WinRM
└── _template

这类子模块虽然位于模块仓库中,但可以作为独立模块通过双斜杠语法引用。例如,可以在 Terraform 配置中写 source = "xxxx//modules/rules" 来单独引用 modules/rules 子目录。每个子模块一般只封装一种典型使用模式或最佳实践配置,让模块聚焦单一功能。这样调用者就能只引入所需的功能子集,而无需了解完整模块的实现细节。

results matching ""

    No results matching ""