用 Terraform 一鍵開關 AWS ( EC2 + RDS + ECR + IAM )
2026-06-27 17:32:31
最近有需求,需要開 VM 實驗看看,實驗完畢就會通通關閉,剛好有這個需求,所以來研究用 terraform 一次開好需要的機器,並且一次關閉
需求可參考這篇 XD [秒殺售票系統:四種庫存扣減策略的設計與實測(ORM / Raw SQL / ExecuteUpdate / Redis)](https://wehelp.tw/topic/6223246856814592)
> 這篇帶你從 0 用 IaC ( 基礎設施即程式碼 ) 開好一整套 AWS 資源,實驗完再一鍵拆掉——省成本、免手動點 Console、還能版本控制。
[terraform](https://developer.hashicorp.com/terraform) :讓開發者能透過簡單、易讀的程式碼,安全且高效地建立、修改與版本控制雲端及地端資源,免去手動點擊網頁介面設定的麻煩,可支援 AWS、Google Cloud、Azure 等平台
本次使用 AWS 當作範例
## AWS CLI 環境建置
安裝 CLI 後,先去 AWS IAM 服務申請一個 CLI 專用的 Key,接著使用 aws configure,依序填入
1. AWS Access Key ID
2. AWS Secret Access Key
3. Default region name
4. Default output format
## terraform 四個步驟
### 步驟一
初始化
```
terraform init
```
### 步驟二
看預計生成的計畫,新增了那些機器,此步驟還不會真的開始開機器
```
terraform plan
```
### 步驟三
按照計畫,開設機器
```
terraform apply
```
我真的在開設的時候,大約花 10 min ,計畫中開最久的是 RDS
並且我是先開設 ECR ,然後再本地 Build Docker Image ,推上去以後
才接著 apply 全部計畫,因為 ECR 還不會扣錢,所以先做
單獨開 ECR 的指令(PowerShell 記得把值加引號):
```
terraform apply -target="aws_ecr_repository.api"
```
### 步驟四
一鍵清除計畫中的服務
```
terraform destroy
```
清除的時候也是 RDS 最久,耗時約 10 min
## 準備 terraform.tfvars
可以直接去 AWS EC2 建立一個 Key-pair,用來連進去 EC2 用的,
反正 AWS CLI 環境都弄好了,直接用指令建立 Key-Pair
```
aws ec2 create-key-pair \
--key-name MyEC2KeyPair \
--query "KeyMaterial" \
--output text > MyEC2KeyPair.pem
```
接著輸入其他資訊到 terraform.tfvars
```
# terraform.tfvars
my_ip = "1.2.3.4/32" # 你的公網 IP,查 https://checkip.amazonaws.com 再加 /32
key_name = "my-ec2-keypair" # 先建好的 EC2 key pair 名稱
db_password = "ChangeMe!Str0ng#Pwd" # SQL Server 複雜度:大小寫+數字+符號
# 其餘有預設,需要才覆寫:
# region = "ap-northeast-1" # 想開的機器在哪個區域
# api_instance_type = "t3.large" # EC2 instance type
# k6_instance_type = "t3.medium"
# rds_instance_class = "db.m5.large" # RDS instance type
```
## 細節 Plan
### 預計會開的東西

子網 B 的用途:純粹是為了滿足 RDS 的硬性規定
RDS 規定:DB 子網組(DB Subnet Group)必須涵蓋「至少 2 個可用區(AZ)」的子網。 所以即使我們的 RDS 實際上只跑在一個 AZ,你也被迫要準備第二個子網(在不同 AZ)給它,否則 apply 會直接報錯。
### 檔案架構
```
infra/
├── main.tf # Provider + 網路骨架(VPC、2 子網、IGW、路由表)
├── network.tf # 3 個 Security Group(api / k6 / rds)+ 進出站規則
├── ecr.tf # ECR 私有映像倉庫
├── ssm.tf # SSM SecureString(加密存 DB 連線字串)
├── rds.tf # RDS SQL Server + DB 子網組 + 引擎版本查詢
├── compute.tf # AMI 查詢、IAM 角色/instance profile、2 台 EC2
├── user-data-api.sh.tpl # api EC2 開機腳本(裝 docker、拉映像、起服務)
├── variables.tf # 輸入變數(region、my_ip、密碼、機型…)
├── outputs.tf # 輸出(api IP、ECR push 指令、SSH 指令)
├── terraform.tfvars.example # 變數範本(佔位值,進 git)
│
├── terraform.tfvars # gitignored
├── .terraform/ # gitignored
├── .terraform.lock.hcl # gitignored
└── terraform.tfstate # 狀態檔(含明文敏感值)— gitignored
同資料夾所有 .tf 會自動合併,拆檔純粹給人讀;terraform.tfvars 與 tfstate 含祕鑰 , 不進 git。
```
#### Security Group(資源層級防火牆)
```
resource "aws_security_group" "api" {
name = "ticketing-api-sg"
vpc_id = aws_vpc.main.id
}
# 進站:8080 只放行「來源是 k6-sg」的流量
resource "aws_vpc_security_group_ingress_rule" "api_8080_from_k6" {
security_group_id = aws_security_group.api.id
referenced_security_group_id = aws_security_group.k6.id # 來源 = 另一個群組
from_port = 8080
to_port = 8080
ip_protocol = "tcp"
}
```
#### ECR ( 私有映像倉庫 )
```
# === Docker 映像倉庫 === console: ECR > Repositories
resource "aws_ecr_repository" "api" {
name = "ticketingapi"
image_tag_mutability = "MUTABLE" # 同一個 tag(如 latest)可被覆蓋
force_delete = true # destroy 時即使倉庫內有映像也照刪
}
```
像私有版 Docker Hub。
name 是倉庫名,MUTABLE 讓你能一直用 latest 重推,
force_delete=true 是為了 terraform destroy 能順利拆掉(預設有映像會擋住刪除)。
映像在本機 build → 推上 ECR → EC2 用 IAM 角色 pull , 組成一條「不需帳密」的映像供應鏈。
> **踩過的坑**:`force_delete` 的另一面是——`terraform destroy` 會把倉庫裡的**映像一起刪掉**。
> 所以下次重新 `apply`,「**先開 ECR → 推映像 → 再 apply 全部**」這個推映像步驟**絕對不能跳**,
> 否則 EC2 開機 pull 不到映像、容器起不來,你會看到一連串 `connection refused` 卻找不到原因。
#### EC2 ( 虛擬機 )
```
# === api 機器(跑 api+redis 容器)=== console: EC2 > Instances
resource "aws_instance" "api" {
ami = data.aws_ssm_parameter.al2023_ami.value # 系統映像(AL2023)
instance_type = "t3.large" # 規格(CPU/記憶體)→ 影響錢與馬力
subnet_id = aws_subnet.public_a.id # 放哪個子網
vpc_security_group_ids = [aws_security_group.api.id] # 套哪個防火牆
iam_instance_profile = aws_iam_instance_profile.api.name # 戴哪個身分
key_name = var.key_name # 哪把 SSH 金鑰能登入
user_data = local.user_data_api # 開機自動跑的腳本
}
```
每個參數對應一個「開機選項」——
1. 用哪個系統(ami)
2. 多大台(instance_type)
3. 放哪(subnet_id)
4. 誰能進(vpc_security_group_ids)
5. 有什麼權限(iam_instance_profile)
6. 誰能 SSH(key_name)
7. 開機做什麼(user_data)
user_data 是「開機即服務」——機器一啟動就自己裝 docker、拉映像、把容器跑起來,全程零手動。
#### IAM ( 用身分取代金鑰 )
一個角色身上掛兩種政策:信任政策(誰能扮演)+ 權限政策(能做什麼)。
```
# 信任政策:只有 EC2 服務能扮演這角色 console: IAM > Roles > Trust relationships
data "aws_iam_policy_document" "ec2_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"] # 白名單對象 = EC2
}
}
}
# 角色本體 , 套上 aws_iam_policy_document
resource "aws_iam_role" "api" {
name = "ticketing-api-role"
assume_role_policy = data.aws_iam_policy_document.ec2_assume.json
}
# 權限:ECR 唯讀 (AWS 現成政策)
resource "aws_iam_role_policy_attachment" "ecr_read" {
role = aws_iam_role.api.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
}
# 權限:讀 /ticketing/ 的 SSM 參數 + 解密(自訂政策,內容略)
resource "aws_iam_role_policy" "ssm_read" {
role = aws_iam_role.api.id
policy = data.aws_iam_policy_document.ssm_read.json
}
# Instance Profile:把角色「裝」到 EC2 的轉接頭
resource "aws_iam_instance_profile" "api" {
name = "ticketing-api-profile"
role = aws_iam_role.api.name
}
```
## 小結
一套 IaC 寫好之後,整個實驗環境就變成可以「**一鍵開、一鍵拆**」:
- **省成本**:用完 `terraform destroy` 全部收掉,不會有機器在背景默默扣錢(RDS/EC2 放過夜很傷)。
- **免手動**:不用一個個進 Console 點 VPC、子網、SG、RDS……,一份程式碼重現整套環境。
- **用身分取代金鑰**:EC2 靠 IAM 角色 pull 映像、讀 SSM,全程沒有把任何 Access Key 落在機器上。
- **可版本控制**:基礎設施跟程式碼一起進 git,改了什麼、為什麼這樣開,都有跡可循。
對「開來實驗、用完即拆」這種短期需求來說,Terraform 幾乎是最省心的解法——這也是我這次真正的收穫。
點擊複製文章連結