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 模块文件关系与数据流

下图展示了四个核心文件之间的数据流向与依赖关系:

模块核心文件数据流 模块调用方 (Root Module) variables.tf 输入变量声明 main.tf 资源定义 outputs.tf 输出值声明 versions.tf 版本约束 传入参数 var.* 引用 资源属性 返回输出 Provider 版本约束 Terraform 版本约束

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 模块结构可视化总览

下图展示了标准模块仓库的完整目录结构与文件职责:

标准 Terraform 模块仓库结构 terraform-aws-vpc/(模块根目录) 核心文件(必须) main.tf variables.tf outputs.tf versions.tf 推荐文件 README.md examples/ tests/ modules/ 文件职责总结 main.tf:资源定义 | variables.tf:输入接口 | outputs.tf:输出接口 | versions.tf:版本约束 README.md:文档 | examples/:使用示例 | tests/:自动化测试 | modules/:嵌套子模块
快速检查清单: 创建新模块时,先问自己三个问题:
1. 调用方需要传入哪些参数?(→ variables.tf)
2. 模块要创建哪些资源?(→ main.tf)
3. 调用方需要获取哪些结果?(→ outputs.tf)
最后,用 versions.tf 锁定版本,确保模块的长期可维护性。