前言
在現代雲端架構中,網路是所有服務的基礎。AWS Virtual Private Cloud(VPC)提供了一個邏輯隔離的虛擬網路環境,讓您可以完全掌控網路設定,包括 IP 位址範圍、子網路、路由表和網路閘道等。
Infrastructure as Code(IaC) 是現代 DevOps 實踐中不可或缺的一環,而 Terraform 作為業界領先的 IaC 工具,具備以下優勢:
- 版本控制:所有基礎設施變更都可追蹤,支援 Git 等版本控制系統
- 可重複性:相同的程式碼可在不同環境(開發、測試、生產)中重複使用
- 自動化部署:減少手動操作錯誤,提高部署效率
- 狀態管理:Terraform 維護基礎設施狀態,確保實際環境與程式碼一致
- 跨雲端支援:支援 AWS、Azure、GCP 等多種雲端平台
AWS VPC 核心元件
在深入程式碼之前,讓我們先了解 VPC 的核心元件:
| 元件 | 說明 |
|---|
| VPC | 虛擬私有雲,定義整體網路範圍 |
| Subnet | 子網路,VPC 內的網路區段 |
| Internet Gateway | 網際網路閘道,連接 VPC 與網際網路 |
| Route Table | 路由表,定義網路流量的導向規則 |
| Security 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
26
27
28
29
30
31
32
33
34
35
36
| ┌─────────────────────────────────────────────────────────┐
│ AWS Cloud │
│ ┌─────────────────────────────────────────────────────┐│
│ │ VPC (10.0.0.0/16) ││
│ │ ││
┌──────────┐ │ │ ┌─────────────────────────────────────────────┐ ││
│ Internet │◄────────┼──┼──┤ Internet Gateway (igw) │ ││
└──────────┘ │ │ └───────────────────┬─────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌─────────────────────────────────────────────┐ ││
│ │ │ Route Table (rt) │ ││
│ │ │ ┌─────────────────────────────────────┐ │ ││
│ │ │ │ 0.0.0.0/0 ──► Internet Gateway │ │ ││
│ │ │ │ 10.0.0.0/16 ──► local │ │ ││
│ │ │ └─────────────────────────────────────┘ │ ││
│ │ └───────────────────┬─────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌─────────────────────────────────────────────┐ ││
│ │ │ Public Subnet (10.0.1.0/24) │ ││
│ │ │ us-west-2a │ ││
│ │ │ │ ││
│ │ │ ┌─────────────────────────────────────┐ │ ││
│ │ │ │ ┌─────────────────────────────┐ │ │ ││
│ │ │ │ │ EC2 Instance │ │ │ ││
│ │ │ │ │ (t2.micro / Amazon Linux)│ │ │ ││
│ │ │ │ └─────────────────────────────┘ │ │ ││
│ │ │ │ Security Group │ │ ││
│ │ │ │ Inbound: SSH (22) from 0.0.0.0/0 │ │ ││
│ │ │ │ Outbound: All traffic │ │ ││
│ │ │ └─────────────────────────────────────┘ │ ││
│ │ └─────────────────────────────────────────────┘ ││
│ │ ││
│ └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘
|
以下為原始示意圖:


專案結構
建議將 Terraform 專案組織為以下結構:
1
2
3
4
5
6
| terraform-aws-network/
├── main.tf # 主要資源定義
├── variables.tf # 變數定義
├── outputs.tf # 輸出值定義
├── terraform.tfvars # 變數值設定
└── README.md # 專案說明
|
Provider 設定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # main.tf
terraform {
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
|
說明:
required_version:指定 Terraform 版本需求required_providers:指定 AWS Provider 版本,確保相容性default_tags:為所有資源自動添加標籤,便於成本追蹤和資源管理
變數定義
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
| # variables.tf
variable "aws_region" {
description = "AWS 部署區域"
type = string
default = "us-west-2"
}
variable "project_name" {
description = "專案名稱,用於資源命名"
type = string
default = "terraform-basic-network"
}
variable "environment" {
description = "環境名稱(dev/staging/prod)"
type = string
default = "dev"
}
variable "vpc_cidr" {
description = "VPC CIDR 區塊"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidr" {
description = "公有子網路 CIDR 區塊"
type = string
default = "10.0.1.0/24"
}
variable "availability_zone" {
description = "可用區域"
type = string
default = "us-west-2a"
}
variable "instance_type" {
description = "EC2 執行個體類型"
type = string
default = "t2.micro"
}
variable "key_name" {
description = "SSH 金鑰對名稱"
type = string
default = "demo-key-us-west-2"
}
variable "allowed_ssh_cidr" {
description = "允許 SSH 連線的 CIDR 區塊"
type = list(string)
default = ["0.0.0.0/0"]
}
|
變數化設計的優點:
- 提高程式碼可讀性和維護性
- 支援不同環境使用不同設定
- 敏感資訊可透過環境變數或 tfvars 檔案管理
VPC 資源
1
2
3
4
5
6
7
8
9
10
11
| # main.tf - VPC 資源
resource "aws_vpc" "vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
}
}
|
說明:
cidr_block:定義 VPC 的 IP 位址範圍(/16 提供 65,536 個 IP)enable_dns_hostnames:啟用 DNS 主機名稱,EC2 將獲得公有 DNS 名稱enable_dns_support:啟用 VPC 內的 DNS 解析
子網路
1
2
3
4
5
6
7
8
9
10
11
| resource "aws_subnet" "public_subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_subnet_cidr
availability_zone = var.availability_zone
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-subnet"
Type = "Public"
}
}
|
說明:
vpc_id:關聯到指定的 VPCcidr_block:子網路的 IP 範圍(/24 提供 256 個 IP,扣除保留 IP 後可用 251 個)availability_zone:指定部署的可用區域map_public_ip_on_launch:設為 true 時,啟動的執行個體會自動獲得公有 IP
網際網路閘道
1
2
3
4
5
6
7
| resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "${var.project_name}-igw"
}
}
|
說明:
- Internet Gateway 是 VPC 連接網際網路的入口
- 每個 VPC 只能有一個 Internet Gateway
- 它是水平擴展、高可用的受管服務
路由表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| resource "aws_route_table" "public_rt" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${var.project_name}-public-rt"
}
}
resource "aws_route_table_association" "public_rta" {
subnet_id = aws_subnet.public_subnet.id
route_table_id = aws_route_table.public_rt.id
}
|
說明:
route:定義路由規則,0.0.0.0/0 代表所有流量導向 Internet Gatewayaws_route_table_association:將子網路與路由表關聯- 沒有關聯路由表的子網路會使用 VPC 的主路由表(Main Route Table)
安全群組
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
| resource "aws_security_group" "ec2_sg" {
name = "${var.project_name}-ec2-sg"
description = "Security group for EC2 instance"
vpc_id = aws_vpc.vpc.id
ingress {
description = "SSH from allowed CIDR"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.allowed_ssh_cidr
}
egress {
description = "Allow all outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-ec2-sg"
}
lifecycle {
create_before_destroy = true
}
}
|
說明:
ingress:入站規則,控制進入執行個體的流量egress:出站規則,控制離開執行個體的流量protocol = "-1":代表所有協定lifecycle.create_before_destroy:更新時先建立新的安全群組再刪除舊的,避免服務中斷
EC2 執行個體
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
| # 使用 data source 動態取得最新的 Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
resource "aws_instance" "ec2" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = var.instance_type
key_name = var.key_name
subnet_id = aws_subnet.public_subnet.id
vpc_security_group_ids = [aws_security_group.ec2_sg.id]
root_block_device {
volume_type = "gp3"
volume_size = 8
encrypted = true
delete_on_termination = true
}
tags = {
Name = "${var.project_name}-ec2"
}
}
|
說明:
- 使用
data source 動態取得最新的 AMI,避免硬編碼 AMI ID vpc_security_group_ids:關聯安全群組(注意:VPC 內使用此參數而非 security_groups)root_block_device.encrypted:啟用 EBS 加密,提升資料安全性
輸出值
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
| # outputs.tf
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.vpc.id
}
output "vpc_cidr" {
description = "VPC CIDR block"
value = aws_vpc.vpc.cidr_block
}
output "public_subnet_id" {
description = "Public subnet ID"
value = aws_subnet.public_subnet.id
}
output "internet_gateway_id" {
description = "Internet Gateway ID"
value = aws_internet_gateway.igw.id
}
output "security_group_id" {
description = "EC2 Security Group ID"
value = aws_security_group.ec2_sg.id
}
output "ec2_instance_id" {
description = "EC2 Instance ID"
value = aws_instance.ec2.id
}
output "ec2_public_ip" {
description = "EC2 Public IP"
value = aws_instance.ec2.public_ip
}
output "ec2_public_dns" {
description = "EC2 Public DNS"
value = aws_instance.ec2.public_dns
}
output "ssh_connection" {
description = "SSH connection command"
value = "ssh -i ~/.ssh/${var.key_name}.pem ec2-user@${aws_instance.ec2.public_ip}"
}
|
完整程式碼
以下為整合後的完整建置環境程式碼:
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
| provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "vpc"
}
}
resource "aws_subnet" "subnet" {
vpc_id = aws_vpc.vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-west-2a"
map_public_ip_on_launch = true
tags = {
Name = "subnet"
}
}
resource "aws_route_table_association" "rta" {
subnet_id = aws_subnet.subnet.id
route_table_id = aws_route_table.rt.id
}
resource "aws_route_table" "rt" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "rt"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
tags = {
Name = "igw"
}
}
resource "aws_security_group" "sg" {
name = "sg"
vpc_id = aws_vpc.vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "sg"
}
}
resource "aws_instance" "ec2" {
ami = "ami-0747e613a2a1ff483"
instance_type = "t2.micro"
key_name = "demo-key-us-west-2"
subnet_id = aws_subnet.subnet.id
vpc_security_group_ids = [aws_security_group.sg.id]
tags = {
Name = "ec2"
}
}
|
初始化專案
預期輸出:
1
2
3
4
5
6
7
| Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.xx.x...
- Installed hashicorp/aws v5.xx.x (signed by HashiCorp)
Terraform has been successfully initialized!
|
驗證設定
預期輸出:
1
| Success! The configuration is valid.
|
預覽變更
預期輸出(部分):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| Terraform will perform the following actions:
# aws_instance.ec2 will be created
+ resource "aws_instance" "ec2" {
+ ami = "ami-0747e613a2a1ff483"
+ instance_type = "t2.micro"
...
}
# aws_internet_gateway.igw will be created
+ resource "aws_internet_gateway" "igw" {
+ id = (known after apply)
+ vpc_id = (known after apply)
...
}
# aws_route_table.rt will be created
# aws_route_table_association.rta will be created
# aws_security_group.sg will be created
# aws_subnet.subnet will be created
# aws_vpc.vpc will be created
Plan: 7 to add, 0 to change, 0 to destroy.
|
執行部署
輸入 yes 確認後,預期輸出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| aws_vpc.vpc: Creating...
aws_vpc.vpc: Creation complete after 2s [id=vpc-0123456789abcdef0]
aws_internet_gateway.igw: Creating...
aws_subnet.subnet: Creating...
aws_internet_gateway.igw: Creation complete after 1s [id=igw-0123456789abcdef0]
aws_subnet.subnet: Creation complete after 1s [id=subnet-0123456789abcdef0]
aws_route_table.rt: Creating...
aws_security_group.sg: Creating...
aws_route_table.rt: Creation complete after 1s [id=rtb-0123456789abcdef0]
aws_route_table_association.rta: Creating...
aws_security_group.sg: Creation complete after 2s [id=sg-0123456789abcdef0]
aws_route_table_association.rta: Creation complete after 0s [id=rtbassoc-0123456789abcdef0]
aws_instance.ec2: Creating...
aws_instance.ec2: Still creating... [10s elapsed]
aws_instance.ec2: Creation complete after 15s [id=i-0123456789abcdef0]
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
Outputs:
ec2_public_ip = "54.xxx.xxx.xxx"
ssh_connection = "ssh -i ~/.ssh/demo-key-us-west-2.pem ec2-user@54.xxx.xxx.xxx"
vpc_id = "vpc-0123456789abcdef0"
|
測試
從下圖可以看出我們可遠端連線 EC2 且利用 EC2 上網:

SSH 連線測試
1
2
3
4
5
6
7
8
| # 連線到 EC2
ssh -i ~/.ssh/demo-key-us-west-2.pem ec2-user@<EC2_PUBLIC_IP>
# 測試網路連通性
ping google.com
# 檢查公有 IP
curl ifconfig.me
|
最佳實務與安全考量
網路設計
CIDR 規劃
- 預留足夠的 IP 空間以因應未來擴展
- 避免與現有網路或其他 VPC 的 CIDR 重疊
- 建議使用 /16 作為 VPC CIDR,子網路使用 /24
多可用區部署
- 生產環境應在多個可用區部署子網路
- 提高應用程式的高可用性和容錯能力
公私有子網路分離
- 資料庫、快取等應部署在私有子網路
- 僅對外服務(如 Load Balancer)使用公有子網路
安全性
最小權限原則
1
2
3
4
5
| # 不建議:開放所有來源的 SSH
cidr_blocks = ["0.0.0.0/0"]
# 建議:限制特定 IP 或 CIDR
cidr_blocks = ["203.0.113.0/24"] # 公司網路 CIDR
|
使用 Security Group 而非 NACL
- Security Group 是有狀態的,較易管理
- NACL 適用於需要明確拒絕特定流量的場景
啟用 VPC Flow Logs
1
2
3
4
5
6
| resource "aws_flow_log" "vpc_flow_log" {
vpc_id = aws_vpc.vpc.id
traffic_type = "ALL"
iam_role_arn = aws_iam_role.flow_log_role.arn
log_destination = aws_cloudwatch_log_group.flow_log.arn
}
|
EBS 加密
- 始終啟用 EBS 加密以保護資料
- 可設定帳戶預設啟用加密
使用遠端狀態後端
1
2
3
4
5
6
7
8
9
| terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "network/terraform.tfstate"
region = "us-west-2"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
|
使用 Workspaces 管理環境
1
2
3
| terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
|
程式碼格式化與驗證
1
2
| terraform fmt -recursive
terraform validate
|
使用 .gitignore
1
2
3
4
5
6
| # .gitignore
*.tfstate
*.tfstate.*
.terraform/
*.tfvars
!example.tfvars
|
清理資源
當不再需要這些資源時,執行以下指令清理:
預期輸出:
1
2
3
4
5
6
7
8
9
10
| Plan: 0 to add, 0 to change, 7 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure.
Enter a value: yes
aws_route_table_association.rta: Destroying...
aws_instance.ec2: Destroying...
...
Destroy complete! Resources: 7 destroyed.
|
後記
aws_route_table_association
aws_route_table_association 主要就是將 subnet 與 route table 綁定在一起的一項功能。若子網路未關聯任何路由表,則會自動使用 VPC 的主路由表。
map_public_ip_on_launch
map_public_ip_on_launch 主要功能是將子網中的 instance 賦予外網 IP。需要注意的是:
- 此設定僅對新啟動的執行個體生效
- 若需要固定的公有 IP,應使用 Elastic IP(EIP)
- 公有 IP 在執行個體停止/啟動後可能會改變
進階學習
掌握了基礎網路架構後,建議繼續學習:
- 公私有子網路架構 - 加入 NAT Gateway 讓私有子網路存取網際網路
- VPC Peering - 連接多個 VPC
- Transit Gateway - 大規模網路連接
- VPN 與 Direct Connect - 混合雲架構
參考資源