4. 基础设施即代码工程化起步
当 Terraform 从“一个人写几个资源”进入“团队协作维护多云基础设施”阶段,最先暴露的问题往往不是语法,而是工程结构。所有资源堆在一个 main.tf 里,变量散落在各处,输出没有统一约定,环境之间靠复制粘贴区分——这样的代码无法复用,也无法安全演进。本节要解决的核心问题是:如何用模块化设计、清晰的目录结构、规范的变量与输出管理,构建一套可复用的多云基础模板。
一、模块化设计:把“能跑”变成“可复用”
Terraform 模块本质上是一组被打包的 .tf 文件集合,它对外暴露输入变量和输出值,对内封装资源实现。模块化的判断标准很简单:如果一段资源配置需要在两个以上地方出现,或者它对应一个清晰的业务能力(如“一个具备公网访问能力的 VPC”“一套标准对象存储桶”),就应该抽成模块。
以多云场景为例,我们通常按“能力”而非“云厂商”划分模块。例如 network 模块负责 VPC/VNet、子网、路由表、安全组;storage 模块负责对象存储与生命周期策略;compute 模块负责虚拟机或容器集群。每个模块内部再通过 provider 别名或条件判断适配 AWS、Azure、阿里云等不同后端。这样上层调用者只关心“我要一个网络”,而不关心底层是哪个云。
模块的目录结构建议采用如下约定:
modules/
network/
main.tf # 资源定义
variables.tf # 输入变量声明
outputs.tf # 输出值声明
versions.tf # provider 与 terraform 版本约束
README.md # 模块用途、输入输出说明
storage/
...
environments/
dev/
main.tf # 调用模块,传入 dev 参数
terraform.tfvars
prod/
main.tf
terraform.tfvars
这种“模块层 + 环境层”分离的结构,使模块保持稳定,环境层只负责组合与参数注入。模块内部不应硬编码任何环境相关值,所有可变部分必须通过变量传入。
二、变量管理:类型、默认值与校验
变量是模块的接口。一个可复用的模块,其变量设计应当满足三点:类型明确、默认值合理、必要时可校验。Terraform 支持 string、number、bool、list、map、object 等类型,复杂结构优先使用 object 以获得更好的可读性和校验能力。
以下是一个网络模块的变量声明示例,展示了类型约束与校验规则:
variable "environment" {
type = string
description = "环境标识,如 dev / staging / prod"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment 必须是 dev、staging 或 prod 之一。"
}
}
variable "vpc_cidr" {
type = string
default = "10.0.0.0/16"
description = "VPC 网段,需为合法 CIDR"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "vpc_cidr 必须是合法 CIDR,例如 10.0.0.0/16。"
}
}
variable "subnets" {
type = list(object({
name = string
cidr = string
public = bool
}))
description = "子网列表,public 决定是否关联公网路由"
}
变量值的来源优先级从低到高依次为:默认值、terraform.tfvars、*.auto.tfvars、命令行 -var、环境变量 TF_VAR_。在团队协作中,建议将非敏感值写入 terraform.tfvars 并纳入版本控制,敏感值(如访问密钥)通过环境变量或密钥管理服务注入,绝不写入代码仓库。
三、输出管理:让模块之间可组合
输出值(output)是模块对外的返回值,也是模块组合的桥梁。一个模块的输出应当只暴露上层真正需要的属性,而不是把所有资源属性都抛出去。例如网络模块应输出 vpc_id、subnet_ids、security_group_id,计算模块再通过 module.network.vpc_id 引用。
output "vpc_id" {
value = aws_vpc.this.id
description = "VPC 唯一标识,供计算与存储模块引用"
}
output "subnet_ids" {
value = { for s in aws_subnet.this : s.tags.Name => s.id }
description = "子网名称到 ID 的映射"
}
输出同样支持 sensitive = true 标记,用于避免密钥、连接串等在 terraform output 中明文显示。对于跨环境共享的值,还可以使用 terraform_remote_state 数据源读取其他状态文件的输出,但更推荐通过模块直接组合,减少隐式依赖。
四、多云基础模板的整体数据流
下图展示了本节所构建模板的核心结构:环境层通过变量向模块层注入参数,模块层内部封装各云厂商资源,最终通过输出值向上返回关键标识,供其他模块或环境引用。图中同时标出了变量校验与敏感值处理的位置。
五、可复用模板的落地检查
在把模板交给团队使用前,建议逐项确认:模块是否只通过变量接收环境差异;变量是否都有类型和描述;敏感变量是否已标记 sensitive 或通过环境变量注入;输出是否只暴露必要属性;versions.tf 是否锁定了 Terraform 与 provider 的版本范围。这些检查能显著降低后续多云扩展时的返工成本。
source = "git::https://...?ref=v1.2.0" 引用固定版本,避免上游模块变更直接冲击生产环境。
完成本节后,你应当能够独立搭建一个“环境层调用模块层、模块层封装多云资源、输出层提供组合接口”的基础模板。下一阶段的多云迁移工作,都将在这个结构之上展开。