題幹與適用場景
Kubernetes Controller 透過 Informer 快取讀取物件,再把期望狀態寫回 API Server。集群高負載、watch 延遲或控制器重啟時,快取可能落後於 API Server;此時控制器可能重複寫入、錯誤擴縮,甚至依據過期 Lease 判斷節點失效。請設計一套陳舊偵測與緩解方案,要求只阻塞受影響物件,不拖住整個控制器。
這道題適合平台工程、SRE 與雲原生控制器職位。Kubernetes v1.36 官方文章和 KEP 5647 給出了 AtomicFIFO、LastStoreSyncResourceVersion()、按物件記錄最後寫入版本、跳過並重新入隊,以及 DaemonSet、StatefulSet、ReplicaSet、Job 控制器的落地範圍。本文是基於公開資料的面試設計推導,不聲稱是公司真題。
面試官考察點
面試官關注你能否解釋最終一致快取的邊界,並把「讀到新資料」轉化為可驗證的資源版本條件。高品質回答會涵蓋快取同步、寫後讀、局部佇列、退避、控制器重啟、監控和 feature gate;普通回答只會建議週期性直讀 API Server,忽略負載與一致性成本。
回答前需要釐清的問題
- 哪些決策具有破壞性:刪除 Pod、縮容、切換主節點,還是普通狀態更新?
- 可接受的陳舊窗口是多少,是否能為關鍵操作支付一次 API Server 直讀?
- 需要保證單一物件寫後讀,還是多物件之間的因果關係?
- 控制器重啟、watch 重連和 API Server 故障時,預設保守等待還是允許有限降級?
30 秒回答框架
「我會保留 Informer 快取作為主讀路徑,利用 AtomicFIFO 和最新已見 resourceVersion 判斷快取進度。控制器寫入關鍵物件後,記錄物件鍵到目標 resourceVersion 的映射;當快取尚未追上時,只跳過該物件並按指數退避重新入隊。對刪除、擴縮容等高風險操作增加直讀或熔斷,暴露快取延遲、跳過次數和佇列年齡指標。重啟後不清空保護狀態,先等待完整同步,再恢復處理。」
分步驟深入解答
先定義陳舊。Informer 的本地 Store 由 watch 事件填充,事件可能延遲、亂序或在重啟重建期間暫時不完整。Kubernetes v1.36 的 AtomicFIFO 將批量到達的初始列表事件原子化,避免列表與增量事件交錯造成不一致;LastStoreSyncResourceVersion() 讓客戶端讀取快取已追上的版本。
控制器為每個關鍵寫入保存 objectKey -> resourceVersion。例如 DaemonSet 更新 Pod 後,記錄 API Server 回傳的版本;Pod informer 每次事件都推進「已見版本」。只有當快取已見版本不小於最後寫入版本時,才允許該 DaemonSet 進入下一輪需要新狀態的 reconcile。未追上時只對該 key requeue,沿用指數退避,不能把整個 worker 池鎖住。
寫入與判斷要具備冪等性。API 更新使用 resourceVersion 衝突重試;重複 reconcile 不應產生額外副作用。控制器重啟後,記憶體中的映射遺失,因此啟動階段先等待 informer cache sync,再把需要保護的狀態從物件狀態或佇列事件重建。映射不完整時寧可延遲破壞性操作,也不要假設快取新鮮。
對時間敏感的決策可增加 circuit breaker:先用快取快速判斷,若目標版本未知或快取延遲超過閾值,執行一次受限直讀 API Server;直讀失敗則暫停該物件並記錄原因。直讀不能成為所有 reconcile 的預設路徑,否則 API Server QPS、延遲和故障半徑會被放大。
觀測指標至少包括 informer 當前 resourceVersion、目標寫入版本、兩者差值或滯後時長、因陳舊跳過的 reconcile 次數、佇列等待年齡、直讀成功率和每類控制器的熔斷次數。日誌帶物件鍵、版本和動作,不打印 Secret。告警應區分 API Server 變慢、watch 斷開、單一物件熱點和控制器自身處理慢。
發布採用 feature gate 與灰度。先對一個高並發控制器和小節點池啟用,驗證跳過後最終收斂、重啟恢復和佇列無飢餓;再擴展到其他控制器。回滾時關閉新的一致性路徑,但保留指標和已有物件狀態,避免批量清理映射觸發額外寫入。
高品質示範回答
我會把陳舊處理設計成「快取主路徑、版本門檻、局部重入隊、關鍵動作熔斷」四層。Informer 透過 AtomicFIFO 保證批量初始化不會和增量事件交錯,Store 暴露最新已見 resourceVersion。控制器寫入物件後記錄目標版本,只有快取追上該版本才繼續處理同一物件;否則指數退避重入隊,不阻塞其他物件。
刪除、縮容和 Lease 判斷等高風險動作在快取版本未知或滯後超閾值時做受限直讀,失敗就對該物件熔斷。指標記錄版本滯後、跳過次數、佇列年齡和直讀比例。重啟先完成 cache sync,再恢復映射和佇列。灰度驗證收斂、恢復和 API Server 負載,任何時候都不全域清空保護狀態。
常見錯誤
- 錯誤表現 → 每次 reconcile 都直讀 API Server;失敗原因 → 放大 QPS 與延遲,快取失去意義;修正方法 → 僅在關鍵動作和版本未知時直讀。
- 錯誤表現 → 發現一個物件陳舊就暫停所有 worker;失敗原因 → 單一熱點物件拖住全域;修正方法 → 以物件鍵為粒度跳過並 requeue。
- 錯誤表現 → 只比較時間戳;失敗原因 → 時鐘漂移不能表達 watch 因果順序;修正方法 → 使用 API resourceVersion 與 Store 已見版本。
- 錯誤表現 → 控制器重啟後刪除全部保護狀態;失敗原因 → 重建期間可能做出舊資料決策;修正方法 → 先 cache sync,再有版本控制地恢復。
追問及應對
為什麼資源版本比本地時間戳可靠?
resourceVersion 來自 API Server 的物件變更序列,可用於判斷快取是否看過某次寫入;本地時間戳受時鐘漂移、網路延遲和程序暫停影響,不能證明因果順序。它仍不是跨物件交易序號,所以多物件一致性要額外設計。
如何避免某個物件一直陳舊導致佇列飢餓?
為該物件設定指數退避上限和最大等待告警,同時讓佇列繼續處理其他 key。超過閾值後可轉為低頻探測或一次直讀;記錄連續跳過次數,避免無限快速重試打滿 API Server。
什麼時候應該直接拒絕操作?
涉及刪除、縮容、故障轉移或 Lease 過期判斷時,如果快取版本未知且直讀失敗,應暫停該物件並保留保護狀態。普通狀態回報可以在明確的陳舊窗口內繼續,但必須把降級標記寫入狀態與指標。