模块基础与设计
学习如何创建、使用和管理 Terraform 模块。
一、什么是模块
模块是 Terraform 配置的容器,用于组织和复用代码。
1.1 模块的优势
- 复用性:一次编写,多次使用
- 组织性:将复杂配置分解为逻辑单元
- 抽象性:隐藏实现细节
- 维护性:集中管理和更新
- 共享性:团队和社区共享
1.2 模块的类型
根模块(Root Module):
project/
├── main.tf
├── variables.tf
└── outputs.tf
子模块(Child Module):
project/
├── main.tf
└── modules/
└── vpc/
├── main.tf
├── variables.tf
└── outputs.tf
二、创建第一个模块
2.1 VPC 模块结构
modules/vpc/
├── main.tf # 主要资源定义
├── variables.tf # 输入变量
├── outputs.tf # 输出值
└── README.md # 文档
2.2 定义输入变量
modules/vpc/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 = "必须是有效的 CIDR 块。"
}
}
variable "availability_zones" {
description = "可用区列表"
type = list(string)
}
variable "public_subnet_cidrs" {
description = "公有子网 CIDR 列表"
type = list(string)
}
variable "private_subnet_cidrs" {
description = "私有子网 CIDR 列表"
type = list(string)
}
variable "enable_nat_gateway" {
description = "是否启用 NAT 网关"
type = bool
default = true
}
variable "tags" {
description = "资源标签"
type = map(string)
default = {}
}
2.3 实现资源
modules/vpc/main.tf:
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(
var.tags,
{
Name = var.vpc_name
}
)
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-igw"
}
)
}
# 公有子网
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.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}"
Type = "Public"
}
)
}
# 私有子网
resource "aws_subnet" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.private_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-private-${count.index + 1}"
Type = "Private"
}
)
}
# NAT Gateway EIP
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
domain = "vpc"
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-eip-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.main]
}
# NAT Gateway
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? length(var.public_subnet_cidrs) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-nat-${count.index + 1}"
}
)
depends_on = [aws_internet_gateway.main]
}
# 公有路由表
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-public-rt"
}
)
}
# 公有子网路由表关联
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# 私有路由表
resource "aws_route_table" "private" {
count = length(var.private_subnet_cidrs)
vpc_id = aws_vpc.main.id
dynamic "route" {
for_each = var.enable_nat_gateway ? [1] : []
content {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
}
tags = merge(
var.tags,
{
Name = "${var.vpc_name}-private-rt-${count.index + 1}"
}
)
}
# 私有子网路由表关联
resource "aws_route_table_association" "private" {
count = length(var.private_subnet_cidrs)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
2.4 定义输出
modules/vpc/outputs.tf:
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "vpc_cidr" {
description = "VPC CIDR 块"
value = aws_vpc.main.cidr_block
}
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_ips" {
description = "NAT 网关公网 IP"
value = aws_eip.nat[*].public_ip
}
output "internet_gateway_id" {
description = "Internet Gateway ID"
value = aws_internet_gateway.main.id
}
三、使用模块
3.1 基本用法
# main.tf
module "vpc" {
source = "./modules/vpc"
vpc_name = "production-vpc"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = true
tags = {
Environment = "Production"
ManagedBy = "Terraform"
}
}
# 使用模块输出
resource "aws_instance" "app" {
ami = "ami-xxx"
instance_type = "t3.micro"
subnet_id = module.vpc.private_subnet_ids[0]
tags = {
Name = "app-server"
}
}
output "vpc_id" {
value = module.vpc.vpc_id
}
3.2 多次使用模块
# 开发环境 VPC
module "dev_vpc" {
source = "./modules/vpc"
vpc_name = "dev-vpc"
vpc_cidr = "10.1.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b"]
public_subnet_cidrs = ["10.1.1.0/24", "10.1.2.0/24"]
private_subnet_cidrs = ["10.1.11.0/24", "10.1.12.0/24"]
enable_nat_gateway = false # 开发环境不需要 NAT
tags = {
Environment = "Development"
}
}
# 生产环境 VPC
module "prod_vpc" {
source = "./modules/vpc"
vpc_name = "prod-vpc"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
enable_nat_gateway = true
tags = {
Environment = "Production"
}
}
四、模块源
4.1 本地路径
module "vpc" {
source = "./modules/vpc"
}
module "vpc_relative" {
source = "../shared-modules/vpc"
}
module "vpc_absolute" {
source = "/absolute/path/to/modules/vpc"
}
4.2 Terraform Registry
# 官方模块
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
}
# 私有 Registry
module "vpc" {
source = "app.terraform.io/my-org/vpc/aws"
version = "1.0.0"
}
4.3 Git 仓库
# HTTPS
module "vpc" {
source = "git::https://github.com/username/terraform-modules.git//vpc?ref=v1.0.0"
}
# SSH
module "vpc" {
source = "git::ssh://git@github.com/username/terraform-modules.git//vpc?ref=v1.0.0"
}
# 分支或标签
module "vpc" {
source = "git::https://github.com/username/terraform-modules.git//vpc?ref=main"
}
4.4 HTTP URL
module "vpc" {
source = "https://example.com/terraform-modules/vpc.zip"
}
五、模块设计原则
5.1 单一职责
# ✅ 好的设计:VPC 模块只负责网络
modules/vpc/
# ✅ 好的设计:应用模块只负责计算资源
modules/app/
# ❌ 不好的设计:一个模块做所有事情
modules/infrastructure/ # 包含 VPC、数据库、应用等
5.2 合理的抽象层次
# ✅ 高层模块:业务导向
module "web_application" {
source = "./modules/web-app"
app_name = "myapp"
environment = "prod"
}
# ✅ 低层模块:技术导向
module "load_balancer" {
source = "./modules/alb"
name = "my-alb"
subnets = var.subnet_ids
security_groups = var.security_group_ids
}
5.3 清晰的接口
# ✅ 好的接口:必需参数少,有合理默认值
module "vpc" {
source = "./modules/vpc"
name = "my-vpc" # 唯一必需参数
# 其他参数都有默认值
}
# ❌ 不好的接口:太多必需参数
module "vpc" {
source = "./modules/vpc"
# 需要设置 20+ 个参数...
}
5.4 文档完善
modules/vpc/README.md:
# VPC 模块
创建 AWS VPC,包含公有和私有子网。
## 使用示例
\`\`\`hcl
module "vpc" {
source = "./modules/vpc"
vpc_name = "production-vpc"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["us-west-2a", "us-west-2b"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
}
\`\`\`
## 输入变量
| 名称 | 描述 | 类型 | 默认值 | 必需 |
|------|------|------|--------|------|
| vpc_name | VPC 名称 | string | - | 是 |
| vpc_cidr | VPC CIDR 块 | string | 10.0.0.0/16 | 否 |
## 输出
| 名称 | 描述 |
|------|------|
| vpc_id | VPC ID |
| public_subnet_ids | 公有子网 ID 列表 |
六、模块版本管理
6.1 语义化版本
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0" # 精确版本
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # >= 5.0.0 且 < 6.0.0
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = ">= 5.0.0, < 6.0.0" # 版本范围
}
6.2 Git 标签
module "vpc" {
source = "git::https://github.com/username/modules.git//vpc?ref=v1.0.0"
}
七、模块测试
7.1 示例配置
modules/vpc/
├── main.tf
├── variables.tf
├── outputs.tf
└── examples/
├── basic/
│ ├── main.tf
│ └── README.md
└── complete/
├── main.tf
└── README.md
examples/basic/main.tf:
module "vpc" {
source = "../../"
vpc_name = "example-vpc"
availability_zones = ["us-west-2a", "us-west-2b"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.11.0/24", "10.0.12.0/24"]
}
output "vpc_id" {
value = module.vpc.vpc_id
}
7.2 自动化测试
使用 Terratest(Go):
// test/vpc_test.go
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestVPCModule(t *testing.T) {
terraformOptions := &terraform.Options{
TerraformDir: "../examples/basic",
}
defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)
}
小结
模块设计的关键要点:
- 结构清晰:输入、处理、输出
- 职责单一:一个模块做好一件事
- 接口简洁:最少必需参数
- 文档完善:README + 注释
- 版本管理:语义化版本
- 可测试性:提供示例和测试
下一章我们将学习如何发布和共享模块。