MatchRPG 連假共享 Dev VM:EC2 + SSM + ECR + Cloudflare Tunnel 零 SSH 部署方案¶
文檔資訊
- 分類: architecture
- 難度: advanced
- 預估閱讀時間: 12 分鐘
- 標籤:
aws,ec2,ssm,ecr,terraform,cloudflare-tunnel,powershell,docker-compose,shared-dev,windows
摘要¶
為解決連假期間無法維持本地 docker-compose 環境供團隊測試的問題,設計並實作一套臨時共享 Dev VM 方案:以 Terraform 建立 EC2(t3.large),使用 AWS SSM Session Manager 取代 SSH(無需固定 IP 或開放 port),將本地 Docker image 直接 push 到 ECR(確保 binary 完全一致),透過 S3 傳輸設定檔與 Cloudflare Tunnel credentials,在 VM 上重現完全一致的 docker-compose 環境,保留相同的 Cloudflare Tunnel domain(local-*.hanwellspring.com)。
關鍵學習¶
-
Windows 環境下 PowerShell 驅動 bash subprocess 有嚴重路徑問題(反斜線被吃掉),應全程用 PowerShell 避免跨 shell 的 PATH 與路徑問題
-
SSM send-command 的 --parameters 若包含複雜 bash 指令,用 file:// 前綴傳 JSON 檔案比 inline 更可靠
-
PowerShell ConvertTo-Json 對單一字串不會自動序列化成陣列,需明確用 @($Commands) 強制包裝
-
PowerShell 多重賦值 $local, $ecr = @('a','b') 會讓 $ecr 變成單元素陣列,改用 $pair[0]; $pair[1]
-
pg_dump 二進位格式透過 PowerShell > redirect 會被轉成 UTF-16,必須用 cmd /c 'docker exec ... > file' 保持二進位安全
-
ECR token 12 小時過期,docker-compose 中不應設 pull_policy: always,否則 VM 重啟後無法拉 image
-
docker-compose 若引用 Dockerfile 路徑,VM 上必須有對應的 Dockerfile,需一起打包到 tar bundle
技術細節¶
架構設計¶
核心原則:零程式碼差異
- 不重新編譯、不 clone repo,直接 docker tag 本地 image 後 push 到 ECR,確保 binary 完全一致
- 透過 docker-compose.override.yml 將服務的 build 替換為 ECR image 參照
- 保留相同 Cloudflare Tunnel ID 與 credentials,domain 不變
Terraform 資源(shared-dev-vm)¶
# 關鍵設計:outbound-only Security Group,靠 SSM 管理
resource "aws_security_group" "shared_dev" {
description = "Shared dev VM - outbound only (SSM, Docker pull, Cloudflare Tunnel)"
# 無 ingress rule
}
resource "aws_instance" "shared_dev" {
instance_type = "t3.large"
iam_instance_profile = aws_iam_instance_profile.shared_dev.name
# IAM 含:SSM managed policy + ECR pull + S3 read
root_block_device {
volume_size = 30
volume_type = "gp3"
encrypted = true
}
}
PowerShell 腳本設計¶
deploy.ps1 核心流程:
1. 讀取 terraform output 取得 instance ID
2. 等待 SSM agent 上線(輪詢 aws ssm describe-instance-information)
3. 等待 user-data.sh 完成(檢查 .vm-ready sentinel file)
4. ECR login(aws ecr get-login-password | docker login)
5. docker tag matchrpg-twirpserver <ecr>/matchrpg-game-server:shared-dev 然後 push
6. 打包 configs/gamedata/Dockerfiles 成 tar.gz
7. 產生 docker-compose.override.yml 指向 ECR image
8. 產生 setup.sh 並上傳到 S3
9. 透過 SSM 執行 setup.sh(使用 file:// 參數避免 quoting 問題)
10. 清理 S3
SSM 呼叫模式(避免 quoting 地獄):
function Invoke-Remote {
param([string[]]$Commands)
$tmpJson = [System.IO.Path]::GetTempFileName() + '.json'
@{ commands = @($Commands) } | ConvertTo-Json | Set-Content $tmpJson
aws ssm send-command `
--instance-ids $InstanceId `
--document-name AWS-RunShellScript `
--parameters "file://$tmpJson" `
--region $Region | Out-Null
# 輪詢等待完成...
}
pg_dump 二進位安全:
# 錯誤:PowerShell > 會轉成 UTF-16
docker exec postgres pg_dump -Fc matchrpg > matchrpg.dump
# 正確:cmd /c 保持二進位
cmd /c "docker exec postgres pg_dump -Fc matchrpg > $dumpPath"
What Changed¶
從「開發者本地 docker-compose + Cloudflare Tunnel」演進為「AWS EC2 共享 Dev VM」方案。
在 MatchRPG_Infra/terraform/environments/shared-dev-vm/ 新增完整 Terraform 模組,建立 EC2 instance(t3.large, 30GB gp3, Amazon Linux 2023),配備 SSM + ECR + S3 的 IAM 權限,Security Group 只開 outbound(無 inbound port,完全靠 SSM 管理)。
同目錄新增三支 PowerShell 腳本:deploy.ps1(一鍵部署)、sync-db.ps1(資料庫遷移)、teardown.ps1(銷毀環境)。原本的 bash 腳本因 Windows PowerShell 路徑與 PATH 問題全數廢棄改用 PS1。
So What¶
這個方案解決了一個常見的團隊開發痛點:開發者的本地環境是團隊唯一的整合測試環境,但開發者有時無法持續在線(出差、連假)。
方案的核心價值在於**零環境差異**:不重新 build image、不 clone repo、不需要固定 IP、不需要 SSH key,任何有 AWS IAM 權限的人都能透過 SSM 管理 VM。Cloudflare Tunnel 保留相同 domain,團隊成員的設定不需任何修改。
Trade-offs¶
- 成本 vs 便利性:t3.large 持續運行每月約 $60-80(ap-east-1),但設計為臨時用途,連假後
teardown.ps1銷毀 - SSM 延遲 vs SSH 方便性:SSM send-command 有 polling 延遲(數秒到數十秒),但換來零 inbound port、無需管理 SSH key
- ECR tag 汙染 vs 方便:共用同一個 ECR repo,用
shared-devtag 與 dev/prod 區隔,有一定混用風險 - Windows-only 腳本 vs 跨平台:全改 PowerShell 後,Mac/Linux 的團隊成員無法直接執行(但此場景下只有 Windows 機器需要執行)
Try It Fast¶
# 1. 建立 VM(只需一次)
cd terraform/environments/shared-dev-vm
terraform init
terraform apply
# 2. 部署完整環境(push images + 啟動 stack)
.\deploy.ps1
# 3. 同步本地 DB 到 VM
.\sync-db.ps1
# 4. 驗證
curl https://local-api.hanwellspring.com/health
# 5. 連假結束後銷毀
.\teardown.ps1
Recommendation¶
- 將 ECR 的 shared-dev image 使用獨立的 ECR repository 命名(如
matchrpg-game-server-shared-dev),避免與 dev/prod tag 混用造成誤拉 - sync-db.ps1 的 SSM remote command 加入更詳細的 stderr 輸出,方便偵錯 pg_restore 失敗原因
- 考慮在 user-data.sh 中加入 ECR login cron job(每 10 小時執行一次),避免 12 小時 token 過期問題
- teardown.ps1 執行前自動提示確認 ECR shared-dev tag 的清理,避免遺留過時 image