Terraform 模組化架構設計

Learn how to design modular Terraform architectures including module concepts, creating modules, using modules, version management, and Terraform Registry

在 Terraform 中,模組(Modules)是組織和重用基礎設施程式碼的核心機制。透過模組化架構設計,您可以將複雜的基礎設施拆分成可管理、可重用的單元,大幅提升程式碼的維護性和團隊協作效率。

模組概念(Module Concepts)

什麼是 Terraform 模組?

Terraform 模組是一組相關資源的容器,它將多個資源封裝在一起,形成一個可重用的單元。簡單來說,任何包含 .tf 檔案的目錄都可以被視為一個模組。

模組的類型

類型說明範例
Root Module執行 Terraform 命令的主要工作目錄您的專案根目錄
Child Module被其他模組呼叫的模組./modules/vpc
Published Module發布到 Registry 的公開模組hashicorp/consul/aws

模組的優點

  1. 可重用性:一次編寫,多處使用
  2. 封裝性:隱藏實作細節,暴露簡潔介面
  3. 一致性:確保整個組織使用相同的基礎設施模式
  4. 可測試性:獨立測試各個模組
  5. 版本控制:追蹤和管理模組變更

建立模組(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

在 Terraform 中指定模組版本

使用 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

什麼是 Terraform Registry?

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/awsAWS VPC 完整解決方案Registry
terraform-aws-modules/eks/awsAWS EKS 叢集Registry
terraform-aws-modules/rds/awsAWS RDS 資料庫Registry
terraform-aws-modules/s3-bucket/awsAWS S3 儲存桶Registry
terraform-aws-modules/security-group/awsAWS 安全群組Registry

發布模組到 Registry

要將模組發布到公開 Registry,需要:

  1. 儲存庫命名規範terraform-<PROVIDER>-<NAME>
  2. 標準模組結構:包含 main.tfvariables.tfoutputs.tf
  3. 版本標籤:使用語意化版本標籤
  4. 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(Terraform Cloud/Enterprise)

對於企業環境,可以使用私有 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_nameVPC 名稱string-
vpc_cidrVPC CIDRstring“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)
comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy