太棒啦,恭喜大家跟著我一起堅持到了最後一天,在過去 29 天裡,我們從最底層的 Netty 非同步 I/O 引擎切入,經歷了網路延遲混沌工程、Resilience4j 斷路器背壓防線、Docker 容器化、K8s 雙探針與 HPA 自動擴容,再到昨天實作的 GitHub Actions CI/CD 自動化交付。
今天,我們要為這個微服務劃下CI/CD的句點,我們會教大家將打包在 AWS ECR 的 Docker 鏡像,正式部署到 AWS EC2 雲端伺服器上,如此一來就可以將我們自己撰寫的服務給大家呼叫了喔!

EC2 (Amazon Elastic Compute Cloud)簡單來說,就是向 AWS 租用的一台雲端虛擬專用伺服器(Virtual Machine),可以直接在上面執行docker
就像你向租屋網租房間一樣,以前企業要架設伺服器,必須自己花幾十萬元購買實體主機、拉專用光纖網路、開冷氣房維運;而 EC2 讓你只要在 AWS 網頁點幾下,幾秒鐘內就能租到一台擁有獨立 IP、可自由安裝 Linux/Windows 的雲端電腦,且隨開隨用、按小時或秒計費!
在本次鐵人賽中,為了讓大家 0 成本學習,選擇在 t3.micro 上運行單機 Docker 容器。但是在真實企業 Production 環境中,單台 1 GiB RAM 的 EC2 無法抵禦高流量並發請求,應該選擇更高規格(如 t3.medium 以上)或直接對接 AWS EKS 託管 K8s 叢集配合 HPA 自動擴充!
啟動執行個體,為了讓所有人都能免費體驗真實的雲端部署,我們進行以下選型:t3.micro
建立新的金鑰對 輸入名稱,系統會自動幫你下載一個.pem的檔案,後續撰寫githun action檔案時會用到啟動執行個體 建立EC2免費額度:AWS 12 個月免費帳戶每個月提供 750 小時免費執行時間,剛好可以讓一台 EC2 24 小時不間斷開著跑一整個月,完全不花半毛錢!

綁定 IAM Role (Instance Profile)
EC2
AmazonEC2ContainerRegistryReadOnly
EC2 Console 點擊你的虛擬機 Actions ➔ Security ➔ Modify IAM role ➔ 選取剛剛建好的 Role
sudo apt-get update
sudo apt-get install -y docker.io awscli
sudo usermod -aG docker ubuntu # 讓 ubuntu user 能跑 docker,需要重新登入生效
重新登入後就可以透過docker ps指令看到docker被安裝,接著如過後續跑完cicd流程,我們再次使用docker ps也可以清楚看到目前ec2有一個docker container正在被執行

為了讓 GitHub Actions 能自動 SSH 連進 EC2 拉取 ECR 最新鏡像並滾動重啟,回到 GitHub repo的 Settings ➔ Secrets and variables ➔ Actions 中,除了昨天已經有的連接ecr的ak,sk環境變數外,再額外補上以下環境變數:
EC2_SSH_KEY:將上個階段建立ec2時下載的 .pem 用文字編輯器打開,複製全部內容(包含 -----BEGIN RSA PRIVATE KEY----- 到 -----END RSA PRIVATE KEY-----)
S3_AK:Day6 建立去連線 S3 的 IAM Access Key
S3_SK:Day6 建立去連線 S3 的 IAM Secret Key
進入程式碼 .github/workflows/deploy.yml,我們昨天已經完成github push後會自動mvn test, docker build, docker push到ecr上,今天在最下面新增第七階段,從ec2上將既有的image拉下來並且執行,要填上自己的Public IPv4
- name: 7. Automated Remote Deployment to EC2 via SSH
uses: appleboy/ssh-action@v1.0.0
with:
host: {your_EC2_ip} #EC2 的公網 IP (Public IPv4 地址)
username: ubuntu #若選 Amazon Linux ➔ 填寫 `ec2-user`
key: ${{ secrets.EC2_SSH_KEY }}
script: |
# 1. 依靠 EC2 綁定的 IAM Role 自動登入 ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}
# 2. 拉取 AWS ECR 最新的 Docker Image
docker pull ${{ steps.login-ecr.outputs.registry }}/s3-app-service:latest
# 3. 滾動重啟容器並注入 S3 環境變數
docker stop s3-app || true
docker rm s3-app || true
docker run -d -p 8080:8080 --name s3-app --restart always -e AWS_ACCESS_KEY_ID="${{ secrets.S3_AK }}" -e AWS_SECRET_ACCESS_KEY="${{ secrets.S3_SK }}" ${{ steps.login-ecr.outputs.registry }}/s3-app-service:latest
部署成功後可能還會發現一個問題,明明github action亮綠燈,但是為什麼我們實測http請求的時候就還是連不上呢?答案很簡單因為ec2出於安全問題,將有對外開放的port限定在22而已,而我們前面對外開的port是8080 (docker run -d -p 8080:8080) 當然也可以直接將docker輸出的port改成22,但我今天要額外讓大家來試試看新增安全群組的部分
儲存規則後再次測試從自己的Public IPv4來測試就成功啦!我們自己開發的服務就可以正式上線讓大家使用啦!

開立 EC2 後,AWS 提供基本的 CloudWatch 免費監控,只有當你手動開啟1 分鐘一次的高頻 Detailed Monitoring、日誌收集超過 5GB、或是建立了幾十個告警時才會產生微量費用。預設狀態下完全 0 元

既然 AWS 已經提供了免費的 CloudWatch,為什麼我們在 Day 17~18 還要自己用 Docker Compose 架設 Prometheus 與 Grafana 呢?因為這兩者的觀察視角有所不同:
/actuator/prometheus 端點,深入容器內部抓取微服務指標。通常會使用CloudWatch 負責監控硬體與雲端服務健康,並在 EC2 當機時發送 Email/Slack 告警,然後 Prometheus + Grafana 負責即時調校 API 效能,抓出 Memory Leak 與連線池瓶頸。
這 30 天我們從 localhost 一步步成長,最終獲得的數據對照:
| 評測指標 / 演進階段 | Day 4 同步 Apache | Day 7 非同步 Netty | Day 18 防禦型 API | Day 27 K8s HPA 擴充 |
|---|---|---|---|---|
| 極限 QPS 吞吐量 | ~6.5(線程飢餓) | ~6.6 QPS | ~10.5 QPS (背壓保護) | 16.2 QPS(突破單機天花板) |
| P99 回應延遲 | 4,477 ms (佇列阻塞) | 5.953 ms | 921 ms | 3906 ms (流量分流) |
| 高壓錯誤率 (Error Rate) | 32.4% (全站崩潰) | 44.4% (Bufferbloat) | 0.00% | 0.00% (0 封包遺失) |
| 高可用自癒力 (RTO) | 無 (Tomcat 執行緒卡死) | 無(瞬間拋任務卡頻寬) | Semaphore(50) + Double-Check | HPA (3 ➔ 8 Pods)+雙探針 |
| 部署架構 | 本地 localhost | 本地 Docker | Docker Compose | K8S |
老實說這三十天也比我想像中的還要煎熬和困難,和我一開始預估要花費的時間和內容也有點差距,不過過程中也讓我慢慢學習了更多概念,這個AI時代或許開發模式漸漸轉變,我們不在需要一行一行手刻程式碼了,但是整體架構的敏銳度,對於專案有bug時要從哪裡切入找到可能的原因和修正,以及每一段 API、一個 Dockerfile、一個 K8s Deployment,背後都隱含著無數效能調校與資安考量
身為後端工程師,最寶貴的資產從來不是記住了多少 API 語法,而是具備剖析系統瓶頸的底層思維,以及用數據說服團隊的架構決策力。
感謝這 30 天陪同這套系統一起演進的自己與每一位讀者,我們的架構師修煉之旅,正式圓滿成功!