題干與適用場景
一個 HTTP 服務使用 Deployment 滾動發布。Pod 被刪除後,部分請求仍進入舊實例,長輪詢和背景任務偶爾中斷。請說明 Kubernetes、Service、負載平衡與程序之間的終止時間線,並設計排空、取消、重試與驗證方案。核心是運行中服務的連線與副作用邊界,因此歸入 backend。
面試官考察點
高品質回答會區分 Pod 刪除、EndpointSlice 更新、負載平衡傳播、preStop、SIGTERM 與 SIGKILL,不把「收到 SIGTERM」當成流量已停止。也要涵蓋長連線、佇列任務、冪等重試、節點意外斷電和 grace period 不足。
回答前需要釐清的問題
- 服務是短請求、SSE、WebSocket 還是長輪詢?是否能遷移連線?
- readiness、liveness、startup 探針分別檢查什麼,是否會把停機狀態寫入應用?
- preStop 是 sleep、HTTP hook 還是應用自身排空介面,誰負責最終退出?
- Pod 是否執行 worker,任務租約與重複投遞如何處理?
- 入口負載平衡、Service mesh 與雲端 LB 的傳播延遲是多少?
30 秒回答框架
「停機先把應用標記為 not-ready,停止接收新工作,再等待 Endpoint 與負載平衡傳播。接著應用進入 draining,停止 accept、讓短請求完成、關閉或遷移長連線,並讓 worker 停止領取新任務、完成或釋放租約。preStop 只提供協調窗口,SIGTERM 觸發應用清理,terminationGracePeriodSeconds 必須覆蓋傳播、排空與清理的 p99;逾時後才會 SIGKILL。用發布壓測、連線和任務指標驗證沒有丟請求或重複副作用。」
分步驟深入解答
先畫時間線:控制器刪除 Pod,kubelet 執行終止流程,EndpointSlice 與各級負載平衡逐步移除端點;容器可能同時開始 preStop,隨後收到 SIGTERM。傳播是非同步的,所以應用在收到停機意圖後必須立即拒絕新請求,即使部分流量仍到達舊位址。
應用應維護 ready 與 draining 狀態。停機入口先切為 draining,使 readiness 失敗並停止 accept 新連線或回傳可重試回應。已建立的短請求繼續完成;SSE、WebSocket 與長輪詢要送出結束訊號、遷移游標,或讓客戶端帶恢復令牌重連。
preStop 的 sleep 不能取代應用邏輯。它最多為端點傳播留時間,也會消耗 grace period。更可靠的是呼叫應用 drain endpoint,記錄開始時間與剩餘預算;SIGTERM handler 負責停止新工作、關閉 listener、等待活動請求並釋放連線池。
背景 worker 必須與 HTTP 排空分開設計。停止領取新訊息,正在處理的任務用租約或可見性逾時保護;剩餘預算不足時主動釋放,讓其他 worker 重試。任務寫入與外部副作用要使用冪等鍵,不能因 Pod 被殺就再次扣款或出貨。
terminationGracePeriodSeconds 應按實測 p99 計算:入口傳播延遲、最長允許請求、連線關閉、worker 收尾和日誌刷新都要計入,並留出抖動餘量。期限到達後 kubelet 會強制終止,未完成請求和程序內狀態不能假設會執行 finally。
節點維護與 Pod 刪除不同。Kubernetes 節點關機流程會給 kubelet 和 Pod 預留關機時間,但斷電、核心崩潰或主機強制重啟無法提供優雅窗口。關鍵任務要持久化狀態、跨副本運行,並用探針與佇列監控恢復。
驗證要在滾動發布、手動刪除、節點排空、LB 慢傳播、長連線和 SIGTERM 後逾時等場景執行。檢查 5xx、連線中斷、請求完成率、draining 拒絕數、任務重試、重複副作用、Endpoint 移除延遲與 Pod 強殺數,確保指標能對齊同一發布版本。
高品質示範回答
「我不會把 preStop: sleep 30 當作優雅停機。收到停機訊號後,應用立即把自己標記為 draining,讓 readiness 失敗並停止接收新工作;因為 Endpoint 和 LB 更新有傳播延遲,舊實例仍需拒絕新請求。短請求繼續完成,SSE/WebSocket 送出結束或恢復令牌,worker 停止領取新任務並釋放無法完成的租約。
preStop 只為傳播留窗口,SIGTERM handler 關閉 listener、等待活動請求、釋放連線池,並在統一截止時間前退出。grace period 按實測傳播 p99、最長請求、worker 收尾和日誌刷新計算。驗證時涵蓋滾動發布、節點排空、長連線、程序崩潰和強殺,檢查沒有丟請求、重複副作用或未恢復任務。」
常見錯誤
- 只等待 SIGTERM → 流量傳播仍會把請求送到舊 Pod → 先失敗 readiness 並進入 draining。
- 用固定 sleep 代替排空 → 傳播或請求時長變化會失效 → 以剩餘截止時間驅動應用邏輯。
- 停止 Pod 卻繼續領取佇列 → 任務在強殺時重複 → 先停止領取並釋放租約。
- 把長連線當普通請求 → 客戶端無從恢復 → 發送關閉訊號和恢復游標。
- grace period 只按平均耗時 → p99 請求被 SIGKILL → 用尾延遲和傳播測量設值。
- 假設 finally 一定執行 → 強殺和斷電不會等待清理 → 關鍵狀態持久化。
- 只看 Pod 狀態 → 看不到 LB 慢傳播和連線中斷 → 對齊端點、請求和發布指標。
- 讓 liveness 在 draining 時失敗 → 觸發不必要重啟 → readiness 表示可接流量,liveness 表示程序健康。
追問及應對
追問一:為什麼 readiness 失敗後仍會收到請求?
Endpoint、Service mesh 與雲端負載平衡都有傳播延遲,已有連線也不會自動遷移。因此應用必須在 draining 狀態拒絕新工作並安全結束已有連線。
追問二:preStop sleep 應設多少?
不能憑經驗固定。應測量端點與 LB 傳播的 p99,再與請求、worker 清理預算一起計算,並把 sleep 視為有限協調窗口。
追問三:WebSocket 如何優雅關閉?
停止接受新連線,向現有連線發送關閉碼和重連提示;客戶端使用會話或游標恢復,服務端保存必要狀態,避免從頭執行副作用。
追問四:任務執行一半 Pod 被殺怎麼辦?
任務狀態與租約必須持久化。新 worker 根據可見性逾時重新領取,副作用使用冪等鍵或狀態查詢,不能依賴程序內 finally。
追問五:節點斷電還能優雅停機嗎?
不能保證。節點關機機制只覆蓋可觀測的計畫流程;斷電與核心崩潰要求跨副本、持久化檢查點與可重試任務。
追問六:如何選擇 grace period?
把端點傳播、最長允許請求、長連線收尾、worker 租約釋放、連線關閉和日誌刷新相加,再以實測 p99 與餘量驗證,不能只看平均值。
追問七:如何防止 draining 觸發重啟風暴?
停機時讓 readiness 失敗,保持 liveness 成功;探針職責分離,避免把「暫時不接流量」誤判為程序崩潰。