Terraform模块编写与规模优化最佳实践

本文详细探讨Terraform模块设计的四个关键维度:范围界定、代码策略、安全考量和测试方法。通过实际案例展示如何创建可维护且高效的云基础设施代码模块,帮助企业优化云资源配置。

如何编写和规模优化Terraform模块

在设计Terraform模块时,您需要平衡许多属性:抽象性、灵活性和可维护性等。shiftavenue的高级云顾问Rene Schach最近在HashiDays 2025上分享了他对Terraform模块设计"规模优化"方面的见解。

基于他多年来使用Google Cloud、Kubernetes和Terraform构建基础设施平台的经验,他认为模块规模优化不仅仅是选择正确数量的资源。他将该主题分为四个主要支柱:

  • 范围
  • 代码策略
  • 安全性
  • 测试

本文分享他为实际团队构建Terraform模块的实用流程、经验教训和观点。

Terraform模块范围

每个Terraform模块都应服务于特定目的和受众。在编写代码之前,花时间了解谁将使用您的模块以及它要解决什么问题。(这就是为什么您的平台团队将平台视为产品很重要。)

与目标用户交流

  • 识别模块用户:开发团队、平台工程师和/或安全专家
  • 当前工作流程和挑战:与他们安排对话,了解他们当前如何配置基础设施以及面临哪些挑战
  • 输入控制要求:澄清他们需要控制哪些输入,以及是否有任何输入应保持固定以确保一致性或合规性原因
  • 建立自己的最佳实践:您的模块应代表真实的公司用例和抽象,而不是从互联网上获取的理论最佳实践

保持模块内聚和松散耦合

  • 遵循清晰的功能分离:每个模块应很好地执行一个功能——如网络、计算或IAM

    • 例如,kubernetes_cluster模块应仅处理集群创建和配置
    • 网络基础设施(VPC、子网和路由)应属于其自己的模块
  • 避免紧密耦合:如果一个模块中的更改在Terraform计划期间意外改变了许多其他模块的状态,这表明它们过于紧密耦合。设计边界,使模块可以独立发展而不会创建意外的依赖关系

    • 示例:AWS VPC模块是一个适当范围的、以网络为中心的模块的知名示例

将相关资源分组,按变动性拆分

  • 组合始终属于一起的资源:一个示例是计算实例及其附加磁盘或服务帐户。避免将逻辑连接的组件分散到不同的模块中
  • 按资源寿命拆分:将短期基础设施(开发或临时环境)与长期基础设施(核心网络或生产资源)分开。这可以防止对临时组件的频繁更改触发稳定基础模块中的更新

“规模优化模块更多是关于交流而不是编码——首先了解您的用户。"— Rene Schach,高级云顾问,shiftavenue

Terraform模块代码考虑因素

Terraform模块是软件工件。它们应该像任何其他代码库一样进行版本控制、测试和结构化。适当的结构还提高了新贡献者的可维护性和可读性。

分离模块资源

Rene的观点——根据目的将Terraform资源拆分为多个文件,而不是将所有内容保存在一个文件中。

示例Google Cloud Compute Engine(GCE)模块结构:

1
2
3
4
5
6
7
8
9
> .github
> examples
account.tf
locals.tf
outputs.tf

versions.tf
variables.tf
vm.tf

根据Rene的经验,大多数公司要求虚拟机使用自己的服务帐户或服务用户运行。因此,在上面的示例中:

  • account.tf定义了具有适当权限的自定义服务帐户(默认GCE服务帐户具有过于宽泛的权限)
  • vm.tf定义了虚拟机资源本身

这种分离有助于团队快速识别特定逻辑所在的位置,并使扩展模块更容易而不产生混淆。

提供示例和文档

  • 始终在模块存储库中包含examples/目录

  • 编写小型、自包含的Terraform配置,展示实际用例

  • 示例:向用户展示如何创建具有私有IP的单个VM和具有公共IP的另一个VM

输入:遵循提供程序的架构

Rene的观点——定义与底层Terraform提供程序资源对齐的输入变量。这使模块与Terraform的本机行为保持一致。

下面的示例变量定义镜像了来自Google Cloud的Terraform提供程序的google_compute_instance资源的结构,使输入可预测且一致:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
variable "instance_name" {
  description = "The VM name"
  type        = string
}
 
variable "location" {
  description = "The VM location"
  default     = "europe-west3-a"
}
 
variable "boot_disk_specs" {
  description = "Boot disk attributes"
  type = object({
    image = string # Operating system
    size  = optional(string, "100")
    type  = optional(string, "pd-ssd")
  })
}

这些属性被定义为一个对象,因为它们在google_compute_instance资源中被定义为一个对象。

除非显著提高模块消费者的清晰度,否则避免偏离Terraform提供程序架构。

局部变量:保持集中

Rene的观点——将所有局部变量放在一个locals.tf文件中。

将所有局部变量放在一个地方可以简化调试或修改模块时的导航。

输出:返回完整对象

Rene的观点——输出完整的资源对象(例如VM实例对象),而不是为每个属性单独创建输出。

例如:

1
2
3
4
5
6
7
output "vm" {
  value = google_compute_instance.vm
}
 
output "service_account" {
  value = google_service_account.vm_sa
}

虽然这意味着用户必须自己导航对象属性,但这也使用户能够灵活地访问他们需要的任何属性,而无需以后请求新的输出。

提供示例和文档,而不是为每个小的输出添加请求创建新的模块版本。

语义化版本控制您的模块

像对软件进行版本控制一样对Terraform模块进行版本控制:使用语义版本控制(MAJOR.MINOR.PATCH)。

Rene推荐的版本控制规则

  • 添加新的必需输入 → 增加主版本
    • 用户必须更改其代码才能安全升级
  • 添加新的可选输入 → 增加次版本
    • 现有用户可以无需任何修改即可升级
  • 添加新输出 → 增加次版本
    • 这不会破坏兼容性
  • 删除输出 → 增加主版本
    • 删除输出可能会破坏用户配置
  • 添加或删除资源 → 根据相关输入/输出确定版本影响
  • 升级提供程序版本 → 如果行为没有变化,则增加修订版本,如果输入或输出架构受到影响,则增加次版本/主版本

这些指南帮助团队可预测且安全地升级模块,而不会引入意外中断。

Terraform模块安全考虑因素

模块定义了如何配置基础设施的防护栏。早期验证输入和限制可配置性有助于防止不安全或不一致的部署。

使用Terraform验证块

Terraform允许在变量定义中直接进行内联输入验证。示例:

1
2
3
4
5
6
7
8
9
variable "location" {
  description = "The VM location"
  type        = string
 
  validation {
    condition     = startswith(var.location, "europe-west3")
    error_message = "Only europe-west3 (Frankfurt) locations are allowed."
  }
}

此示例验证确保用户无法在批准的区域外部部署资源。

早期验证配置

terraform plan阶段快速失败,而不是等待应用阶段触发策略违规。

手动审查和组织策略实施也可以阻止无效配置,但它们通常在状态更改开始后应用——使修复更具破坏性。

在需要时应用策略框架

使用Sentinel(HashiCorp)或Open Policy Agent(OPA)进行更高级别的实施。

这些工具可以定义可重用的集中合规规则。

仅暴露必要内容

避免让用户完全控制资源的每个属性。

如果每个VM必须是私有的,甚至不要暴露使其公开的标志。

保持可配置性狭窄可以减少误用并强制执行一致性。

Terraform模块测试

测试Terraform模块过去很麻烦,但现代Terraform测试框架使其变得实用和集成。

使用官方Terraform测试框架

terraform test命令允许您使用标准Terraform配置语法编写和执行测试。

Rene的观点——不需要额外的工具或编程语言,如Go(Terratest)或Ruby(Kitchen-Terraform)。

将测试集成到CI/CD中

在拉取请求上或发布新版本的模块之前自动运行测试。

失败的测试应阻止合并,直到纠正,就像任何应用程序代码一样。

专注于真实用例测试

不要尝试测试每个输入组合。

相反,创建反映实际使用模式和组织要求的测试用例。

测试您的示例

如果您为模块维护示例(您应该这样做),请将它们重用为测试用例。

这确保您的文档保持有效,并且示例永远不会过时。

协作设计Terraform模块

当Terraform模块被视为共享的、不断发展的资产而不是隐藏的内部工具时,它们会变得更加成功。

让团队可见模块

将它们存储在共享版本控制系统中。考虑私有模块注册表。

避免限制访问;透明度有助于开发人员了解模块的作用和工作原理。

开发人员更有可能采用和贡献他们可以看到和信任的模块。

通过拉取请求和问题跟踪鼓励反馈。

使用架构决策记录(ADR)

记录塑造模块结构和约定的关键设计决策。

例如,解释团队为什么选择强制执行私有IP或限制某些输入。

确保您的模块实现与这些ADR保持一致,以实现长期一致性。

知道何时不构建模块

如果您的模块仅围绕单个Terraform资源构建包装器,Rene建议它不应该是一个模块。

如果您的模块简单地一对一地暴露资源的每个输入,它可能不适合作为自定义模块。在这些情况下,请考虑使用维护良好的公共模块。

将模块开发视为持续过程

从解决具体用例的最小可行模块开始。

随着新需求的出现,通过小的增量更改和拉取请求扩展它。

应用标准软件工程实践:语义版本控制、代码审查、变更日志维护和CI/CD集成。

要点

Rene演讲中的一些建议有些主观,但通常这些要点是广泛接受的最佳实践:

  • 了解您的用户并围绕他们的实际需求设计模块
  • 保持模块内聚、松散耦合,并范围限定到明确的职责
  • 对于输入,偏离Terraform提供程序架构可能会导致问题。考虑遵循提供程序的架构,除非打破它可以让您显著提高模块消费者的清晰度
  • 语义化版本控制模块以确保安全、清晰的升级
  • 早期验证输入并限制不必要的可配置性
  • 使用Terraform的本机测试框架测试模块并将这些测试集成到CI/CD中
  • 通过开放存储库和文档化决策促进可见性和协作
  • 将Terraform模块视为不断发展的软件产品,而不是静态模板
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计