雲端原生設計模式簡介
雲端原生不是把既有應用程式搬到 Google Cloud 的動作,而是一種設計紀律:讓應用程式在分散式、具備彈性、多租戶的基礎設施上運行良好,其中每個實例都是廉價、短暫、可被替換的。對 Google Cloud Professional Cloud Developer (PCD) 考試而言,這代表一份明確的檢查清單:套用 twelve-factor 方法論、把所有可變狀態外部化、在 Cloud Run 與 GKE 上正確處理 SIGTERM、產生 Cloud Logging 能解析的結構化日誌、暴露給 Kubernetes 與 Cloud Run 探針使用的健康檢查端點、用指數退避加 jitter 重試暫時性失敗、用 Anthos Service Mesh 的 Circuit Breaker 隔離失敗的依賴,以及透過 blue-green 或 canary 部署來推送變更,而不是直接重啟。
白話文解釋
無狀態服務就像旅館房間
一間精品旅館有 50 間相同的房間。你 check-in 時,櫃台給你任何一間打掃乾淨的房間。你的行李、護照、牙刷都跟在你身上,而不是留在房間裡。如果 207 號房水管爆裂,旅館把你換到 312,你幾乎不會察覺。一個無狀態的 Cloud Run 服務也是如此運作。每個容器實例都是一間打掃乾淨的房間。使用者的 session、購物車、上傳檔案存在 Memorystore、Firestore 或 Cloud Storage,從來不存在容器的本地磁碟。當 Cloud Run 為了縮容而結束實例 207,請求會自動轉到實例 312,使用者不會遺失任何工作。
優雅關機就像咖啡師打烊
當咖啡店晚上 9 點打烊,咖啡師不會在 8 點 59 分把濃縮咖啡機切斷電源。她會停止接受新訂單、完成已經在櫃台上的飲品、清洗蒸氣管、然後鎖門。Cloud Run 與 GKE 在縮容或滾動更新時的行為一模一樣。它們對你的容器送出 SIGTERM,這是禮貌的「我們快關門了」警告。你的程式碼在 Cloud Run 上預設有大約 10 秒、在 GKE 上有 terminationGracePeriodSeconds(預設 30 秒)的時間,可以排空進行中的請求、刷新日誌、關閉資料庫連線,然後 SIGKILL 才會降臨。
Circuit Breaker 就像家用電箱的保險絲
當吹風機短路,保險絲跳脫,切斷那一個插座的電源。家裡其他地方的燈還是亮的。如果沒有保險絲,整個電箱會燒掉。Anthos Service Mesh 裡的 Circuit Breaker 就是軟體版本的保險絲。如果 recommendations 服務開始對 50% 的請求回傳 503,服務網格會暫停送流量過去 30 秒,改用快取的 fallback。checkout 服務還活著,客戶仍然能付款,你的 on-call 工程師可以從容修復 recommendations 而不會引發全站斷線。沒有 Circuit Breaker 的話,每個等待 recommendations 的服務都會把執行緒卡住、等記憶體被排隊的 context 塞滿,整個機群一起崩潰。
Twelve-Factor App 方法論在 Google Cloud 的對應
Twelve-factor 方法論最早由 Heroku 提煉出來,是雲端原生服務的標準合約,Google 也有自己針對 GCP 的架構中心版本。PCD 考試考的不是死背十二條規則,而是每一個 factor 對應到哪一個 GCP 服務。
Factor I、II、III:Codebase、Dependencies、Config
每個微服務一個 Git repository,乾淨地對應到一個 Cloud Build 觸發器與一個 Artifact Registry repo。依賴用 requirements.txt、package-lock.json 或 go.mod 鎖版本,在建置時烤進容器映像,執行時不再 pip install。設定要外部化:非機敏設定用環境變數(Cloud Run 透過 --set-env-vars、GKE 用 ConfigMap),認證憑據用 Secret Manager 並指定版本(projects/PROJECT/secrets/db-password/versions/3),絕對不要烤進映像。理由很重要:一個從 dev 推到 prod 的映像,跨環境時必須是 zero-byte 變化。
Factor IV、V、VI:Backing Services、Build/Release/Run、Processes
像 Cloud SQL、Pub/Sub、Memorystore 這些後端服務都是「掛載資源」,只透過 URL 或連線字串取得。換掉連線字串就能換掉供應商。Build (Cloud Build)、Release (一個標籤過的映像加上設定)、Run (Cloud Run 或 GKE) 嚴格分離,這就是為什麼 Cloud Build 產生一個不可變的 digest 例如 gcr.io/PROJECT/api@sha256:abc123,release 階段引用這個 digest,而不是可變的 tag 例如 latest。Processes 必須是無狀態、不共享記憶體 — 詳見下一節。
Factor VII 到 XII:Port binding、Concurrency、Disposability、Dev/prod parity、Logs、Admin processes
你的容器透過 Cloud Run 注入的 PORT 環境變數(預設 8080)暴露單一 HTTP port。Concurrency 透過增開實例水平擴充,而不是在一個實例裡多加執行緒。Disposability 代表在 5 秒內啟動(否則 Cloud Run 冷啟動會 timeout),收到 SIGTERM 後立即關機。Dev/prod parity 代表同一個映像本地用 gcloud run services proxy 跑得起來,正式環境也跑得起來。Logs 是 stdout/stderr 的串流,Cloud Logging 會自動收。Admin processes(資料庫遷移)用一次性的 Cloud Run Jobs 或 Kubernetes Jobs 跑,不是用臨時 SSH session。
在 Cloud Run 上 PORT 環境變數是由平台注入的 — 你的程式碼必須讀取它,不能寫死 8080。Cloud Run Jobs 用 CLOUD_RUN_TASK_INDEX 與 CLOUD_RUN_TASK_COUNT 把工作分散到多個平行的 task。沒有讀取這些環境變數,是把舊系統移植到 Cloud Run 時最常見的失敗原因。
Reference: https://cloud.google.com/run/docs/container-contract
無狀態服務設計與外部化狀態
無狀態性是讓所有其他特性(自動擴充、blue-green、自我修復)成為可能的基石。規則是:給予相同輸入,無論之前處理過多少請求,實例必須產生相同的輸出。具體來說,這代表沒有使用者依賴的記憶體內快取、沒有重啟後還會留著的本地磁碟上傳、沒有把使用者綁定到單一實例的 sticky session。
外部化 Session 狀態
使用者的 session 移到 Memorystore for Redis 或 Firestore。Memorystore 是低延遲選項(次毫秒讀取,5 GB 到 300 GB 分層),Firestore 則適合你同時想要每位使用者文件儲存的場景。HTTP cookie 只放一個不透明的 session ID;伺服器在每個請求都去外部儲存取得 session 文件。這個模式讓 Cloud Run 在閃購活動時可以從 0 擴充到 1000 個實例,一個購物車也不會掉。
外部化檔案上傳
使用者上傳的圖片、PDF、匯出檔都放 Cloud Storage。容器寫到暫存目錄,用 signed URL 模式上傳,然後丟棄。Cloud Run 上千萬不要依賴 /tmp 跨請求留存 — /tmp 是計入容器記憶體上限的 in-memory tmpfs,實例回收時就會消失。
外部化背景工作
長時間任務(PDF 生成、影片轉檔)必須移出請求執行緒,搬到 Pub/Sub 或 Cloud Tasks 上。HTTP handler 發出一則訊息,立刻回傳 202。另一個 Cloud Run 服務或 Cloud Run Job 訂閱、處理、把結果寫到持久儲存。Cloud Run 請求 timeout 上限是 60 分鐘(2024 年從 15 分鐘調高),但任何超過幾秒鐘的工作都應該走非同步。
對於黏著性的工作負載例如 WebSocket 或 server-sent events,Cloud Run 有 per-instance 的 session affinity 設定 (--session-affinity),可以透過 GCP_IAP_UID cookie 把客戶端路由到同一個實例上長達 30 天。請謹慎使用 — 它弱化了無狀態保證,也讓 blue-green 切換更複雜。優先把狀態推到 Memorystore,讓服務維持真正的無狀態。
Reference: https://cloud.google.com/run/docs/configuring/session-affinity
用 Secret Manager 與 ConfigMap 做設定外部化
雲端原生的規則是設定屬於環境,不屬於程式碼。GCP 上有兩個主要服務。
Secret Manager 管理憑據
Secret Manager 儲存 TLS key、資料庫密碼、第三方 API token、OAuth client secret,內建版本管理 (versions/1、versions/2、…、versions/latest)。你在 Cloud Run 用 --set-secrets=DB_PASS=db-password:3 引用特定版本,所以 rollback 是一個設定變更,不是重新部署。IAM 授權針對單一 secret,角色是 roles/secretmanager.secretAccessor;Cloud Run 服務帳號就是那個在容器啟動時拉取值的身分。
環境變數與 Kubernetes ConfigMap 管理非機敏設定
普通設定(feature flag、log level、region 名稱)放在 Cloud Run 環境變數,或在 GKE 上以 env 或檔案掛載 ConfigMap。檔案掛載的優勢:hot reload。把 application.yaml 從 ConfigMap 掛載進來,用 inotify 監聽 inode,服務不重啟也能讀到新值。代價是:你必須自己寫那段 reload 邏輯;單純的環境變數需要 pod 重啟才會變更。
Runtime 設定模式
對於動態開關(kill switch、A/B 實驗 flag)Firestore 是事實標準。config/feature_flags 底下的文件在每個請求都會讀,搭配 60 秒的客戶端快取。更新在快取 TTL 內傳播,並且 Firestore 的逐文件版本歷史會自動留下審計軌跡。
跨部署環境會變動的設定(staging 對 production、客戶 A 對客戶 B)儲存在環境中,從不放在程式碼基底裡。在 Google Cloud 上,這代表環境變數 + Secret Manager + ConfigMap,絕對不要 commit config.production.yaml 到 git。
Reference: https://cloud.google.com/architecture/twelve-factor-app-development-on-gcp
優雅關機與 SIGTERM 處理
當 Cloud Run 縮容、GKE 滾動更新一個 Deployment、或一個節點被排空進行維護,平台會對你容器的 PID 1 送出 SIGTERM。你的程式碼必須捕捉它、停止接受新工作、排空進行中的請求、刷新緩衝中的 telemetry、關閉連線池、然後乾淨地離開。如果沒有,平台會在 grace period 之後送出 SIGKILL,任何進行中的請求都會被丟棄並回傳 5xx。
Cloud Run 的時間限制
Cloud Run 送出 SIGTERM 後預設等 10 秒才送 SIGKILL。--container-command-timeout-seconds 設定無法延長 grace period;你必須把 shutdown 控制在 10 秒內,不然平台會強制終結你。實務模式:
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGTERM)
go func() {
<-sig
server.Shutdown(context.Background()) // 停止 accept,排空進行中的請求
db.Close()
logger.Flush()
}()
GKE 的時間限制
一個 pod 在 SIGTERM 與 SIGKILL 之間有 terminationGracePeriodSeconds(預設 30 秒,可依需求調整)的時間。kubelet 同時把 pod 從 Service endpoints 中移除,但 kube-proxy 的 iptables 規則更新需要幾秒鐘傳播,所以 SIGTERM 之後仍可能收到請求。緩解方法:加一個 preStop hook 先 sleep 5–10 秒再讓應用程式開始 shutdown,給 iptables 收斂時間。
常見的 bug
一個 library 開了一個 goroutine 每 5 秒 flush 一次日誌,但會卡住 SIGTERM 等下次 tick。一個資料庫 driver 持有 30 秒 idle-timeout 連線,拒絕關閉。一個 worker 從 Pub/Sub 拉了一則訊息還沒處理完就 ack — 收到 SIGTERM 時工作消失了,因為訊息已經被 ack 掉。最後一個的修法是 modAck 模式:處理中持續延長 ack 截止時間,成功後才 ack。
Health Check:Liveness、Readiness、Startup Probe
Probe 是平台決定你的容器是否健康到可以收流量、是否病到需要重啟、或是否仍在熱機的依據。設錯了,你不是殺掉正常運作的 pod,就是把流量送進半初始化的 pod。
Liveness Probe
「這個 process 還活著到值得保留嗎,還是該殺掉?」回傳 200 代表 event loop 還有反應。不應該檢查下游依賴 — 如果 Cloud SQL 慢了,殺掉 API server pod 只會讓災難更大。一個簡單的 /healthz 回傳 {"status":"ok"} 就是正確做法。
Readiness Probe
「這個 pod 現在能不能處理請求?」應該檢查關鍵依賴是否可達 — 資料庫連線池就緒、設定載入完成、熱機快取已預熱。Readiness 失敗時 pod 會被從 Service endpoints 移除但不會重啟。端點慣例是 /ready 或 /readyz。
Startup Probe
「應用程式啟動完了嗎?」只跑一次,在啟動完成前阻擋 liveness 與 readiness 開始執行。對啟動很慢的 JVM 應用、或載入 2 GB ML 模型的服務至關重要。沒有 startup probe 的話,liveness probe 可能在 JVM 90 秒熱機期間失敗,把 pod 殺進重啟迴圈。
Cloud Run 的 Health Check
Cloud Run 會自動偵測健康狀態:如果你的容器對 PORT 上的 TCP 連線有回應,就被認定為健康。沒有 liveness/readiness 區分;平台會自行推斷。要更細的控制,Cloud Run for Anthos 與 GKE 都支援完整的 Kubernetes probe 語義。
Liveness 失敗 = 重啟 pod。Readiness 失敗 = 從負載平衡器移除但繼續執行。Startup 失敗 = 重啟,但只在 failureThreshold * periodSeconds 累積失敗後才執行。考試常考的情境:當一個慢啟動的 ML 模型一直被殺,答案是用 startup probe,而不是調高 liveness timeout。
Reference: https://kubernetes.io/docs/concepts/configuration/liveness-readiness-startup-probes/
結構化日誌與 Correlation ID
純文字日誌在 Cloud Logging 上會讓你付出代價 — 它們不能過濾、不能 alert、也不能跟 trace 做 join。結構化日誌(JSON 寫到 stdout)才是雲端原生的預設。
JSON 合約
GCP 上的 Cloud Logging 認得一個特定的 JSON 信封,寫到 stdout 就會被解析:
{
"severity": "ERROR",
"message": "checkout failed",
"logging.googleapis.com/trace": "projects/my-proj/traces/abc123",
"logging.googleapis.com/spanId": "def456",
"user_id": "u-987",
"order_id": "o-654"
}
severity 欄位對應到 Cloud Logging 嚴重等級 (DEBUG、INFO、WARNING、ERROR、CRITICAL)。logging.googleapis.com/trace 欄位會把這筆 log 自動連結到 Cloud Trace 裡的 trace — 點 log 就能開啟對應的 trace timeline。
Correlation ID
每個進入服務的請求都應該帶 X-Correlation-Id 或 X-Request-Id header。如果沒有,就產生一個 UUID。把它傳到所有下游呼叫(Pub/Sub 訊息屬性、gRPC metadata、BigQuery 查詢 label)。每行 log 都要帶。當 SRE 收到客服票上寫「14:32 的 abc123 請求失敗」,他們在每個服務搜尋同一個 ID 就能重建整段旅程。
避免日誌洪水
Cloud Logging 在每月 50 GB 免費額度之後收費 $0.50 / GB。一個 1000 RPS 的端點如果用 console.log(req.body) 印出 2 KB body,一天就要花約 $86。高吞吐的雜訊用採樣 debug log (if Math.random() < 0.001),INFO/ERROR 留給真正具營運意義的事件。
冪等性與指數退避重試
網路會失敗。Pod 會被驅逐。API 在負載大時會回 503。雲端原生程式碼預期這些事件並且會重試 — 但只重試那些可以安全重試的操作,也就是「冪等」(idempotent) 的操作。
冪等性金鑰 (Idempotency Key)
一個會對客戶扣款的 POST /charge 必須接受 Idempotency-Key header(客戶端產生的任何 UUID)。伺服器把 (key, response) 配對存進 Firestore 並設 24 小時 TTL,遇到重複請求直接回快取的 response。Stripe、PayPal、Google Cloud Tasks API 都用這個模式;考試與正式環境都要熟練。
指數退避與 Jitter
Google API client library 對暫時性錯誤 (429、503、502、UNAVAILABLE) 用指數時程重試:1 秒、2 秒、4 秒、8 秒、16 秒,最高 60 秒,最多 6 次。加上 jitter(每次延遲隨機 ±25%)非常關鍵,可以防止 thundering herd — 沒有 jitter,每個客戶端會在短暫斷線後同一時間重試,再次把恢復中的服務打掛。
Pub/Sub 重新投遞
Pub/Sub 在 ack 截止時間(預設 10 秒,最高 600 秒,或用 modifyAckDeadline 延長)內如果沒收到 ack 就會重新投遞訊息。設定 dead-letter topic 把例如失敗 5 次以上的訊息送到另一個 topic,免得它無止盡循環,讓 on-call 工程師可以撿出來檢查。
把每個錯誤都當成可重試的。一個 400 Bad Request 不是暫時性錯誤 — 重試只會浪費配額與費用。Google API library 只在明確列出的暫時性代碼上重試 (HTTP 408、429、500、502、503、504;gRPC UNAVAILABLE、RESOURCE_EXHAUSTED、DEADLINE_EXCEEDED)。如果你自己寫重試 wrapper,請複製這份清單 — 不要 catch Exception 基底類別然後無限重試。
Reference: https://cloud.google.com/storage/docs/retry-strategy
用 Anthos Service Mesh 做 Circuit Breaker
當下游依賴開始緩慢失敗(從 200 毫秒回應變成 30 秒),重試會讓情況更糟。in-flight 請求的池子變大、執行緒卡住、記憶體被排隊的 context 塞滿,呼叫端的服務崩潰。解法是用 Circuit Breaker 在失敗率超過門檻時停止送流量。
DestinationRule outlierDetection
Anthos Service Mesh(底層是 Istio)透過 DestinationRule 設定 Circuit Breaker。outlierDetection 區塊會把回傳太多 5xx 的 endpoint 從負載平衡池中踢出:
trafficPolicy:
outlierDetection:
consecutive5xxErrors: 5
interval: 30s
baseEjectionTime: 30s
maxEjectionPercent: 50
一個 pod 回傳連續 5 次 5xx 就被踢出 30 秒。重新加回後如果再次失敗,踢出時間加倍。最多 50% 的 pod 可同時被踢出,避免服務被完全餓死。
Connection Pool 限制
Circuit Breaker 的另一半是 connectionPool 限制:最大連線數、最大 pending HTTP 請求、單次呼叫最大重試次數。達到上限時,額外的呼叫立刻 fail-fast 回 503,而不是排隊等待。呼叫端可以立即提供快取 fallback 或降級回應。
Fallback 策略
模式:先打 live 服務,503 時改從 Memorystore 快取取得,快取 miss 時改提供靜態 fallback(一份通用的推薦清單、空結果集、一個「我們目前流量過大」頁面)。使用者體驗優雅降級,而不是丟出白屏。
分散式追蹤與 Context 傳遞
一次使用者點擊可能會經過 API gateway、auth 服務、orders 服務、inventory 服務、加上三個 Pub/Sub topic。沒有 trace ID,要除錯「checkout 很慢」全憑猜測。分散式追蹤在邊緣指派一個 trace ID,並把它沿著每一跳傳播。
W3C Trace Context Header
雲端原生標準是 W3C Trace Context 規格:traceparent header 攜帶 version-traceId-spanId-flags。Cloud Trace 與 OpenTelemetry 都使用這個格式;舊的 X-Cloud-Trace-Context 仍然支援,但新程式碼應該使用 traceparent。
OpenTelemetry SDK
加入 OpenTelemetry SDK(Java、Go、Python、Node — 都有官方支援)與一個指向 cloudtrace.googleapis.com 的 exporter。SDK 會自動 instrument 大部分 HTTP 與 gRPC client,因此 trace 不用改任何應用程式碼就會出現。對 Pub/Sub,要手動把 traceparent 注入訊息屬性,讓訂閱端的 span 能正確接續。
取樣與成本
100% trace 取樣率在 10 K RPS 的服務上會產生每天 8.64 億個 span。Cloud Trace 在免費額度之後是每百萬個 span $0.20,等於每天每個服務 $172。預設用 10% 機率取樣,錯誤 trace 透過 (x-cloud-trace-context flag bit) 強制 100%,省下的預算花在更細的儀器化,而不是浪費在量。
Trace context 必須在每一跳都被傳遞,包括非同步的那些。一個 Pub/Sub publisher 如果沒把 traceparent 注入訊息屬性,就會造成 trace 鏈斷裂:trace 在 publish 結束,subscriber 從零開始一個新的 disconnected trace。在 Cloud Trace UI 上就是一棵沒用的半樹。請務必把 context 注入 Pub/Sub 的 attributes、Cloud Tasks 的 headers、以及任何自製的訊息傳遞層。
Reference: https://cloud.google.com/trace/docs/setup
訊息驅動的非同步架構
同步呼叫鏈很脆弱:鏈上任何一個服務掛了,整個請求就掛。訊息驅動的非同步把 producer 與 consumer 解耦,讓兩端可以獨立擴充、失敗、恢復。
Pub/Sub 做 Fan-out
一次 publish 的 user.signed_up 事件會送到 N 個訂閱:welcome-email、analytics-warehouse、fraud-screening、crm-sync。每個訂閱獨立 — analytics-warehouse 掛了,email 還是會寄。明天加一個新訂閱者,你不用動 producer 半行程式。
Cloud Tasks 做延遲與限速工作
Cloud Tasks 跟 Pub/Sub 不同之處在於它是一個有顯式 per-task 排程的佇列。你 enqueue 「24 小時後寄提醒信給 user X」,Cloud Tasks 在那個時間點 exactly-once 投遞。限速(每秒 50 dispatch、最多 100 個並發)是佇列設定,不是應用程式碼。
Eventarc 做觸發器膠水
Eventarc 把 Cloud Storage 物件寫入、Cloud Audit Logs、Firestore 文件更新轉成 Pub/Sub 訊息或直接 Cloud Run 呼叫。它是「當 X 發生時就跑 Y」這一層,把輪詢程式碼從你的服務裡清掉。
Blue-Green 與 Canary 部署
In-place 重啟(刪掉舊的、開新的)會造成停機,也沒有安全的 rollback 路徑。雲端原生的部署是漸進式的。
Blue-Green
兩個完整環境。Blue 是 production。Green 是新版,完整部署但收到零流量。冒煙測試通過後,用一個設定變更把負載平衡器切到 green。Rollback = 切回去。Cloud Run 上每次部署都產生一個新 revision;gcloud run services update-traffic --to-revisions=NEW=100 就是切換指令。代價:切換期間 2 倍的基礎設施。
Canary
新版獲得一小片流量 (5%、25%、50%、100%) 的可控漸進推送。Cloud Run 原生支援這個模式,透過加權的 revision:--to-revisions=v2=10,v1=90 送 10% 給 v2。Anthos Service Mesh 在 GKE 上透過 VirtualService 的 weight 規則用同樣的百分比處理 canary。把 canary 配上 SLO-based 自動晉升:如果錯誤率在 10% 流量下 10 分鐘維持在 0.1% 以下就晉升到 50%,否則自動 rollback。
GKE 上的 Rolling Update
GKE Deployment 的預設策略。maxSurge: 25% 與 maxUnavailable: 25% 代表 Kubernetes 會超量 25% 開新 pod,等新 pod ready 就殺掉舊的。比 blue-green 便宜但 rollback 比較慢(你要再 rollout 一次回去),而且也不能在完整切換前對整個新機群跑冒煙測試。
Feature Flag
獨立於部署策略之外,feature flag 把發布與部署解耦。把程式碼以「dark」狀態部署,在 Firestore 翻 flag 把功能開給 1% 使用者,觀察,再擴大。一個 bug 只需要翻 flag 就能緩解 — 不用 rollback。
常見問題 (FAQ)
Q1:Cloud Run 最嚴格執行的 12-factor 原則是哪一個?
Cloud Run 在平台層級強制執行無狀態 process (factor VI) 與 port binding (factor VII)。你的容器必須暴露 PORT,任何寫到本地磁碟的狀態在實例重用或回收時都會被銷毀。Disposability (factor IX) 也透過 10 秒的 SIGTERM 期限被強制。其他 factor(設定、log、build/release/run)是慣例,不是強制。
Q2:Session 狀態何時用 Memorystore、何時用 Firestore?
需要次毫秒讀取、簡單 key/value 存取、TTL 過期時用 Memorystore for Redis — 典型的 HTTP session cache 與 rate-limit 計數器。Session 是更豐富的文件(購物車內容、草稿表單)、需要跨 session 查詢、或需要開箱即用的多區域複製時用 Firestore。Memorystore 只在單一區域。
Q3:我的請求要跑 30 秒,怎麼在 Cloud Run 上正確處理 SIGTERM?
Cloud Run 在強制 kill 前送 SIGTERM 10 秒。如果單一請求要 30 秒,你需要 (1) 收到 SIGTERM 立刻停止接受新請求、(2) 等進行中的請求完成 — Cloud Run 如果還有 active 連線在 drain 會延長那 10 秒,最長到請求 timeout、(3) 回傳前 flush log、關閉連線池。對真正的長工作負載,搬到 Cloud Run Jobs 或 Pub/Sub 驅動的 worker,把 runtime 與 HTTP 週期解耦。
Q4:Liveness Probe 與 Readiness Probe 有什麼差別?
Liveness probe 失敗會讓 Kubernetes 重啟 pod。只用在「process 卡死必須讓它死」的場景 — event loop 鎖死、OOM 但沒崩潰。Readiness probe 失敗會讓 Kubernetes 把 pod 從 Service endpoints 移除但不重啟。用在「這個 pod 現在不能服務」的場景 — 熱機快取中、DB 連線斷、依賴降級。搞混兩者要嘛造成重啟風暴,要嘛把流量送進半壞的 pod。
Q5:沒有 Anthos Service Mesh 要怎麼實作 Circuit Breaker?
如果不能採用 service mesh,就在程式碼裡用 library 實作這個模式:Java 用 resilience4j、Go 用 gobreaker、Node 用 opossum、Python 用 circuitbreaker。設定失敗門檻(例如 10 次呼叫中錯誤 50%)、開啟期間(例如 30 秒)、half-open 試探(一個試呼叫測試下游是否恢復)。跟 Anthos Service Mesh 相比的取捨:程式碼層的 Circuit Breaker 每個語言實作不同,多語言機群要保持一致很難;service mesh 從資料平面強制執行統一行為。
Q6:為什麼指數退避加 jitter 比固定重試間隔好?
固定間隔會造成 thundering herd:下游恢復時,所有用同樣節奏重試的客戶端會同時打過去再次把它弄掛。指數退避(1 秒、2 秒、4 秒、8 秒)把重試分散到逐漸擴大的時間窗口。Jitter(隨機 ±25%)進一步去同步客戶端,即使有相同的退避時程也不會對齊。Google API client library 內建這個機制;自己寫但忘了 jitter 是 PCD 考試常見的陷阱。
Q7:新微服務部署應該用 blue-green 還是 canary?
穩態變更(新功能、設定調整、依賴升級)用 canary,要漸進曝光與 SLO 驅動的自動 rollback。無法部分推送的變更(schema migration、breaking API change、整版切換)用 blue-green,要原子切換與乾淨 rollback 路徑。在 Cloud Run 兩個都是一行指令的操作;選擇取決於風險樣態,不是實作難度。