4. 模块基础结构:模块目录标准布局与核心文件
一个可复用、可共享的 Terraform 模块,其文件结构必须遵循社区约定和官方推荐标准。标准化的目录布局不仅让模块使用者一目了然,还能被 Terraform Registry、服务目录工具(如 Terraform Cloud 的模块库)自动识别和解析。本章将详细拆解模块的四个核心文件:main.tf、variables.tf、outputs.tf 和 versions.tf,并说明它们各自的作用与最佳实践。
4.1 模块目录标准布局
一个典型的 Terraform 模块仓库根目录结构如下:
terraform-aws-vpc/
├── main.tf # 核心资源定义
├── variables.tf # 输入变量声明
├── outputs.tf # 输出值声明
├── versions.tf # Terraform 与 Provider 版本约束
├── README.md # 模块说明文档
├── examples/ # 使用示例目录
│ └── basic/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── tests/ # 测试目录(可选)
└── modules/ # 嵌套子模块目录(可选)
└── sub-module/
├── main.tf
├── variables.tf
└── outputs.tf
其中,main.tf、variables.tf、outputs.tf 和 versions.tf 是模块的四个基础文件,缺一不可。下表总结了每个文件的核心职责:
| 文件名 | 核心职责 | 是否必须 | 典型内容 |
|---|---|---|---|
main.tf |
定义模块管理的核心云资源 | 是 | resource、data、module 块 |
variables.tf |
声明模块的输入参数 | 是 | variable 块,含类型、描述、默认值 |
outputs.tf |
定义模块对外暴露的输出值 | 是 | output 块,含描述与值表达式 |
versions.tf |
约束 Terraform 版本与 Provider 版本 | 强烈推荐 | terraform 块与 required_providers |
最佳实践: 即使模块只有一个资源,也建议将四个文件分开。这符合“关注点分离”原则,便于代码审查、自动化测试和版本管理。
4.2 核心文件详解
4.2.1 main.tf —— 资源定义中心
main.tf 是模块的主体文件,包含所有需要创建、管理或引用的云资源。它通常包含 resource 块和 data 块。模块使用者通过调用模块,间接创建这些资源。
# main.tf 示例:创建一个 AWS VPC 模块的核心资源
resource "aws_vpc" "this" {
cidr_block = var.vpc_cidr
enable_dns_support = var.enable_dns_support
enable_dns_hostnames = var.enable_dns_hostnames
tags = merge(
var.tags,
{
Name = var.vpc_name
}
)
}
resource "aws_subnet" "public" {
count = var.create_public_subnets ? length(var.public_subnet_cidrs) : 0
vpc_id = aws_vpc.this.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-public-${count.index + 1}"
Tier = "public"
}
)
}
关键原则:
- 所有硬编码值必须通过
var.*引用,确保模块可配置。 - 资源命名使用
this作为默认标签(如aws_vpc.this),保持一致性。 - 使用
count或for_each处理条件创建或批量资源。
4.2.2 variables.tf —— 输入接口定义
variables.tf 定义了模块的输入参数,是模块与外部世界交互的接口。每个变量都应包含 type、description,并尽可能提供 default 值或 validation 规则。
# variables.tf 示例
variable "vpc_name" {
description = "VPC 的名称标签"
type = string
}
variable "vpc_cidr" {
description = "VPC 的 CIDR 地址块"
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "VPC CIDR 必须是一个有效的 IPv4 CIDR 地址块。"
}
}
variable "availability_zones" {
description = "可用区列表"
type = list(string)
}
variable "tags" {
description = "应用于所有资源的标签映射"
type = map(string)
default = {}
}
variable "create_public_subnets" {
description = "是否创建公有子网"
type = bool
default = true
}
variable "public_subnet_cidrs" {
description = "公有子网的 CIDR 列表"
type = list(string)
default = []
}
| 变量属性 | 作用 | 是否推荐 |
|---|---|---|
type |
强制类型检查,防止传入错误类型 | 必须 |
description |
生成文档,帮助使用者理解变量用途 | 必须 |
default |
提供合理的默认值,降低使用复杂度 | 强烈推荐 |
validation |
运行时校验输入值,提前捕获错误 | 推荐 |
sensitive |
标记为敏感值,防止日志泄露 | 密码/密钥时使用 |
4.2.3 outputs.tf —— 输出接口定义
outputs.tf 定义了模块对外暴露的信息,如资源 ID、ARN、DNS 名称等。这些输出可以被模块调用方引用,或作为其他模块的输入。
# outputs.tf 示例
output "vpc_id" {
description = "创建的 VPC 的 ID"
value = aws_vpc.this.id
}
output "vpc_arn" {
description = "创建的 VPC 的 ARN"
value = aws_vpc.this.arn
}
output "public_subnet_ids" {
description = "公有子网 ID 列表"
value = aws_subnet.public[*].id
}
output "public_subnet_cidrs" {
description = "公有子网 CIDR 列表"
value = aws_subnet.public[*].cidr_block
}
注意: 不要将敏感信息(如数据库密码、私钥)直接暴露在输出中。如果必须输出,请将
sensitive = true 设置为 true,并确保调用方也做相应处理。
4.2.4 versions.tf —— 版本约束声明
versions.tf 用于声明模块所依赖的 Terraform 版本和 Provider 版本。这是确保模块在不同环境中行为一致的关键文件。
# versions.tf 示例
terraform {
required_version = ">= 1.3.0, < 2.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0.0, < 6.0.0"
}
random = {
source = "hashicorp/random"
version = ">= 3.5.0"
}
}
}
| 约束项 | 推荐写法 | 原因 |
|---|---|---|
required_version |
>= 1.3.0, < 2.0.0 |
确保使用 Terraform 1.x 稳定版本,避免 2.0 大版本不兼容 |
| Provider 版本 | >= 5.0.0, < 6.0.0 |
锁定大版本,允许小版本升级获取 bug 修复 |
| Provider Source | hashicorp/aws |
明确指定 Provider 来源,支持 Terraform Registry 和私有仓库 |
4.3 模块文件关系与数据流
下图展示了四个核心文件之间的数据流向与依赖关系:
4.4 模块目录布局的进阶模式
除了基础的四文件结构,生产级模块仓库通常还会包含以下目录和文件:
examples/:提供至少一个完整的使用示例,展示如何调用该模块。示例本身也是一个完整的 Terraform 配置,可以直接terraform apply测试。tests/:包含自动化测试代码(如 Terratest 或 Terraform 测试框架),验证模块在不同输入下的行为。modules/:当模块变得复杂时,可以拆分为多个嵌套子模块。每个子模块拥有自己的main.tf、variables.tf、outputs.tf。README.md:模块的文档入口,包含模块用途、前置条件、输入输出说明、使用示例等。
模块命名规范: 遵循
terraform-<PROVIDER>-<NAME> 格式(如 terraform-aws-vpc),这是 Terraform Registry 自动识别模块的标准命名方式。
4.5 常见错误与避坑指南
| 错误做法 | 后果 | 正确做法 |
|---|---|---|
将所有代码写在单个 main.tf 中 |
难以维护、代码审查困难、无法自动生成文档 | 按职责拆分为 main.tf、variables.tf、outputs.tf |
变量缺少 type 或 description |
使用者无法了解变量用途,容易传错值 | 每个变量都声明 type 和 description |
| 输出所有资源属性 | 暴露敏感信息,增加模块耦合 | 只输出调用方真正需要的属性 |
不声明 versions.tf |
模块可能因 Terraform/Provider 版本不兼容而失败 | 始终声明 required_version 和 required_providers |
| 在模块内硬编码 Region 或 Account ID | 模块无法跨区域/跨账号复用 | 通过变量传入 Region 和 Account ID |
4.6 模块结构可视化总览
下图展示了标准模块仓库的完整目录结构与文件职责:
快速检查清单: 创建新模块时,先问自己三个问题:
1. 调用方需要传入哪些参数?(→
2. 模块要创建哪些资源?(→
3. 调用方需要获取哪些结果?(→
最后,用
1. 调用方需要传入哪些参数?(→
variables.tf)2. 模块要创建哪些资源?(→
main.tf)3. 调用方需要获取哪些结果?(→
outputs.tf)最后,用
versions.tf 锁定版本,确保模块的长期可维护性。