3. 模块化设计原则:模块封装、单一职责、组合与复用、版本控制、输入输出变量设计规范

模块化是 Terraform 生产级实践的核心。在 CI/CD 服务目录环境中,模块不仅是代码的复用单元,更是治理、安全与变更管理的边界。本章围绕五项核心原则,建立模块设计的规范体系。

3.1 模块封装:隐藏实现,暴露契约

封装要求模块对外隐藏内部资源定义、数据源和 provider 配置,仅通过 输入变量 和 输出值 与调用方交互。调用方不应感知模块使用了哪种计算实例类型、安全组规则细节或存储后端。

封装检查清单:
  • 模块内所有资源名称不依赖调用方命名约定,使用 var.name_prefix 或 local 派生。
  • 不暴露 resource 块中的敏感属性(如密码、私钥)作为输出,除非明确标记 sensitive = true。
  • 模块根目录下仅保留 main.tf、variables.tf、outputs.tf、versions.tf 四个核心文件。
模块封装边界示意 调用方 Root Module module "vpc" { ... } 模块封装边界 输入变量 variable "cidr" {} 内部资源(隐藏) aws_vpc / aws_subnet 输出值 output "vpc_id" {} 调用方只与输入/输出交互,不感知内部实现

3.2 单一职责:一个模块只做一件事

单一职责原则要求每个模块只负责管理 一类基础设施资源 或 一个明确的业务能力。如果一个模块同时管理 VPC、EC2 和 RDS,则职责过重,难以测试、复用和版本化。

反模式(职责混杂) 正模式(单一职责)
module "infra" 包含 VPC + 子网 + EC2 + RDS + S3 module "vpc"、module "ec2"、module "rds"、module "s3"
模块内同时有 resource "aws_instance" 和 resource "aws_lb" 计算模块与负载均衡模块分离
一个模块同时处理创建和销毁逻辑(通过 count 切换) 创建模块与销毁模块分离,或使用 terraform destroy 原生能力
判断标准: 如果模块的 README.md 需要超过 3 个段落才能描述清楚用途,说明职责可能过重。一个良好设计的模块,其描述应不超过两句话。

3.3 组合与复用:从原子模块到复合模块

组合原则将小粒度、单一职责的 原子模块 通过 module 块嵌套,构建出 复合模块 或 服务目录模板。复用不是复制代码,而是通过 source 引用已发布的模块版本。

模块组合与复用层次 vpc 模块 ec2 模块 rds 模块 s3 模块 原子模块(单一职责) web-app 复合模块 组合 vpc + ec2 + rds data-pipeline 复合模块 组合 s3 + ec2 + 自定义资源 CI/CD 服务目录:环境模板(开发/预发/生产)
复用策略:
  • 原子模块发布到 Git 标签或 Terraform Registry,复合模块通过 source = "git::https://...//modules/vpc?ref=v1.2.0" 引用。
  • 服务目录模板(如 environments/prod)只做组合编排,不直接定义资源。
  • 避免在复合模块中覆盖原子模块的默认值,除非有明确的业务差异。

3.4 版本控制:模块即制品

模块必须像软件制品一样进行版本管理。Terraform 通过 version 约束和 Git 标签实现模块版本控制。

版本策略 实现方式 适用场景
语义化版本 v1.0.0、v1.1.0、v2.0.0 所有生产模块
版本约束 version = "~> 1.0"(允许补丁版本) 调用方锁定兼容范围
分支引用 ref = "main" 仅开发/测试阶段,禁止用于生产
Commit SHA ref = "abc1234" 紧急回滚或审计追溯
版本控制避坑:
  • 不要在生产环境中使用 ref = "main" 或 ref = "latest",会导致不可控的变更。
  • 模块的 versions.tf 中必须声明 required_version,避免 Terraform 版本不兼容。
  • 每次模块变更(即使只是文档更新)都应创建新标签,确保可追溯。

3.5 输入输出变量设计规范

输入输出变量是模块的契约接口,设计质量直接影响模块的可复用性和可维护性。

3.5.1 输入变量设计规范

规范项 要求 示例
命名风格 小写字母 + 下划线,语义清晰 vpc_cidr_block 而非 cidr
类型声明 必须显式声明 type,避免 any type = list(string)
默认值 提供合理的默认值,减少调用方配置量 default = "10.0.0.0/16"
描述 必须包含 description,说明用途和格式 description = "VPC 的 CIDR 块,格式如 10.0.0.0/16"
验证 使用 validation 块约束输入值 condition = can(cidrhost(var.vpc_cidr_block, 0))
敏感变量 标记 sensitive = true sensitive = true 用于密码、密钥
# variables.tf 规范示例
variable "vpc_cidr_block" {
  description = "VPC 的 IPv4 CIDR 块,例如 10.0.0.0/16"
  type        = string
  default     = "10.0.0.0/16"

  validation {
    condition     = can(cidrhost(var.vpc_cidr_block, 0))
    error_message = "vpc_cidr_block 必须是有效的 CIDR 表示法。"
  }
}

variable "subnet_configs" {
  description = "子网配置列表,每个元素包含 name、cidr、az"
  type = list(object({
    name = string
    cidr = string
    az   = string
  }))
  default = []
}

variable "db_password" {
  description = "数据库管理员密码"
  type        = string
  sensitive   = true
}

3.5.2 输出变量设计规范

规范项 要求 示例
最小化输出 只输出调用方真正需要的属性 输出 vpc_id,而非所有 VPC 属性
命名一致 使用 <资源>_<属性> 格式 vpc_id、subnet_ids、security_group_id
描述 必须包含 description description = "创建的 VPC 的 ID"
敏感标记 敏感数据必须标记 sensitive = true sensitive = true 用于连接字符串
避免暴露原始资源 不要直接输出整个 aws_vpc.this 应输出 value = aws_vpc.this.id
# outputs.tf 规范示例
output "vpc_id" {
  description = "创建的 VPC 的 ID"
  value       = aws_vpc.this.id
}

output "subnet_ids" {
  description = "所有子网的 ID 列表"
  value       = aws_subnet.this[*].id
}

output "database_endpoint" {
  description = "RDS 实例的连接端点"
  value       = aws_db_instance.this.endpoint
  sensitive   = true
}
设计检查: 在模块开发完成后,运行 terraform providers schema -json 检查模块的输入输出契约是否完整。调用方应仅通过 module.<name>.<output> 获取值,不应直接引用模块内的资源地址。