3. 模块化设计原则:模块封装、单一职责、组合与复用、版本控制、输入输出变量设计规范
模块化是 Terraform 生产级实践的核心。在 CI/CD 服务目录环境中,模块不仅是代码的复用单元,更是治理、安全与变更管理的边界。本章围绕五项核心原则,建立模块设计的规范体系。
3.1 模块封装:隐藏实现,暴露契约
封装要求模块对外隐藏内部资源定义、数据源和 provider 配置,仅通过 输入变量 和 输出值 与调用方交互。调用方不应感知模块使用了哪种计算实例类型、安全组规则细节或存储后端。
封装检查清单:
- 模块内所有资源名称不依赖调用方命名约定,使用
var.name_prefix或local派生。 - 不暴露
resource块中的敏感属性(如密码、私钥)作为输出,除非明确标记sensitive = true。 - 模块根目录下仅保留
main.tf、variables.tf、outputs.tf、versions.tf四个核心文件。
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 引用已发布的模块版本。
复用策略:
- 原子模块发布到 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> 获取值,不应直接引用模块内的资源地址。