Kubernetes Admission 異常模式與復原防線

🌏 Read this article in English

當 Webhook 驗證服務出現異常時,Kubernetes API Server 面臨一個結構性的取捨:是放行請求以維持可用性,還是拒絕請求以保全合規?這並非單純的技術參數設定,而是對風險與業務連續性的明確權衡。

理解 failurePolicy 的行為邊界,以及當 Webhook 失效時集群可能陷入的狀態,是架構設計中不可忽視的一環。特別是像 Gatekeeper 這類策略引擎,其設計初衷是確保合規,但在極端故障情境下,若配置範圍涵蓋了復原所需的關鍵資源,也可能成為阻礙系統自動修復的瓶頸。

Webhook 呼叫的生命週期與異常定義

Admission Webhook 允許開發者在資源提交至 etcd 之前介入請求的驗證與修改過程。這套機制將原本硬編碼在 kube-apiserver 中的邏輯,拆解為可插拔的外部服務。這種架構分散了責任邊界:API Server 負責路由與認證,Webhook 負責業務邏輯,而兩者之間的通訊鏈路則引入了新的變數。

動態 Admission Webhook(如 ValidatingAdmissionWebhook)的執行時機是在資源持久化到 etcd 之前。這意味著如果 Webhook 拒絕請求,該資源根本不會被寫入儲存層;若 Webhook 允許請求,資源才會被持久化。這種「持久化前攔截」的設計是 Kubernetes 安全模型的基礎,但也帶來了單點依賴的風險:當驗證服務不可達時,符合該 Webhook rules、scope、selectors 與 matchConditions 的 admission 請求都可能受阻。

需要釐清的是,failurePolicy 僅處理呼叫錯誤或逾時等異常狀況。如果 Webhook 成功回應並明確回傳拒絕(Deny),系統依然會拒絕請求;Ignore 僅忽略網路中斷、服務崩潰或逾時等異常。在這些情況下,kube-apiserver 依賴於 Webhook Configuration 中定義的 failurePolicy 來處理異常。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: my-validating-webhook
webhooks:
- name: validate.mycompany.com
  failurePolicy: Fail # 或 Ignore
  timeoutSeconds: 3
  sideEffects: None

上述配置展示了關鍵參數。timeoutSeconds 定義了 API Server 等待 Webhook 回應的最大時長,超過此時間即視為失敗。而 failurePolicy 則決定了在這種失敗發生時,原始請求的命運。這兩個參數共同構成了系統在壓力下的行為模型。

Gatekeeper 與集群凍結的邊界條件

Open Policy Agent (OPA) 的 Gatekeeper 是 Kubernetes 生態系統中常用的策略引擎之一。它通過 ValidatingWebhookConfiguration 將 OPA 規則應用於集群資源,確保所有操作符合預定義的政策。需要注意的是,Kubernetes API 的 failurePolicy 預設值為 Fail,但具體部署時的設定取決於管理員配置。

這種設計的初衷是確保合規,但其強制力僅及於 Webhook 可達、請求特徵匹配(rules/selectors),且確實經過 admission 流程的資源操作;這並不涵蓋既有資源與豁免情況。然而,這也意味著 Gatekeeper 本身成為了集群的一個關鍵依賴點。如果 Gatekeeper 的服務端點不可達,符合該 Webhook 匹配條件的 admission 請求都可能受到影響。

根據 GitHub 上 open-policy-agent/gatekeeper 專案的 Issue #733 記錄,存在一種極端情境:當管理員在叢集中啟用了 Anthos Managed Gatekeeper (Policy Controller) 並嘗試建立命名空間時,若驗證 Webhook 的 failurePolicy 設定為 Fail,且該 Webhook 本身無法回應,可能會導致命名空間創建失敗。這並非因為 Gatekeeper 本身有 bug,而是因為在 Fail-Closed 模式下,任何試圖恢復集群狀態的操作(例如重新創建 Namespace 資源)都會受到驗證邏輯的限制。如果驗證邏輯依賴於尚未恢復的基礎設施狀態,系統將陷入僵局。

這種情境揭示了 Fail-Closed 策略在極端故障場景下的潛在脆弱性。對於大多數集群而言,通過合理的配置(例如排除關鍵系統命名空間、設置合理的逾時時間)可以大幅降低此類風險。然而,這並不意味著能忽視這一邊界。架構師需要意識到,當安全性成為絕對優先級時,可用性將面臨挑戰。

Fail-Open:可用性優先的風險轉移

failurePolicy 設定為 Ignore(即 Fail-Open)時,意味著管理員選擇在 Webhook 呼叫異常時,優先維持匹配請求的可用性,並接受該項政策未即時執行的風險。如果 Webhook 因為網路中斷、服務崩潰或逾時而無法回應,kube-apiserver 將忽略該 Webhook 的呼叫異常,並繼續後續 admission 流程

這種模式在開發環境或非關鍵業務系統中較為適用。其核心邏輯是:「如果驗證服務不可用,不要阻塞用戶的操作。」這能有效避免因為單一組件的故障導致匹配該 Webhook 規則與 selector 的寫入操作受阻。對於需要高度持續性的應用來說,這種設計提供了必要的緩衝空間。需要注意的是,讀取操作通常不受此影響。

然而,Fail-Open 並非沒有代價。它實際上將安全風險轉移給了後續的監控與補救機制。當驗證被跳過時,不合規的資源可能會進入集群。例如,一個未打標籤的敏感 Pod 可能成功啟動,或者一個使用了未經審計鏡像的工作負載可能被部署。這些資源不會被立即攔截,而是會以未被即時攔截的配置偏差形式存在於系統中。

這種「沉默的違規」帶來了兩個主要挑戰。首先,它破壞了合規性的即時保證。安全團隊無法在請求階段獲得確切的合規狀態報告。其次,它增加了事後審計的複雜度。管理員仰賴其他監控工具來發現這些被放行的資源,並透過稽核、控制器或人工流程進行清理與補救。這是一種將即時防禦轉為事後補丁的策略。

Fail-Closed:安全性優先的可用性代價

與 Fail-Open 相反,failurePolicy: Fail(即 Fail-Closed)意味著當 Webhook 無法回應時,系統將拒絕請求。這是一種將安全風險最小化,但接受操作中斷成本的策略。在這種模式下,Webhook 成為了集群操作的強依賴項。如果驗證服務宕機,所有觸發該 Webhook 的資源操作都將失敗。

這種模式適用於對安全性或合規性要求極高的生產環境。例如,金融交易系統通常設定所有部署皆需經過特定的安全掃描,以防止任何未經授權的配置變動進入核心區塊;醫療系統通常設定數據存儲需符合隱私法規。在這些場景中,允許不合規資源進入集群的風險,遠高於暫時無法部署新資源的成本。

然而,Fail-Closed 也帶來了顯著的可用性風險。最直接的後果是,當 Webhook 服務出現異常時,符合該 Webhook rules、scope、selectors 與 matchConditions 的請求將陷入停滯。管理員無法創建新的 Pod、更新 Deployment 或執行其他關鍵操作。這種停滯可能持續數分鐘甚至更久,取決於故障恢復的速度。

更深層的風險在於「循環依賴導致的恢復僵局」。這類循環依賴風險在特定配置下可能導致系統進入無法透過常規手段恢復的狀態。例如,如果 Webhook 服務的部署本身也依賴於相同的驗證邏輯(即該 Webhook 需要先由某個資源觸發才能啟動或更新),那麼當該服務出現問題時,管理員可能無法通過常規方式重新部署或修複它,因為任何嘗試都會被 Fail-Closed 策略拒絕。這形成了一個循環依賴:要修復 Webhook,需要創建資源;但要創建資源,Webhook 需正常運作。

為了降低此類風險,高可用架構設計通常會採取以下措施:

  1. 多副本部署 (Multi-Replica):確保 Webhook 服務本身具備冗餘能力。
  2. 分散式負載均衡 (Load Balancer):避免單一 Pod 或節點故障導致的連線中斷。
  3. 隔離關鍵命名空間:在 ValidatingWebhookConfiguration 中使用 namespaceSelector,排除掉如 kube-system 等核心系統組件,確保集群基礎設施在 Webhook 失效時仍能維持基本運作能力。

需要注意的是,namespaceSelector 不等於排除所有叢集範圍資源;同時檢查 rules、selectors 與 matchConditions,以確保關鍵復原路徑確實被豁免。此外,多副本僅解決單點故障,若涉及同節點或同區故障、Service 配置、憑證過期或網路策略限制,仍有賴額外設計以避免端到端的高可用缺口。

失敗模式的可觀測性與緊急復原

無論選擇 Fail-Open 還是 Fail-Closed,可觀測性都是管理 Admission Control 風險的關鍵。當 Webhook 呼叫異常時,kube-apiserver 會在日誌 (log)審計事件 (audit event)Prometheus metric 中記錄相關資訊,但這些資訊可能不夠直觀,難以快速定位異常根因。

常見的監控策略涵蓋以下幾個維度:

  1. Webhook 回應時間:追蹤每次呼叫的延遲,識別潛在的性能瓶頸或網路不穩定性(可透過 metric)。
  2. 失敗率統計:計算 Webhook 無法回應的比例,評估其對集群操作的影響頻率(可透過 log 或 metric)。
  3. 拒絕請求日誌:記錄被 Webhook 拒絕的資源請求,分析違規模式與根本原因(可透過 audit event 或 log)。

在 Fail-Closed 模式下,緊急復原通常涉及手動干預。如果 Webhook 服務完全宕機且無法快速恢復,管理員會透過 kubectl 命令臨時修改 Webhook Configuration,將 failurePolicyFail 改為 Ignore

# 執行前確認正確的 webhook name 與索引位置
kubectl patch validatingwebhookconfiguration <webhook-name> \
  --type='json' \
  -p='[{"op": "replace", "path": "/webhooks/0/failurePolicy", "value": "Ignore"}]'

當 WebhookConfiguration 本身也被故障的 Webhook 攔截時(例如規則配置過於寬泛,攔截了對 ValidatingWebhookConfiguration 資源的 UPDATE/DELETE 操作),上述 kubectl patch 會直接被拒絕,系統將陷入無法透過常規 API 修復的僵局。

在這種極端邊界下,具體的 break-glass 復原順序通常如下:

  1. 利用既有豁免通道:若 Webhook 預先配置了 matchConditionsnamespaceSelector 排除特定管理員群組或系統命名空間,可切換至該身分或在豁免命名空間內操作來刪除/修改 Webhook。
  2. 恢復未被攔截的 Webhook 端點:若無 API 豁免通道,只有在 Webhook 服務的相關底層資源(如 Deployment、Pod/ReplicaSet)不符合故障 Webhook 的匹配條件時,才可經由 API 修復異常的 Pod、節點或網路,恢復既有 Webhook 端點的可達性,再處理被阻斷的請求。
  3. 控制平面重啟(最終邊界):當所有 API 寫入與底層資源操作皆被阻斷,復原邊界將退回基礎設施層。這涉及修改 kube-apiserver 的靜態 Pod 配置(如暫時移除 --enable-admission-plugins 中的 ValidatingAdmissionWebhook),重啟 API Server 以關閉驗證機制,刪除異常的 WebhookConfiguration 後,再將 API Server 恢復原狀。

OneUptime 的分析指出,選擇 Gatekeeper webhook 失敗行為時值得謹慎評估,加固 fail-closed admission,並為控制平面事件保留經過測試的復原路徑,是避免系統鎖定的關鍵。

取捨與決策框架

選擇 Fail-Open 還是 Fail-Closed,並非單純的技術偏好,而是基於業務風險評估的結果。架構師通常會從政策敏感度、復原能力與 Webhook 服務穩定性等維度進行權衡。對於高敏感度的工作負載,優先確保合規性通常指向 Fail-Closed;而若缺乏可靠的復原路徑或 Webhook 穩定性較低,為了避免單點故障導致集群長期停擺,Fail-Open 則成為維持可用性的務實選擇。

此外,在微服務架構下,Admission Control 對系統吞吐量的潛在影響是一個值得評估的維度。當 Webhook 因網路擁塞或邏輯複雜導致延遲增加時,API Server 的併發請求會因為等待回應而積壓,這可能引發連鎖反應,最終導致整個集群的控制平面(Control Plane)響應變慢,甚至觸發大規模的 API 超時。因此,在設計 Webhook 時,除了考量 failurePolicy,設定合理的 timeoutSeconds 與優化 Webhook 的執行效能同樣是維持系統穩定性的關鍵。

實際決策往往需要權衡多個維度,例如政策敏感度、匹配範圍、補救能力與復原路徑。一個生產環境可能同時包含「高穩定性但低敏感度」與「低穩定性但高敏感度」的工作負載。在這種情況下,架構師可以透過 namespaceSelectorobjectSelector 將不同屬性的資源路由至不同的 Webhook 配置,從而實現細粒度的風險控制。高敏感度政策若僅依賴 objectSelector 會有繞過風險(因標籤可由請求者控制),搭配 namespaceSelector 與規則範圍縮小,並建立復原豁免機制以處理衝突情境,是較為穩健的做法。

適用邊界與結論

Kubernetes Admission Control 的設計體現了分布式系統中常見的取捨哲學:沒有完美的方案,只有適合當前情境的選擇。Fail-Open 與 Fail-Closed 並非對立選項,而是風險轉移的不同路徑。架構師的任務不是尋找完美設定,而是確保在故障發生時,團隊擁有清晰的復原路徑與可觀測性。

承認 Fail-Closed 帶來的結構性風險,並預先規劃應對這種風險的機制(如緊急覆寫流程),是維持長期穩定性的重要條件之一。適用邊界、結構性取捨與可觀測及復原後果,共同構成了現代集群安全治理的核心課題。

Sources