在 Terraform 中,模組(Modules)是組織和重用基礎設施程式碼的核心機制。透過模組化架構設計,您可以將複雜的基礎設施拆分成可管理、可重用的單元,大幅提升程式碼的維護性和團隊協作效率。
模組概念(Module Concepts)
Terraform 模組是一組相關資源的容器,它將多個資源封裝在一起,形成一個可重用的單元。簡單來說,任何包含 .tf 檔案的目錄都可以被視為一個模組。
模組的類型
| 類型 | 說明 | 範例 |
|---|
| Root Module | 執行 Terraform 命令的主要工作目錄 | 您的專案根目錄 |
| Child Module | 被其他模組呼叫的模組 | ./modules/vpc |
| Published Module | 發布到 Registry 的公開模組 | hashicorp/consul/aws |
模組的優點
- 可重用性:一次編寫,多處使用
- 封裝性:隱藏實作細節,暴露簡潔介面
- 一致性:確保整個組織使用相同的基礎設施模式
- 可測試性:獨立測試各個模組
- 版本控制:追蹤和管理模組變更
建立模組(Creating Modules)
模組結構
一個標準的 Terraform 模組應包含以下檔案:
1
2
3
4
5
6
7
| modules/
└── vpc/
├── main.tf # 主要資源定義
├── variables.tf # 輸入變數
├── outputs.tf # 輸出值
├── versions.tf # Provider 版本限制
└── README.md # 模組說明文件
|
建立 VPC 模組範例
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| 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 格式不正確"
}
}
variable "availability_zones" {
description = "可用區域清單"
type = list(string)
default = ["ap-northeast-1a", "ap-northeast-1c"]
}
variable "public_subnet_cidrs" {
description = "公有子網路 CIDR 清單"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "private_subnet_cidrs" {
description = "私有子網路 CIDR 清單"
type = list(string)
default = ["10.0.101.0/24", "10.0.102.0/24"]
}
variable "enable_nat_gateway" {
description = "是否啟用 NAT Gateway"
type = bool
default = true
}
variable "tags" {
description = "資源標籤"
type = map(string)
default = {}
}
|
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
| # 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"
})
}
# Public Subnets
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"
})
}
# Private Subnets
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"
})
}
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? 1 : 0
domain = "vpc"
tags = merge(var.tags, {
Name = "${var.vpc_name}-nat-eip"
})
depends_on = [aws_internet_gateway.main]
}
# NAT Gateway
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? 1 : 0
allocation_id = aws_eip.nat[0].id
subnet_id = aws_subnet.public[0].id
tags = merge(var.tags, {
Name = "${var.vpc_name}-nat"
})
depends_on = [aws_internet_gateway.main]
}
# Public Route Table
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"
})
}
# Private Route Table
resource "aws_route_table" "private" {
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[0].id
}
}
tags = merge(var.tags, {
Name = "${var.vpc_name}-private-rt"
})
}
# Route Table Associations - Public
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
}
# Route Table Associations - Private
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.id
}
|
outputs.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| 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 "internet_gateway_id" {
description = "Internet Gateway ID"
value = aws_internet_gateway.main.id
}
output "nat_gateway_id" {
description = "NAT Gateway ID"
value = var.enable_nat_gateway ? aws_nat_gateway.main[0].id : null
}
output "public_route_table_id" {
description = "公有路由表 ID"
value = aws_route_table.public.id
}
output "private_route_table_id" {
description = "私有路由表 ID"
value = aws_route_table.private.id
}
|
versions.tf
1
2
3
4
5
6
7
8
9
10
| terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.0.0"
}
}
}
|
使用模組(Using Modules)
呼叫本地模組
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| module "vpc" {
source = "./modules/vpc"
vpc_name = "production-vpc"
vpc_cidr = "10.0.0.0/16"
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnet_cidrs = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
tags = {
Environment = "production"
Project = "web-application"
ManagedBy = "terraform"
}
}
|
存取模組輸出值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 使用 VPC 模組的輸出值建立 EC2 執行個體
resource "aws_instance" "web" {
ami = "ami-0123456789abcdef0"
instance_type = "t3.micro"
subnet_id = module.vpc.public_subnet_ids[0]
tags = {
Name = "web-server"
}
}
# 使用 VPC 模組的輸出值建立 RDS
resource "aws_db_subnet_group" "main" {
name = "main-db-subnet-group"
subnet_ids = module.vpc.private_subnet_ids
tags = {
Name = "Main DB Subnet Group"
}
}
|
多環境部署
透過模組,您可以輕鬆實現多環境部署:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 開發環境
module "vpc_dev" {
source = "./modules/vpc"
vpc_name = "dev-vpc"
vpc_cidr = "10.1.0.0/16"
enable_nat_gateway = false # 開發環境省成本
tags = {
Environment = "development"
}
}
# 正式環境
module "vpc_prod" {
source = "./modules/vpc"
vpc_name = "prod-vpc"
vpc_cidr = "10.2.0.0/16"
enable_nat_gateway = true
tags = {
Environment = "production"
}
}
|
使用 for_each 建立多個模組實例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| variable "environments" {
type = map(object({
vpc_cidr = string
enable_nat_gateway = bool
}))
default = {
dev = {
vpc_cidr = "10.1.0.0/16"
enable_nat_gateway = false
}
staging = {
vpc_cidr = "10.2.0.0/16"
enable_nat_gateway = true
}
prod = {
vpc_cidr = "10.3.0.0/16"
enable_nat_gateway = true
}
}
}
module "vpc" {
source = "./modules/vpc"
for_each = var.environments
vpc_name = "${each.key}-vpc"
vpc_cidr = each.value.vpc_cidr
enable_nat_gateway = each.value.enable_nat_gateway
tags = {
Environment = each.key
}
}
# 存取特定環境的輸出值
output "prod_vpc_id" {
value = module.vpc["prod"].vpc_id
}
|
模組版本管理(Module Versioning)
為什麼需要版本管理?
版本管理確保您的基礎設施程式碼可以:
- 可追溯:知道哪個版本部署了什麼變更
- 可回滾:發生問題時可以快速回到穩定版本
- 可協作:團隊成員使用相同的模組版本
使用 Git 標籤進行版本控制
1
2
3
4
5
6
7
8
9
10
| # 建立版本標籤
git tag -a v1.0.0 -m "Initial VPC module release"
git push origin v1.0.0
# 列出所有標籤
git tag -l
# 建立新版本
git tag -a v1.1.0 -m "Add NAT Gateway support"
git push origin v1.1.0
|
使用 Git 來源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 使用特定標籤
module "vpc" {
source = "git::https://github.com/your-org/terraform-modules.git//vpc?ref=v1.0.0"
}
# 使用特定分支
module "vpc" {
source = "git::https://github.com/your-org/terraform-modules.git//vpc?ref=develop"
}
# 使用特定 commit
module "vpc" {
source = "git::https://github.com/your-org/terraform-modules.git//vpc?ref=abc1234"
}
|
使用 SSH
1
2
3
| module "vpc" {
source = "git@github.com:your-org/terraform-modules.git//vpc?ref=v1.0.0"
}
|
語意化版本(Semantic Versioning)
建議遵循語意化版本規範:
| 版本格式 | 說明 | 範例 |
|---|
| MAJOR | 不相容的 API 變更 | v2.0.0 |
| MINOR | 向下相容的功能新增 | v1.1.0 |
| PATCH | 向下相容的問題修正 | v1.0.1 |
版本約束語法
1
2
3
4
5
6
7
8
9
10
11
| # Terraform Registry 模組支援版本約束
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0" # 允許 5.x.x,但不允許 6.0.0
}
# 其他版本約束範例
version = ">= 1.0.0" # 1.0.0 或更高版本
version = "= 1.0.0" # 僅限 1.0.0
version = "~> 1.0.0" # 1.0.x (patch 更新)
version = ">= 1.0, < 2.0" # 1.x.x 範圍
|
Terraform Registry 是 HashiCorp 官方的模組和 Provider 儲存庫,提供:
- 公開模組:社群維護的開源模組
- 驗證模組:經 HashiCorp 驗證的官方合作夥伴模組
- 私有 Registry:企業版支援私有模組儲存
使用 Registry 模組
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 使用官方 AWS VPC 模組
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.2"
name = "production-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
tags = {
Environment = "production"
Terraform = "true"
}
}
|
熱門 Registry 模組
| 模組名稱 | 說明 | 來源 |
|---|
| terraform-aws-modules/vpc/aws | AWS VPC 完整解決方案 | Registry |
| terraform-aws-modules/eks/aws | AWS EKS 叢集 | Registry |
| terraform-aws-modules/rds/aws | AWS RDS 資料庫 | Registry |
| terraform-aws-modules/s3-bucket/aws | AWS S3 儲存桶 | Registry |
| terraform-aws-modules/security-group/aws | AWS 安全群組 | Registry |
發布模組到 Registry
要將模組發布到公開 Registry,需要:
- 儲存庫命名規範:
terraform-<PROVIDER>-<NAME> - 標準模組結構:包含
main.tf、variables.tf、outputs.tf - 版本標籤:使用語意化版本標籤
- README.md:提供使用說明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| terraform-aws-my-module/
├── main.tf
├── variables.tf
├── outputs.tf
├── versions.tf
├── README.md
├── examples/
│ ├── basic/
│ │ └── main.tf
│ └── complete/
│ └── main.tf
└── modules/
└── submodule/
├── main.tf
├── variables.tf
└── outputs.tf
|
對於企業環境,可以使用私有 Registry:
1
2
3
4
5
6
7
| # 使用 Terraform Cloud 私有 Registry
module "vpc" {
source = "app.terraform.io/your-organization/vpc/aws"
version = "1.0.0"
# 模組參數...
}
|
模組設計最佳實踐
1. 保持模組單一職責
1
2
3
4
5
6
| # 好的做法:每個模組負責一個邏輯單元
modules/
├── vpc/ # 只處理 VPC 相關資源
├── security/ # 只處理安全群組
├── compute/ # 只處理運算資源
└── database/ # 只處理資料庫資源
|
2. 提供合理的預設值
1
2
3
4
5
| variable "instance_type" {
description = "EC2 執行個體類型"
type = string
default = "t3.micro" # 提供合理的預設值
}
|
3. 使用輸入驗證
1
2
3
4
5
6
7
8
9
| variable "environment" {
description = "部署環境"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "環境必須是 dev、staging 或 prod"
}
}
|
4. 完整的輸出值
1
2
3
4
5
6
7
8
9
10
| # 輸出所有可能被其他模組需要的值
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
output "vpc_arn" {
description = "VPC ARN"
value = aws_vpc.main.arn
}
|
5. 撰寫文件和範例
1
2
3
4
5
6
7
8
9
10
11
12
13
| # VPC Module
這個模組建立完整的 AWS VPC 網路架構。
## 使用方式
```hcl
module "vpc" {
source = "./modules/vpc"
vpc_name = "my-vpc"
vpc_cidr = "10.0.0.0/16"
}
|
輸入變數
| 名稱 | 說明 | 類型 | 預設值 |
|---|
| vpc_name | VPC 名稱 | string | - |
| vpc_cidr | VPC CIDR | string | “10.0.0.0/16” |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
## 總結
Terraform 模組化架構設計是建構可擴展、可維護基礎設施程式碼的關鍵。透過本文介紹的概念和實踐,您可以:
- **理解模組概念**:掌握 Root Module、Child Module 和 Published Module 的區別
- **建立標準模組**:遵循最佳實踐建立可重用的模組
- **靈活使用模組**:透過 for_each 和多環境配置實現彈性部署
- **管理模組版本**:使用語意化版本和版本約束確保穩定性
- **善用 Terraform Registry**:利用社群模組加速開發
掌握模組化設計,將大幅提升您的 Terraform 專案品質和團隊協作效率。
## 參考資源
- [Terraform Modules 官方文件](https://developer.hashicorp.com/terraform/language/modules)
- [Terraform Registry](https://registry.terraform.io/)
- [Module Development Guidelines](https://developer.hashicorp.com/terraform/language/modules/develop)
- [terraform-aws-modules GitHub](https://github.com/terraform-aws-modules)
|