題幹與適用場景
平台團隊要在 Pod 建立時注入預設安全上下文與可觀測標籤,但不想維護外部 mutating webhook。請基於 Kubernetes MutatingAdmissionPolicy 設計策略、binding、衝突處理、稽核與回滾方案。
Kubernetes v1.36 將 MutatingAdmissionPolicy 標記為 stable。它在 API server 內用 CEL 描述匹配與變更,可透過 ApplyConfiguration 或 JSONPatch 修改入站物件;策略定義與 binding 分離。面試重點是把宣告式變更放進可稽核、可回滾的 admission 鏈,而不是簡單把 webhook YAML 改成另一種格式。
面試官考察點
面試官會看你能否區分 policy 與 binding;能否選擇 ApplyConfiguration 或 JSONPatch;能否保證 patch 冪等、欄位所有權清楚且不覆蓋使用者值;能否處理 failurePolicy、匹配範圍、順序、衝突、自身保護與升級回滾;能否用稽核與指標證明變更真的生效。
回答前需要釐清的問題
目標物件與預設值
確認只處理 Pod 還是也處理 Deployment、Job 等工作負載,哪些欄位是強制預設,哪些欄位允許使用者覆寫,以及服務帳號、namespace 和標籤選擇範圍。
版本與執行邊界
確認叢集是否為 v1.36、是否啟用 admissionregistration.k8s.io/v1,現有 webhook 是否仍在鏈路中,以及策略是否必須跨多個叢集重用。
風險與回滾
確認不能被修改的安全欄位、允許的故障模式、稽核保留期、變更窗口和停用策略時對既有物件及新請求的影響。
30 秒回答框架
「我先用 policy 定義匹配條件與冪等變更,再用 binding 限定 namespace、資源和參數。簡單預設值優先 ApplyConfiguration,需要精確陣列操作時才用 JSONPatch。策略不匹配時跳過,變更錯誤按 failurePolicy 決定 fail 或 ignore;我會避免匹配自身並限制 RBAC。上線採用 dry-run、分層 binding 與稽核指標,回滾先解除 binding,再按版本恢復策略,確保既有物件不會被誤改。」
分步驟深入解答
第一步:拆分策略與 binding
Policy 儲存規則、變數、匹配條件與變更表達式;Binding 決定哪些資源與 namespace 採用該策略,並可提供參數。這樣可以重用同一策略,在不同租戶或環境用不同 binding 逐步放量。
第二步:選擇變更表示
ApplyConfiguration 適合表達結構化預設值,讓變更意圖接近物件模型;JSONPatch 適合精確增刪路徑,但必須正確處理陣列索引和 JSON Pointer 轉義。兩者不能混用成「先覆蓋再猜測」,每個欄位都要有明確所有權與覆寫規則。
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: pod-default-observability
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
mutations:
- applyConfiguration:
expression: >-
Object{metadata: Object{labels: {"observability.example.com/enabled": "true"}}}
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicyBinding
metadata:
name: pod-default-observability-binding
spec:
policyName: pod-default-observability
matchResources:
namespaceSelector:
matchLabels:
platform.example.com/enabled: "true"第三步:保證冪等與不覆蓋
只對缺少欄位寫入預設值,使用者已明確設定的值保持不變。對列表欄位要定義按鍵合併還是整列表替換;多次 admission、重試和 update 都不能不斷追加重複元素。對修改後物件重新評估會不會觸發同一規則,避免自激迴圈。
第四步:控制匹配與自身保護
用 apiGroups、資源、操作、namespaceSelector 和 objectSelector 縮小範圍。MutatingAdmissionPolicy 不能匹配自身或其 binding,避免策略把自己的設定改到不可恢復。高風險欄位採用明確 allowlist,不讓任意使用者提供的 CEL 參數擴大寫入權限。
第五步:定義失敗與順序
區分「不匹配」「表達式回傳空」「變更錯誤」和 API server 暫時失敗。failurePolicy 要結合安全目標選擇 Fail 或 Ignore,並配套告警。不要依賴多個 mutator 的隱含呼叫順序;若多個策略寫同一欄位,應拆分所有權、讓結果可交換,或在單一策略中集中決策。
第六步:遷移 webhook 與版本化
先以 shadow 或只寫稽核的方式比較舊 webhook 與新 policy 結果,再 binding 到小範圍 namespace。記錄策略版本、request UID、原物件摘要與變更原因;確認沒有衝突後逐步解除 webhook。每次變更保留可回滾的 policy 名稱與 manifest,避免原地編輯導致無法比較。
第七步:回滾與觀測
緊急回滾先暫停或刪除 binding,讓新請求停止變更,再恢復上一版 policy。既有物件不會因解除 binding 自動反向修改,若需要清理必須用獨立、可稽核的 controller 或批次處理。監控 admission 延遲、拒絕率、變更計數、表達式錯誤和按 namespace 的命中率。
高品質示範回答
我會把 policy 當作不可變規則版本,把 binding 當作放量和權限邊界。先限定 CREATE Pod 與目標 namespace,只對缺少標籤和安全預設值做冪等 ApplyConfiguration;涉及陣列路徑時使用經過測試的 JSONPatch。failurePolicy、欄位 allowlist、RBAC 和自身保護先定義清楚,避免多個策略爭搶同一欄位。遷移時以舊 webhook 結果做對照,先小範圍 binding,再用稽核、延遲和錯誤指標擴大範圍。回滾解除 binding 並恢復上一版 policy;既有物件不自動回滾,所以需要獨立清理流程和審批記錄。
常見錯誤
- 錯誤表現: 把所有欄位整物件覆蓋。→ 失敗原因: 會擦掉使用者顯式設定並製造所有權衝突。→ 修正方法: 只寫缺少欄位,定義列表合併規則。
- 錯誤表現: 以為刪除 binding 會恢復舊物件。→ 失敗原因: admission 只影響請求,不提供反向變更。→ 修正方法: 另設可稽核清理流程,並說明既有物件不會自動回滾。
- 錯誤表現: 多個 policy 依賴固定執行順序。→ 失敗原因: admission 鏈順序變化會產生不同結果。→ 修正方法: 分離欄位所有權或集中到單一策略。
- 錯誤表現: 直接替換生產 webhook。→ 失敗原因: 新舊表達式差異可能在高峰期放大。→ 修正方法: 先 shadow、分 namespace 放量,保留版本化 manifest。
追問及應對
ApplyConfiguration 和 JSONPatch 怎麼選?
結構化預設值優先 ApplyConfiguration,表達意圖更清楚;需要精確刪除、插入或轉義路徑時用 JSONPatch。無論選擇哪種,都要測試重複執行和陣列衝突。
failurePolicy 應該總是 Fail 嗎?
安全基線或合規欄位通常傾向 Fail,非關鍵可觀測標籤可以在明確告警和補償機制下選擇 Ignore。決策要與影響範圍、可用性目標和稽核證據綁定。
如何測試策略不會改壞物件?
用伺服器 dry-run、固定輸入快照、重複 admission、不同 namespace 標籤、使用者已設定欄位、空列表和表達式錯誤做矩陣測試,並比較舊 webhook 與新 policy 的差異。
為什麼不直接寫一個 controller?
Admission 能在物件持久化前阻止或修改請求,適合預設值和入口約束;controller 適合非同步收斂和既有物件修復。兩者職責不同,可組合但不能用 controller 取代入口安全策略。