Terraform模块化设计
约 1232 字大约 4 分钟
terraformmodules
2025-06-26
Terraform 模块是组织和复用基础设施代码的核心机制。良好的模块化设计能提高代码的可维护性、可测试性和团队协作效率。本文将深入讲解模块的设计原则、组合模式和最佳实践。
模块基本概念
Terraform 中每个包含 .tf 文件的目录都是一个模块。根模块(Root Module)是执行 terraform apply 的目录,子模块通过 module 块引用。
模块结构
标准的模块目录结构:
modules/vpc/
├── main.tf # 核心资源定义
├── variables.tf # 输入变量
├── outputs.tf # 输出值
├── versions.tf # Provider 版本约束
├── data.tf # 数据源(可选)
├── locals.tf # 局部变量(可选)
├── README.md # 模块文档
├── examples/ # 使用示例
│ └── complete/
│ ├── main.tf
│ └── outputs.tf
└── tests/ # 测试文件
└── vpc_test.goInput Variables(输入变量)
# modules/vpc/variables.tf
variable "name" {
description = "VPC name prefix"
type = string
validation {
condition = length(var.name) >= 3 && length(var.name) <= 24
error_message = "Name must be between 3 and 24 characters."
}
}
variable "cidr_block" {
description = "VPC CIDR block"
type = string
default = "10.0.0.0/16"
validation {
condition = can(cidrhost(var.cidr_block, 0))
error_message = "Must be a valid CIDR block."
}
}
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "CIDR blocks for private subnets"
type = list(string)
default = []
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
default = []
}
variable "enable_nat_gateway" {
description = "Whether to create NAT Gateway"
type = bool
default = true
}
variable "single_nat_gateway" {
description = "Use single NAT Gateway (cost savings)"
type = bool
default = false
}
variable "tags" {
description = "Additional tags for all resources"
type = map(string)
default = {}
}
# 复杂类型变量
variable "vpc_endpoints" {
description = "VPC Endpoints configuration"
type = list(object({
service_name = string
type = optional(string, "Gateway")
private_dns = optional(bool, true)
}))
default = []
}Output Variables(输出变量)
# modules/vpc/outputs.tf
output "vpc_id" {
description = "The ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_cidr" {
description = "The CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
output "private_subnet_ids" {
description = "List of private subnet IDs"
value = aws_subnet.private[*].id
}
output "public_subnet_ids" {
description = "List of public subnet IDs"
value = aws_subnet.public[*].id
}
output "nat_gateway_ips" {
description = "NAT Gateway public IPs"
value = aws_eip.nat[*].public_ip
}
# 敏感输出
output "db_password" {
description = "Generated database password"
value = random_password.db.result
sensitive = true
}模块调用
# root/main.tf
module "vpc" {
source = "./modules/vpc"
name = "production"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
private_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = false
tags = {
Environment = "production"
Team = "platform"
}
}
module "eks" {
source = "./modules/eks"
cluster_name = "prod-cluster"
cluster_version = "1.28"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
node_groups = {
general = {
instance_types = ["m6i.xlarge"]
min_size = 3
max_size = 10
desired_size = 5
}
spot = {
instance_types = ["m6i.xlarge", "m5.xlarge"]
capacity_type = "SPOT"
min_size = 0
max_size = 20
desired_size = 3
}
}
depends_on = [module.vpc]
}
module "rds" {
source = "./modules/rds"
identifier = "prod-db"
engine = "aurora-mysql"
engine_version = "8.0"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
instance_count = 3
instance_class = "db.r6g.xlarge"
}Module Sources(模块来源)
# 本地路径
module "vpc" {
source = "./modules/vpc"
}
# Terraform Registry
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"
}
# GitHub
module "vpc" {
source = "github.com/example/terraform-aws-vpc?ref=v2.0.0"
}
# Git (通用)
module "vpc" {
source = "git::https://git.example.com/modules/vpc.git?ref=v1.0.0"
}
# S3 Bucket
module "vpc" {
source = "s3::https://s3-us-east-1.amazonaws.com/my-bucket/vpc.zip"
}版本管理
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# 模块版本约束
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # >= 5.0.0, < 6.0.0
# version = ">= 5.0, < 5.5" # 更精确的约束
}组合模式
Flat Module Pattern(扁平模块)
适合小型项目:
project/
├── main.tf
├── networking.tf
├── compute.tf
├── database.tf
├── variables.tf
└── outputs.tfNested Module Pattern(嵌套模块)
适合中大型项目:
Composition Pattern(组合模式)
将小模块组合为更大的抽象:
# modules/platform/main.tf
# 组合多个小模块为一个平台模块
module "vpc" {
source = "../vpc"
# ...
}
module "eks" {
source = "../eks"
vpc_id = module.vpc.vpc_id
# ...
}
module "addons" {
source = "../eks-addons"
cluster_name = module.eks.cluster_name
# ...
}Terragrunt
Terragrunt 是 Terraform 的包装工具,解决代码重复和环境管理问题:
infrastructure/
├── terragrunt.hcl # 根配置
├── _envcommon/ # 环境通用配置
│ ├── vpc.hcl
│ └── eks.hcl
├── production/
│ ├── env.hcl # 环境变量
│ ├── vpc/
│ │ └── terragrunt.hcl
│ └── eks/
│ └── terragrunt.hcl
└── staging/
├── env.hcl
├── vpc/
│ └── terragrunt.hcl
└── eks/
└── terragrunt.hcl# infrastructure/terragrunt.hcl (根配置)
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "my-terraform-state"
key = "${path_relative_to_include()}/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
generate "provider" {
path = "provider.tf"
if_exists = "overwrite"
contents = <<EOF
provider "aws" {
region = "${local.region}"
}
EOF
}# infrastructure/production/vpc/terragrunt.hcl
include "root" {
path = find_in_parent_folders()
}
include "env" {
path = "${get_terragrunt_dir()}/../../_envcommon/vpc.hcl"
}
inputs = {
name = "production"
cidr_block = "10.0.0.0/16"
}# Terragrunt 命令
cd infrastructure/production
terragrunt run-all plan # 并行 plan 所有模块
terragrunt run-all apply # 按依赖顺序 apply
terragrunt graph # 查看依赖图模块测试
Terraform Test(原生测试框架,1.6+)
# tests/vpc_test.tftest.hcl
provider "aws" {
region = "us-east-1"
}
variables {
name = "test-vpc"
cidr_block = "10.99.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b"]
}
run "create_vpc" {
command = apply
assert {
condition = aws_vpc.main.cidr_block == "10.99.0.0/16"
error_message = "VPC CIDR block mismatch"
}
assert {
condition = length(aws_subnet.private) == 2
error_message = "Expected 2 private subnets"
}
}
run "validate_outputs" {
command = plan
assert {
condition = output.vpc_id != ""
error_message = "VPC ID should not be empty"
}
}Terratest(Go 测试框架)
func TestVpcModule(t *testing.T) {
t.Parallel()
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: "../modules/vpc",
Vars: map[string]interface{}{
"name": "test-vpc",
"cidr_block": "10.99.0.0/16",
"availability_zones": []string{"us-east-1a", "us-east-1b"},
},
})
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
subnetIds := terraform.OutputList(t, terraformOptions, "private_subnet_ids")
assert.Equal(t, 2, len(subnetIds))
}总结
Terraform 模块化设计的关键原则:
- 单一职责:每个模块只负责一组相关资源
- 明确接口:通过 variables 和 outputs 定义清晰的输入输出
- 合理抽象:模块应隐藏实现细节,暴露必要的配置项
- 版本锁定:始终指定模块和 Provider 的版本约束
- 使用 Terragrunt 管理多环境配置,减少代码重复
- 编写测试:使用 Terraform Test 或 Terratest 验证模块行为
- 文档先行:每个模块包含 README 和使用示例
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于