Module 设计与复用
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read273 words

Module 设计与复用

核心问题:VPC、EKS、RDS 等基础设施模式在多个项目中重复出现——怎样把它们封装成可复用的模块?


什么是 Module

Module 是一组相关 Terraform 资源的封装,就像编程语言中的函数——有输入(variables)、有输出(outputs)、内部实现细节对调用者透明。

graph TB ROOT["根模块(Root Module)
main.tf"] VPC["vpc 模块
modules/vpc/"] EKS["eks 模块
modules/eks/"] RDS["rds 模块
modules/rds/"] ROOT -->|调用| VPC ROOT -->|调用| EKS ROOT -->|调用| RDS VPC -->|输出 vpc_id| EKS VPC -->|输出 subnet_ids| RDS

Module 目录结构

modules/
└── vpc/
├── main.tf          # 资源定义
├── variables.tf     # 输入变量(模块接口)
├── outputs.tf       # 输出值(供调用者使用)
└── README.md        # 模块说明

编写 VPC 模块

# modules/vpc/variables.tf
variable "name" {
description = "VPC 名称前缀"
type        = string
}
variable "cidr" {
description = "VPC CIDR 块"
type        = string
default     = "10.0.0.0/16"
}
variable "azs" {
description = "可用区列表"
type        = list(string)
}
variable "private_subnets" {
description = "私有子网 CIDR 列表"
type        = list(string)
}
variable "public_subnets" {
description = "公有子网 CIDR 列表"
type        = list(string)
}
variable "enable_nat_gateway" {
description = "是否启用 NAT Gateway"
type        = bool
default     = true
}
variable "tags" {
description = "通用标签"
type        = map(string)
default     = {}
}
# modules/vpc/main.tf
resource "aws_vpc" "this" {
cidr_block           = var.cidr
enable_dns_hostnames = true
enable_dns_support   = true
tags = merge(var.tags, { Name = var.name })
}
resource "aws_subnet" "public" {
count             = length(var.public_subnets)
vpc_id            = aws_vpc.this.id
cidr_block        = var.public_subnets[count.index]
availability_zone = var.azs[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.name}-public-${count.index + 1}"
Tier = "Public"
"kubernetes.io/role/elb" = "1"   # EKS ALB Controller 需要
})
}
resource "aws_subnet" "private" {
count             = length(var.private_subnets)
vpc_id            = aws_vpc.this.id
cidr_block        = var.private_subnets[count.index]
availability_zone = var.azs[count.index]
tags = merge(var.tags, {
Name = "${var.name}-private-${count.index + 1}"
Tier = "Private"
"kubernetes.io/role/internal-elb" = "1"
})
}
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags   = merge(var.tags, { Name = "${var.name}-igw" })
}
resource "aws_eip" "nat" {
count  = var.enable_nat_gateway ? 1 : 0
domain = "vpc"
tags   = merge(var.tags, { Name = "${var.name}-nat-eip" })
}
resource "aws_nat_gateway" "this" {
count         = var.enable_nat_gateway ? 1 : 0
allocation_id = aws_eip.nat[0].id
subnet_id     = aws_subnet.public[0].id
depends_on    = [aws_internet_gateway.this]
tags          = merge(var.tags, { Name = "${var.name}-nat" })
}
# modules/vpc/outputs.tf
output "vpc_id" {
description = "VPC ID"
value       = aws_vpc.this.id
}
output "public_subnet_ids" {
description = "公有子网 ID 列表"
value       = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "私有子网 ID 列表"
value       = aws_subnet.private[*].id
}
output "nat_gateway_id" {
description = "NAT Gateway ID"
value       = try(aws_nat_gateway.this[0].id, null)
}

调用 Module

# 根模块 main.tf
module "vpc" {
source = "./modules/vpc"    # 本地路径
name = "${var.env}-myapp"
cidr = "10.0.0.0/16"
azs  = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = var.env == "production"
tags = local.common_tags
}
module "eks" {
source = "./modules/eks"
cluster_name = "${var.env}-cluster"
vpc_id       = module.vpc.vpc_id           # 引用 vpc 模块输出
subnet_ids   = module.vpc.private_subnet_ids
}

使用 Terraform Registry 上的公共模块

# 使用官方 AWS VPC 模块(terraform-aws-modules/vpc/aws)
module "vpc" {
source  = "terraform-aws-modules/vpc/aws"
version = "5.2.0"    # 锁定版本!
name    = "${var.env}-vpc"
cidr    = "10.0.0.0/16"
azs     = data.aws_availability_zones.available.names
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = var.env != "production"   # 非生产省钱用单个 NAT
tags = local.common_tags
}
# 使用官方 EKS 模块
module "eks" {
source  = "terraform-aws-modules/eks/aws"
version = "20.0.0"
cluster_name    = "${var.env}-cluster"
cluster_version = "1.29"
vpc_id          = module.vpc.vpc_id
subnet_ids      = module.vpc.private_subnets
eks_managed_node_groups = {
default = {
min_size     = 2
max_size     = 10
desired_size = var.env == "production" ? 3 : 2
instance_types = ["t3.medium"]
}
}
}

私有模块仓库

# 从 Git 仓库加载模块(私有 GitHub)
module "vpc" {
source = "git::https://github.com/myorg/terraform-modules.git//vpc?ref=v1.2.0"
# // 双斜线后是仓库内子目录,?ref= 是 tag/branch/commit
}
# 从 Terraform Cloud 私有仓库
module "vpc" {
source  = "app.terraform.io/myorg/vpc/aws"
version = "1.2.0"
}

Module 设计原则

原则 说明 示例
单一职责 每个模块只管一类资源 vpceksrds 分开,不要做"大而全"模块
稳定的接口 variables.tf 变更要向后兼容 新增变量给默认值,不要删除已有变量
有意义的输出 输出所有可能被调用者用到的属性 vpc_idsubnet_idssecurity_group_ids
不含 Provider 配置 Provider 由根模块配置,模块不配置 模块里不写 provider "aws" {}
版本锁定 外部模块锁定版本 version = "5.2.0"

.terraform.lock.hcl:依赖锁文件

# .terraform.lock.hcl(应该提交到 Git!)
provider "registry.terraform.io/hashicorp/aws" {
version     = "5.31.0"
constraints = "~> 5.0"
hashes = [
"h1:abc123...",
"zh:def456...",
]
}
# 更新锁文件
terraform init -upgrade
# 多平台锁文件(为 CI Linux 和本地 macOS 同时生成)
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64

下一节Workspace 与多账号管理