透過Terraform在AWS上運行基礎網路

Terraform AWS Basic Network

前言

在現代雲端架構中,網路是所有服務的基礎。AWS Virtual Private Cloud(VPC)提供了一個邏輯隔離的虛擬網路環境,讓您可以完全掌控網路設定,包括 IP 位址範圍、子網路、路由表和網路閘道等。

為什麼選擇 Terraform?

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 程式碼詳解

專案結構

建議將 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:關聯到指定的 VPC
  • cidr_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 Gateway
  • aws_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"
  }
}

執行 Terraform

初始化專案

1
terraform init

預期輸出:

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
terraform validate

預期輸出:

1
Success! The configuration is valid.

預覽變更

1
terraform plan

預期輸出(部分):

 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.

執行部署

1
terraform apply

輸入 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

最佳實務與安全考量

網路設計

  1. CIDR 規劃

    • 預留足夠的 IP 空間以因應未來擴展
    • 避免與現有網路或其他 VPC 的 CIDR 重疊
    • 建議使用 /16 作為 VPC CIDR,子網路使用 /24
  2. 多可用區部署

    • 生產環境應在多個可用區部署子網路
    • 提高應用程式的高可用性和容錯能力
  3. 公私有子網路分離

    • 資料庫、快取等應部署在私有子網路
    • 僅對外服務(如 Load Balancer)使用公有子網路

安全性

  1. 最小權限原則

    1
    2
    3
    4
    5
    
    # 不建議:開放所有來源的 SSH
    cidr_blocks = ["0.0.0.0/0"]
    
    # 建議:限制特定 IP 或 CIDR
    cidr_blocks = ["203.0.113.0/24"]  # 公司網路 CIDR
    
  2. 使用 Security Group 而非 NACL

    • Security Group 是有狀態的,較易管理
    • NACL 適用於需要明確拒絕特定流量的場景
  3. 啟用 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
    }
    
  4. EBS 加密

    • 始終啟用 EBS 加密以保護資料
    • 可設定帳戶預設啟用加密

Terraform 最佳實務

  1. 使用遠端狀態後端

    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"
      }
    }
    
  2. 使用 Workspaces 管理環境

    1
    2
    3
    
    terraform workspace new dev
    terraform workspace new staging
    terraform workspace new prod
    
  3. 程式碼格式化與驗證

    1
    2
    
    terraform fmt -recursive
    terraform validate
    
  4. 使用 .gitignore

    1
    2
    3
    4
    5
    6
    
    # .gitignore
    *.tfstate
    *.tfstate.*
    .terraform/
    *.tfvars
    !example.tfvars
    

清理資源

當不再需要這些資源時,執行以下指令清理:

1
terraform destroy

預期輸出:

 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 主要就是將 subnetroute table 綁定在一起的一項功能。若子網路未關聯任何路由表,則會自動使用 VPC 的主路由表。

map_public_ip_on_launch

map_public_ip_on_launch 主要功能是將子網中的 instance 賦予外網 IP。需要注意的是:

  • 此設定僅對新啟動的執行個體生效
  • 若需要固定的公有 IP,應使用 Elastic IP(EIP)
  • 公有 IP 在執行個體停止/啟動後可能會改變

進階學習

掌握了基礎網路架構後,建議繼續學習:

  1. 公私有子網路架構 - 加入 NAT Gateway 讓私有子網路存取網際網路
  2. VPC Peering - 連接多個 VPC
  3. Transit Gateway - 大規模網路連接
  4. VPN 與 Direct Connect - 混合雲架構

參考資源

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy