01 · Selected Work · 2025

Cloud-Native 全端應用(GKE)

把一個 Todo List 應用——Vite + React 前端、Node.js + Express 後端、 Prisma + PostgreSQL 資料層——完整走過容器化、Kubernetes 部署、 CI/CD 自動化、可觀測性與三場故障演練的每一步,目標是「Build Once, Run Anywhere」: 同一套 code 與 manifests,本機 kind、GKE 都能任意部署。

Google Kubernetes Engine(Autopilot) 個人專案 2025 — 2026
專案總覽:左邊是 Todo List 應用截圖,右邊列出 Application Stack(Vite+React 前端、Node.js+Express 後端、PostgreSQL 資料庫、Prisma ORM)與 DevOps & Operations(Vitest/Playwright 測試、Docker/Docker Compose 容器、kind/GKE、GitHub Actions CI/CD、Prometheus+Grafana 觀測)

專案總覽

應用本身是一個 Todo List:Vite + React 前端、Node.js + Express 後端、 Prisma 作為 ORM 存取 PostgreSQL。真正的重點不在「做一個 Todo App」, 而在它背後完整的維運能力——測試、容器化、Kubernetes 部署、CI/CD、可觀測性, 以及最後拿真實故障場景驗證這套系統到底禁不禁得起壓。

ViteReactNode.js ExpressPrismaPostgreSQL DockerKubernetesGKE Autopilot GitHub ActionsPrometheusGrafana

測試策略

測試分成好幾層,每一層都對應一個實際可能出錯的地方:Backend API 用 Vitest 覆蓋 GET/POST/PUT/DELETE 的成功與錯誤情境(11 個測試)、 DB Smoke 用 Prisma 的 $queryRaw 驗證資料庫連線本身是活的、Frontend 元件用 Vitest + jsdom 驗證渲染與 mock fetch、 ESLint Gate 擋掉不合規範的程式碼、E2E Flow 用 Playwright 跑過 「開頁 → 新增 → 顯示 → 勾選 → 刪除」完整使用者流程、Docker Build 在 CI 裡驗證 多階段建置可以成功,最後 Readiness Probe/ready) 專門用來檢查資料庫連線,直接對接後面的故障演練。

測試矩陣表格與 CI Gates 結果:test-backend、test-frontend、build-and-push 全部 PASS;程式碼覆蓋率 Branch 100%、Statements 88.37%、Functions 71.42%、Lines 88.37%;Playwright E2E todo.spec.js 1 passed。
CI Gates 全綠、覆蓋率 Branch 100% / Stmts 88.37% / Funcs 71.42% / Lines 88.37%

Docker 容器化

前後端都採用多階段建置(multi-stage build):Builder 階段負責裝依賴、跑 npx prisma generate, Runtime 階段只留執行必要的檔案,並以非 root 的 USER node 執行, 兼顧 image 體積更小、執行更安全;再搭配 layer 快取順序優化,讓每次重建的安裝速度更快。 本機用 Docker Compose 起三個服務(frontend / backend / postgres),彼此在同一個 network 裡用 service name 互相呼叫,資料寫進掛載的 host volume,容器重生資料還在。

Docker 多階段建置示意:Builder 階段安裝依賴與 Prisma generate,Runtime 階段只保留執行檔並用 USER node 執行;右側是 Docker Desktop 的 Port Mapping 與 Compose Network 示意,frontend/backend/postgres 三個服務透過 service name 互連。
多階段 build → image 更小;USER node → 更安全;layer 快取 → 安裝更快

Kubernetes 部署架構

對外只有一個 Ingress,依路徑分流:/ 導到 frontend、/api 導到 backend。前後端都各自是 Deployment(replicas=2),由 Service 用 selector 輪流分流到兩個 Pod 副本。backend 的 Pod 在啟動前會先跑一個 initContainer 執行 prisma migrate deploy, 確保 schema 是最新的才讓主容器起來;環境變數 DATABASE_URL 來自 Secret、PORT 來自 ConfigMap,並掛 /health health probe。資料庫則是 postgres StatefulSet,兩個 backend 副本都連到同一個 postgres:5432, 資料寫進共用的 PVC → PV,Pod 重生資料還在。

CI/CD:Build Once, Run Anywhere

每次 git push, GitHub Actions 平行跑 test-backend(Vitest + Postgres service)與 test-frontend(ESLint + Vitest),兩者都過才會繼續往下走 build-and-push(多階段 Docker build,推到 Artifact Registry, 同時打上 :latest:SHA 兩個 tag),最後 deploy 用 Workload Identity Federation 認證,執行 kubectl set image :SHA 並確認 rollout status 零停機,部署到 GKE Autopilot 完成滾動更新。整個設計的核心是「一套 code + manifests, 本機 / kind / GKE 都能部署」——本機用 kind 驗證過的東西,理論上直接部署到雲端也會是同樣的行為。

CI/CD 流程圖:git push 觸發 GitHub Actions,test-backend 與 test-frontend 平行跑,兩者都過(needs gate)才進 build-and-push 做多階段 Docker build 並推到 Artifact Registry,接著 deploy 用 WIF 認證執行 kubectl set image 與 rollout status 零停機部署,最後上線到 GKE Autopilot。
test-backend / test-frontend → build-and-push → deploy(WIF + rollout,零停機)→ GKE Autopilot

可觀測性(Prometheus + Grafana)

Dashboard 追蹤 CPU 使用率、記憶體使用率、Pod 數量三個核心指標, 用途包括容量規劃、異常偵測、支撐 HPA 的擴縮容決策,以及作為後面故障演練的觀測依據—— 沒有可觀測性,故障演練頂多只能「看起來有修好」,沒辦法真正拿數據證明系統的行為。

Grafana Dashboard:CPU Utilisation、Memory Utilisation、CPU Usage 折線圖、CPU Request/Limit、Memory 折線圖等多張面板,即時顯示各 Pod 的資源使用狀況。
Grafana:CPU / 記憶體使用率、Pod 數量——容量規劃、異常偵測、支撐 HPA、災難演練

三場 Disaster Recovery Drill

部署完成不代表結束。我設計了三場故障演練,分別打向資料層、應用層、負載層, 目的是拿真實的失效場景驗證系統的自我修復能力,而不是只憑感覺相信「Kubernetes 應該會自動處理」。

演練一・資料層:強制停止 postgres pod

把 postgres 的 replica 設成 0,模擬資料庫掛掉。後端沒有直接跟著停機,只是暫時被移出流量, 前端會收到 503 Service Temporarily Unavailable; 把 replica 設回 1,kind 自動把後端拉回 Running。

演練一截圖:postgres pod 從 1/1 Running 變成 backend pod 0/1,前端出現 503 Service Temporarily Unavailable 與 JSON parse 錯誤;右側 Grafana 折線圖顯示 replica 數量從 2 掉到 0 再回升到 2。
postgres 掛掉 → backend 503(不是直接崩潰)→ 資料庫恢復後自動 running

演練二・應用層:強制刪除 backend pod

強制刪除其中一個 backend pod,觀察 Kubernetes 會不會自動補回、服務會不會中斷。 結果證明:在多副本結構下,就算一個 Pod 掛掉,系統依然能正常運作,K8s 也會自動把 Pod 數量補回設定值。

演練二截圖:kubectl get pods -l app=backend -w 顯示被刪除的 Pod 經歷 Pending → Init:0/1 → PodInitializing → Running,最終兩個 backend Pod 都恢復 1/1 Running。
殺掉一個 backend Pod → K8s 自動重建 → 服務全程不中斷

演練三・負載層:流量暴增觀察 CPU 自動擴縮

套用 HPA(min 2 / max 10 / 目標 CPU 50%),灌壓力測試流量讓 CPU 飆升, 觀察 HPA 自動把 backend 從 2 個副本擴到 10 個;流量退去後又自動縮回。

演練三截圖:Grafana CPU Usage 折線圖隨壓力測試流量上升成山形;下方表格顯示 HPA 目標 backend Deployment 的 CPU 使用率從 5% 一路飆到 431%,REPLICAS 欄位隨之從 2 增加到 4、8、10,流量退去後又緩慢降回 10 附近。
HPA min 2 / max 10 / CPU 50% → 尖峰流量下自動擴到 10 個副本,退流量後自動縮回

學到的事:把應用部署到 Kubernetes 只是起點,真正的功課是「它在壓力下會怎麼樣」—— 資料庫斷線後後端會不會優雅降級、單一 Pod 掛掉服務會不會中斷、流量暴增時 HPA 擴容夠不夠快, 這些都要親手打壞一次系統、用 Dashboard 上的數字驗證,才算真正確認過。