管理 Kubernetes Webhook 故障:從診斷到解決方案

管理 Kubernetes Webhook 故障:從診斷到解決方案

在現代雲原生架構中,Kubernetes Admission Webhook 扮演著至關重要的角色,允許我們擴充和自定義 Kubernetes 的行為,而無需修改核心代碼。然而,這種強大的功能也帶來了潛在的風險 — 當 webhook 發生故障時,可能會對整個叢集造成嚴重影響,甚至導致系統性的服務中斷。

作為一名在容器技術領域的實踐者,我曾協助處理過多起因 webhook 故障導致的生產環境事件。這些經驗讓我深刻認識到,了解 webhook 的運作機制、掌握故障診斷技巧,以及實施適當的監控策略,對於維護 Kubernetes 叢集的穩定性至關重要。因此,本篇內容將著重於分享從診斷到解決方案的全面指南,包含實際案例分析和預防措施。透過這些內容,希望能夠幫助更多 Kubernetes 管理員在面對 webhook 相關問題時,能夠迅速找到根本原因並採取適當的解決方案。

本篇內容同時也摘要總結分享至 Kubernetes Community Day (KCD) Taipei 2025 演講 1

Kubernetes Admission Webhook 概述

Kubernetes Admission Control 是 Kubernetes 中的一種機制,允許在資源被儲存到 etcd 之前攔截和修改 API 請求。而 Kubernetes Admission Webhook 則屬於動態擴充機制的一部分,允許開發者自定義和擴充 Kubernetes 的控制邏輯。這種機制透過 HTTP 回調的方式,使外部服務能夠參與到資源管理決策中,在資源被持久化到 etcd 之前進行驗證或修改。

Kubernetes 提供了許多原生內建的 Admission 插件,可以透過 Kubernetes API Server 的 --enable-admission-plugins 參數啟用,例如:

kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger...

例如,在本文撰寫時,最新版本為 Kubernetes 1.33,預設啟用了以下 Admission 插件:

  • CertificateApproval
  • CertificateSigning
  • CertificateSubjectRestriction
  • DefaultIngressClass
  • DefaultStorageClass
  • DefaultTolerationSeconds
  • LimitRanger
  • MutatingAdmissionWebhook
  • NamespaceLifecycle
  • PersistentVolumeClaimResize
  • PodSecurity
  • Priority
  • ResourceQuota
  • RuntimeClass
  • ServiceAccount
  • StorageObjectInUseProtection
  • TaintNodesByCondition
  • ValidatingAdmissionPolicy
  • ValidatingAdmissionWebhook

為了擴增 Kubernetes 的功能性,額外的 MutatingAdmissionWebhook 以及 ValidatingAdmissionWebhook 為 Kubernetes 動態擴充 Kubernetes API 的關鍵機制。它們能夠在資源被 Kubernetes API Server 處理的過程中攔截請求,執行額外的邏輯,例如驗證資源規格是否符合組織政策,或者自動修改資源配置以符合特定需求。

在實際應用中,Admission Webhook 被廣泛用於多個場景:

  • 鏡像安全掃描:確保只有經過安全掃描的容器鏡像才能部署到叢集
  • 命名空間管理:自動為資源添加特定的標籤或註釋
  • Sidecar 注入:自動注入監控和日誌收集的 sidecar 容器

許多開源專案依賴 webhook 來提供功能,如 Istio(用於 sidecar 注入)、Cert-manager(自動化憑證管理)、Kyverno 和 Gatekeeper(策略控制)等。

假設你需要根據 Pod 的標籤來自動加上對應的註解。比如說,當一個 Pod 帶有標籤 app=nginx 時,你需要自動加上一個叫做 foo 的註解,其值為 stack。但如果 app 標籤的值是 mongo,則註解的值應該是不同的。這本質上需要一個查找表來將 X 值映射到 Y 值,這通常不太容易實現。而使用 Kyverno,不僅可以取代這些客製化的註解工具,還可以利用原生的 Kubernetes 資源如 ConfigMap 來定義這個對照表,並動態套用相對應的值。這裡的動態操作,便涉及了 Kubernetes Admission Webhook 的運作機制。以下是一個 Kyverno 規則示例,它可以根據 Pod 的標籤動態地添加註解:

Kyverno 使用案例

首先,我們需要一個作為參考表的 ConfigMap。這就是一個標準的 ConfigMap,其中每個鍵 (Key) 都有對應的值 (Value),形成標籤和註解之間的映射關係。

apiVersion: v1
kind: ConfigMap
metadata:
  name: resource-annotater-reference
  namespace: default
data:
  httpd: conf
  nginx: stack
  tomcat: java

接下來,我們需要一個包含變更邏輯的策略。以下是一個範例,它做了幾件事。首先,它使用了 Kyverno 中稱為 context 的概念來提供對現有 ConfigMap 的引用。其次,它使用 JMESPath 表達式 來執行參考並插入名為 foo 的註解值。

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: resource-annotater
spec:
background: false
rules:
  - name: add-resource-annotations
    context:
    - name: LabelsCM
      configMap:
        name: resource-annotater-reference
        namespace: default
    preconditions:
    - key: ""
      operator: NotEquals
      value: ""
    - key: ""
      operator: Equals
      value: "CREATE"
    match:
      resources:
        kinds:
        - Pod
    mutate:
      overlay:
        metadata:
          annotations:
            foo: "}}"

最後,讓我們用一個簡單的 Pod 定義來測試。在這個定義中,輸入是一個名為 app 的標籤,其值為 nginx

apiVersion: v1
kind: Pod
metadata:
  labels:
    app: nginx
  name: mypod
spec:
  automountServiceAccountToken: false
  containers:
  - name: busybox
    image: busybox:1.28
    args:
    - "sleep"
    - "9999"

建立這資源後,檢查 Pod 註解就會看到 Kyverno 根據傳入的 app 標籤值,寫入了從 ConfigMap 中找到的對應值。

$ kubectl get po mypod -o jsonpath='{.metadata.annotations}' | jq
{
  "foo": "stack",
  "policies.kyverno.io/patches": "add-resource-annotations.resource-annotater.kyverno.io: removed /metadata/creationTimestamp\n"
}

Kubernetes Admission Webhook 工作流程

在了解基礎的 Kubernetes Admission Webhook 概念後,讓我們來看看 Webhook 的工作流程。在典型的 API 請求處理過程中,當用戶或系統組件向 Kubernetes API Server 發送請求時,該請求會經過一系列的處理步驟。首先,請求會通過認證(Authentication)和授權(Authorization)檢查,確保請求者有權執行該操作。接著,請求會進入准入控制(Admission Control)階段,其中包括 Mutating Webhook 和 Validating Webhook 的處理。

Admission Webhook 主要分為兩種類型:

  • 驗證型 (Validating) Webhook:負責驗證資源請求是否符合特定規則,可以接受或拒絕請求,但不能修改請求內容。
  • 修改型 (Mutating) Webhook:除了可以驗證請求外,還能修改請求內容,例如新增預設值或注入額外配置。

Kubernetes Admission Webhook 工作流程

在 API 伺服器的請求處理流程中,當一個請求到達時,首先會進行身份驗證和授權檢查,然後會依序經過所有已註冊的 Mutating Webhook。這些 Webhook 可以對請求進行修改,例如新增標籤、註解或是注入 Sidecar 容器。完成修改後,請求會進入 Validating Webhook 階段,在這個階段中,Webhook 只能驗證請求是否符合特定規則,但不能進行任何修改。最後,如果所有的 Webhook 都通過,請求才會被永久保存到 etcd 中。

在這背後,API Server 將會主動呼叫並且觸發對應的 Webhook 服務。例如,當使用者嘗試創建 Pod 資源時,API Server 將會請求 Webhook 服務進行驗證或修改。Webhook 服務收到請求後,會根據預定的邏輯處理該請求,並返回結果給 API Server。API Server 根據 Webhook 的回應決定是否繼續處理該請求,或者返回錯誤給使用者。這種請求與回應的循環構成了 Webhook 的基本工作流程。

Kubernetes Admission Webhook 互動流程

其中,作為 Webhook 服務的應用程式需要返回 AdmissionReview 類型的回應資料 (200 HTTP status code),資料結構為 Content-Type: application/json。這個回應告訴 API Server 該請求是被允許還是被拒絕,以及可能需要的修改內容。以下是兩個典型的 AdmissionReview回應範例,分別代表接受和拒絕請求的情況。

接受請求的 webhook 回應示例:

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true
  }
}

拒絕請求的 webhook 最小回應示例 (Validating webhook):

{
 "kind": "AdmissionReview",
 "apiVersion": "admission.k8s.io/v1",
 "response": {
   "uid": "9e8992f7-5761-4a27-a7b0-501b0d61c7f6",
   "allowed": false,
   "status": {
     "message": "pod name contains \"offensive\"",
     "code": 403
   }
 }
}

那 Kubernetes API Server 是如何具體得知在執行特定 API 操作時,要觸發哪個 Webhook 呢?這就輪到 MutatingWebhookConfigurationValidatingWebhookConfiguration 這兩類資源定義來發揮作用了。這兩類配置告訴 API Server 哪些 Webhook 服務應該在什麼情況下被觸發。這些配置資源包含了 Webhook 的 URL、驗證選項、匹配規則以及錯誤處理策略等重要信息。

以下展示了一個基本的驗證型 webhook (Validating webhook),它會:

  • 監控所有 Pod 的建立操作
  • 通過指定的服務發送請求到 webhook 服務 (webhook-service)
  • 如果 webhook 服務失敗,根據 failurePolicy 決定是否允許請求通過
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-policy.example.com
webhooks:
  - name: pod-policy.example.com
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
    clientConfig:
      service:
        name: webhook-service
        namespace: default
        path: "/validate"
    failurePolicy: Fail

自定義 Mutating webhook 的設定也十分類似,以下用 AWS Load Balancer Controller 實現自動注入 Pod readinessGates 的功能為例,其設定如下:

$ kubectl describe MutatingWebhookConfiguration/aws-load-balancer-webhook
Name:         aws-load-balancer-webhook
Namespace:
Labels:       app.kubernetes.io/instance=aws-load-balancer-controller
              app.kubernetes.io/name=aws-load-balancer-controller
              app.kubernetes.io/version=v2.7.2
API Version:  admissionregistration.k8s.io/v1
Kind:         MutatingWebhookConfiguration
Webhooks:
  Admission Review Versions:
    v1beta1
  Client Config:
    Service:
      Name:        aws-load-balancer-webhook-service
      Namespace:   kube-system
      Path:        /mutate-v1-pod
      Port:        443
  Failure Policy:  Fail
  Match Policy:    Equivalent
  Name:            mpod.elbv2.k8s.aws
  Namespace Selector:
    Match Expressions:
      Key:       elbv2.k8s.aws/pod-readiness-gate-inject
      Operator:  In
      Values:
        enabled
  Object Selector:
    Match Expressions:
      Key:       app.kubernetes.io/name
      Operator:  NotIn
      Values:
        aws-load-balancer-controller
  Rules:
    API Versions:
      v1
    Operations:
      CREATE
    Resources:
      pods
  ...

這個 AWS Load Balancer Controller 的 MutatingWebhookConfiguration 展示了幾個關鍵設定項:

  • Client Config:定義 API Server 與 webhook 服務的通信方式:
    • 指向 kube-system 命名空間中的 aws-load-balancer-webhook-service
    • 使用 /mutate-v1-pod 路徑處理 Pod 變更
    • 透過 443 端口進行安全通信
  • Failure Policy:設為 Fail,表示 webhook 無回應時 API Server 會拒絕請求。這是重要的安全措施,但可能在故障時阻礙資源創建。
  • 命名空間選擇器 (Namespace Selector):僅處理帶有標籤 elbv2.k8s.aws/pod-readiness-gate-inject: enabled 的命名空間中的資源。這提供精細控制,讓管理員可選擇需要此功能的命名空間。
  • 物件選擇器 (Object Selector):排除標籤為 app.kubernetes.io/name: aws-load-balancer-controller 的資源,避免 webhook 處理 AWS Load Balancer Controller 本身,防止循環依賴問題。
  • 規則定義 (Rules):指定僅處理 Pod 資源的建立 (CREATE) 操作,進一步限制其作用範圍。

這個設定明確界定了 webhook 的行為邊界,確保它只在需要的地方生效(特定標籤的命名空間中的新建 Pod)。

AWS Load Balancer Controller Webhook Service 工作流程

以下是具體 API 請求經由 API Server 觸發和更改 Pod 規格的流程:

  1. 用戶建立標記了 elbv2.k8s.aws/pod-readiness-gate-inject: enabled 的命名空間
  2. 當在此命名空間中建立 Pod 時,API Server 會檢查 MutatingWebhookConfiguration
  3. API Server 發現 Pod 建立操作符合 webhook 規則,發送請求到 AWS Load Balancer Controller webhook 服務 (借助 kube-dns 解析 aws-load-balancer-webhook-service.kube-system.svc 對應的服務 Cluster IP 地址)
  4. Webhook 服務收到請求後,檢查 Pod 規格並注入所需的 readinessGates 配置
  5. API Server 接收修改後的 Pod 規格,將其保存到 etcd 中

從以上的討論中,我們可以看到 Kubernetes Admission Webhook 的工作流程相當複雜,牽涉到多個組件之間的通信和協作。當使用者或系統發起一個請求創建資源時,該請求需要經過一系列的檢查和處理才能最終被執行。這個過程中的任何環節出現問題,都可能導致整個工作流程受阻,進而影響叢集的正常運作。

常見的 Webhook 故障模式

基於實際案例分析,我總結了幾種常見的 webhook 故障模式:

1. 網路連接問題

網路連接問題是 webhook 最常見的故障之一。當叢集內的網路通信出現問題時,可能導致 webhook 服務無法正常響應 API Server 的請求。例如,以下是一個典型的錯誤信息,表明 API Server 無法找到 webhook 服務:

error when patching "istio-gateway.yaml": Internal error occurred: failed calling webhook "validate.kyverno.svc-fail": failed to call webhook: Post "https://kyverno-svc.default.svc:443/validate/fail?timeout=10s": context deadline exceeded

這些問題可能源於 DNS 解析失敗、網路延遲過高、CNI 錯誤,或是服務發現機制失效等情況。以 Amazon EKS 環境為例,以下是一個常見 Webhook 服務在雲原生環境中的資料流,它揭示了 API Server 如何通過雲端基礎設施連接到 webhook 服務:

網路連接流程

  • API Server 位於 AWS 管理的 EKS Control Plane,而 webhook 服務則運行在客戶 VPC 的 EC2 實例上
  • 為實現高可用性,webhook 服務通常部署在不同可用區 (AZ-1, AZ-2) 的子網路中
  • 安全群組限制了進出 webhook 服務的網路流量

此架構中可能出現的網路連接問題包括:子網路路由錯誤、安全群組規則限制、DNS 解析失敗 (CoreDNS)、系統層網路問題 (例如 kube-proxy 無法正確工作設定 iptables 或 ipvs 規則影響到目標 Pod) 等,任何一環出現問題都可能導致 webhook 調用失敗,進而影響整個叢集的正常運作。在故障診斷時,這些網路路徑都是需要檢查的重點。

2. 證書過期和 TLS 問題

TLS 連接示意圖

證書過期和 TLS 問題是另一個常見的故障點。當 webhook 的 TLS 證書過期或配置不當時,API Server 將無法與 webhook 服務建立安全連接。這種情況特別棘手,因為證書即將過期的問題往往不易被及時發現。

舉例來說,在我協助客戶案例的經驗中,AWS Load Balancer Controller 過去的版本曾因常遇到證書過期,而導致新的 Ingress 資源無法創建。

Events:
  Type     Reason             Age                From     Message
  ----     ------             ----               ----     -------
  Warning  FailedDeployModel  53m (x9 over 63m)  ingress  (combined from similar events): Failed deploy model due to Internal error occurred: failed calling webhook "mtargetgroupbinding.elbv2.k8s.aws": Post "https://aws-load-balancer-webhook-service.kube-system.svc:443/mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding?timeout=30s": x509: certificate has expired or is not yet valid: current time 2022-03-03T07:37:16Z is after 2022-02-26T11:24:26Z

為了解決此問題,最新版本在使用 Helm 安裝時,會透過內建的 genCA 方法自動產生有效期超過十年的自簽憑證

若你的集群環境採用 Cert-manager 等套件來輪替 TLS 證書,則需要特別注意憑證更新機制是否正常運作,並確保在憑證到期前有充足的處理時間,實施自動化的憑證輪替機制,以減少人為干預的需求。

3. 延遲和性能下降 (Control Plane)

控制平面層延遲和性能下降是另一個重要的故障來源。當 Kubernetes Master Node 服務響應時間過長,或者服務本身處理能力下降時,可能導致請求超時。這種情況通常發生在大規模負載過高,或者底層資源(如 CPU、記憶體)不足時,可能會導致請求堆積和超時。

以內建的 ResourceQuota 為例,作為內建 Kubernetes Admission Controller 的一部分,其工作流程與 Webhook 非常相似。當使用者嘗試建立或更新資源時,API Server 會呼叫 ResourceQuota Admission Controller 檢查是否違反資源配額限制。這個過程中,API Server 需要計算資源的當前使用量並進行比較,對應的資源限制狀態將會更新至 etcd。

ResourceQuota 工作流程

一個有趣的案例是 ResourceQuota 因為 Kubernetes Controller Manager 負載過高無法正確工作,常見於 Kubernetes 資源過多或是 CPU 處理速度過慢導致。由於 ResourceQuota 屬於預設 Validating Admission Plugin 的一部分,當其監控邏輯出現問題時,可能會影響到整個集群的資源配額執行。這種情況下,集群可能無法正確限制資源使用,導致超額配置或資源分配不均。以下是一個實際案例,展示了 ResourceQuota Controller 因為同步超時而影響集群運作的情況:

E0119 11:37:53.532226       1 shared_informer.go:243] unable to sync caches for garbage collector
E0119 11:37:53.532261       1 garbagecollector.go:228] timed out waiting for dependency graph builder sync during GC sync (attempt 73)

I0119 11:37:54.680276       1 request.go:645] Throttling request took 1.047002085s, request: GET:https://10.150.233.43:6443/apis/configuration.konghq.com/v1beta1?timeout=32s
I0119 11:37:54.831942       1 shared_informer.go:240] Waiting for caches to sync for garbage collector
I0119 11:38:04.722878       1 request.go:645] Throttling request took 1.860914441s, request: GET:https://10.150.233.43:6443/apis/acme.cert-manager.io/v1alpha2?timeout=32s
E0119 11:38:04.861576       1 shared_informer.go:243] unable to sync caches for resource quota
E0119 11:38:04.861687       1 resource_quota_controller.go:447] timed out waiting for quota monitor sync

在這個範例中,我們可以看到 API Server 發出請求到 ResourceQuota 控制器時,可能會因為控制平面負載過高而導致處理延遲,使得套用的 ResourceQuota 更新與 etcd 的紀錄不一致,比如套用新的 記憶體上限 (52Gi) 但無法在新的資源中同步套用 (62Gi)。

$ kubectl get quota webs-compute-resources -n webs -o yaml
spec:
  hard:
    pods: "18"
    limits.cpu: "26"
    limits.memory: "52Gi"
status: {}

$ kubectl describe namespace webs
Name:         webs
Labels:       team=webs
              webs.tree.hnc.x-k8s.io/depth=0
Annotations:  <none>
Status:       Active

Resource Quotas
 Name:     webs-compute-resources
 Resource  Used  Hard
 --------  ---   ---

Resource Limits
 Type       Resource  Min   Max   Default Request  Default Limit  Max Limit/Request Ratio
 ----       --------  ---   ---   ---------------  -------------  -----------------------
 Container  cpu       50m   2     150m             500m           -
 Container  memory    16Mi  18Gi  32Mi             64Mi           -

新建立的 Pod 事件仍參考舊有的記憶體限制 (62Gi),但後續的更新顯示限制已降至 52Gi,然而系統行為仍然參考舊的限制值。這種不一致性可能導致資源配置混亂,甚至阻礙新 Pod 的建立。在高負載環境中,控制平面組件之間的同步延遲會更加嚴重。

creating: pods "workspace-7cc88789c5-fmcxm" is forbidden: exceeded quota: compute-resources, requested: limits.memory=10Gi, used: limits.memory=60544Mi, limited: limits.memory=62Gi

4. 資源限制和擴展性問題 (Data Plane)

當 webhook 服務的資源配置不足,或者無法有效地進行水平擴展時,可能會導致服務不穩定。例如,如果 webhook Pod 的記憶體限制設置過低,可能會導致 OOM(Out of Memory)錯誤;或者如果沒有正確配置 HPA(Horizontal Pod Autoscaling),在高負載時可能無法及時擴展來處理增加的請求量。

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: pod-resources-example
spec:
  resources:
    requests:
      memory: "100Mi"
    limits:
      memory: "200Mi"
  containers:
  - name: memory-demo-ctr
    image: nginx
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

連鎖故障案例

連鎖故障是指當一個核心組件的 webhook 出現問題時,可能會觸發一連串的系統性故障。以下是幾個實際案例:

系統資源耗盡故障案例

在一個真實案例中,Webhook 服務因為因為節點資源不足發生的錯誤。核心問題由於節點的記憶體不足加上磁碟空間接近滿觸發驅逐事件 (Eviction):

$ kubectl get pods -n kube-system
NAME                 READY   STATUS      RESTARTS   AGE
example-pod-1234     0/1     Evicted     0          5m
example-pod-5678     0/1     Evicted     0          10m
example-pod-9012     0/1     Evicted     0          7m
example-pod-3456     0/1     Evicted     1          15m
example-pod-7890     0/1     Evicted     0          3m

最終導致新的 Pod 無法創建,造成整個服務不可用。

$ kubectl get events -n curl
...
23m Normal   SuccessfulCreate replicaset/curl-9454cc476   Created pod: curl-9454cc476-khp45
22m Warning  FailedCreate     replicaset/curl-9454cc476   Error creating: Internal error occurred: failed calling webhook "namespace.sidecar-injector.istio.io": failed to call webhook: Post "https://istiod.istio-system.svc:443/inject?timeout=10s": dial tcp 10.96.44.51:443: connect: connection refused

Node-pressure Eviction 發生在當節點面臨資源壓力(例如記憶體不足、磁碟空間不足或磁碟 I/O 壓力)時。Kubelet 會根據預設或自定義的閾值監控這些資源,當超過閾值時,節點會被標記為有壓力狀態,並開始驅逐 Pod 以釋放資源。這種情況下,優先級較低的 Pod 會先被驅逐,直到節點資源壓力降低到安全水平。然而,如果驅逐進程未能及時停止,可能會連鎖影響到關鍵的 webhook 服務 Pod,導致它們也被驅逐。當 webhook 服務不可用時,API Server 無法完成任何受該 webhook 關聯的資源操作,進而導致整個集群的功能受限。在高壓力環境下,這種連鎖反應可能會迅速擴散,從單個節點問題演變為全集群服務中斷。

Kubernetes Controller Manager 阻塞故障案例

在這個案例中,Controller Manager 因為無法與 webhook 服務建立連接而陷入重試循環,導致系統性能下降。從日誌可以看出,控制器嘗試同步作業時遇到了 webhook 調用失敗的問題:

I0623 12:15:42.123456       1 job_controller.go:256] Syncing Job default/example-job
E0623 12:15:42.124789       1 job_controller.go:276] Error syncing Job "default/example-job": Internal error occurred: failed calling webhook "validate.jobs.example.com": Post "https://webhook-service.default.svc:443/validate?timeout=10s": dial tcp 10.96.0.42:443: connect: connection refused
W0623 12:15:42.125000       1 controller.go:285] Retrying webhook request after failure
E0623 12:15:52.130123       1 job_controller.go:276] Error syncing Job "default/example-job": Internal error occurred: failed calling webhook "validate.jobs.example.com": Post "https://webhook-service.default.svc:443/validate?timeout=10s": dial tcp 10.96.0.42:443: connect: connection refused
W0623 12:15:52.130456       1 controller.go:285] Retrying webhook request after failure

在這個案例中,原先只是單一個 Webhook 故障導致 Pod 無法建立的錯誤,然而,引爆連鎖故障的關鍵在於這個案例也同時部署了大量的 Kubernetes Jobs,這些 Kubernetes Jobs 會連續不斷地被重新建立,每一次 Job 建立都會觸發 webhook 調用。

當 webhook 服務不可用時,Job Controller 會進入重試循環。Job Controller 同時屬於 Kubernetes Controller Manager 的一部分,Kubernetes Controller Manager 本身的設計為一個無窮迴圈,不斷的運行其他控制器。隨著 Job 數量增加,Job Controller 的資源消耗急劇上升 (CPU),因為迴圈大部分時間都在處理 Job Controller 的工作,進而影響到其他 Kubernetes Controller Manager 核心功能的正常運作。

kube-controller-manager 運作示例

這種情況特別嚴重的是,當 Kubernetes Controller Manager 的處理能力被大量 Job Controller 觸發的 webhook 重試請求佔用時,它無法有效地執行其他關鍵控制器,如 Deployment、StatefulSet 或 DaemonSet。

當 Kubernetes Controller Manager 反覆嘗試與不可用的 webhook 服務通訊,導致控制迴圈被阻塞。隨著重試次數增加,越來越多的請求堆積,這種阻塞效應會迅速擴散,特別是當涉及核心組件(如 CNI 插件依賴地的 DaemonSet Controller)時,CNI 無法正確部署,可能導致新擴展出來的節點無法正確初始化 (處於 NotReady 狀態),即使有足夠的硬體資源也無法恢復服務。

Calico + Kyverno 案例

如果有關鍵組件 (例如 CNI) 與 Webhook 服務存在依賴關係時,更要特別注意 Webhook 故障對於叢集可用性的影響,否則一個簡單的 Webhook 故障可能會導致更嚴重的連鎖效應。

在這個案例提供的審計紀錄檔中,顯示 Calico 網路組件和 Kyverno 策略引擎之間的相互阻塞導致了嚴重的叢集可用性問題。當 Calico 需要更新服務配置時,Kyverno 的 webhook 無法正常回應,因為它沒有可用的端點。這種死鎖狀態使關鍵的網路元件無法更新,最終導致整個叢集的網路功能受損或完全失效。

{
    "kind": "Event",
    "apiVersion": "audit.k8s.io/v1",
    "level": "RequestResponse",
    "stage": "ResponseComplete",
    "requestURI": "/api/v1/namespaces/calico-system/services/calico-typha",
    "verb": "update",
    "responseStatus": {
        "metadata": {},
        "status": "Failure",
        "message": "Internal error occurred: failed calling webhook \"validate.kyverno.svc-fail\": failed to call webhook: Post \"https://kyverno-svc.kyverno.svc:443/validate/fail?timeout=10s\": no endpoints available for service \"kyverno-svc\"",
        "reason": "InternalError",
        "details": {
            "causes": [{
                "message": "failed calling webhook \"validate.kyverno.svc-fail\": failed to call webhook: Post \"https://kyverno-svc.kyverno.svc:443/validate/fail?timeout=10s\": no endpoints available for service \"kyverno-svc\""
            }]
        },
        "code": 500
    },
}

這種錯誤輕則影響網路策略 (NetworkPolicy) 無法正常工作,頂多影響一些 Pod 的網路通訊,然而,嚴重點則可能導致整個叢集網路完全失效。當 Calico 網路元件無法更新或重新配置時,整個叢集的網路功能可能會被癱瘓,節點間的通訊完全中斷,新節點也無法加入叢集。

監控與偵測策略

前面我們探討了一些 webhook 故障的模式,在瞭解到 webhook 故障所帶來的可能影響後,下面要繼續討論一些可以考慮實行的一些錯誤偵測和監控策略。

1. 關鍵指標監控

Kubernetes 預設提供了許多內建以 Prometheus 格式呈現的關鍵指標 2,涉及 Admission Webhook 相關的指標通常有以下幾項:

  • webhook_rejection_count: 追蹤被拒絕的webhook請求數量,異常增加可能表示配置問題或服務不穩定。
  • webhook_request_total: 記錄所有請求總數,幫助了解流量趨勢並識別潛在異常模式。
  • webhook_fail_open_count: 記錄因故障而自動通過的請求數,對設置 failurePolicy: Ignore 的 webhook 尤其重要。

這些指標可以透過訪問 /metrics 路徑採集,常見透過 Prometheus、Datadog 或其他支持 Prometheus 格式的監控系統來收集和分析。以下是一個通過 kubectl 命令檢查 API Server 相關指標的範例:

$ kubectl get --raw /metrics | grep "apiserver_admission_webhook"
apiserver_admission_webhook_request_total{code="400",name="mpod.elbv2.k8s.aws",operation="CREATE",rejected="true",type="admit"} 17

$ kubectl get --raw /metrics | grep "apiserver_admission_webhook_rejection"
apiserver_admission_webhook_rejection_count{error_type="calling_webhook_error",name="mpod.elbv2.k8s.aws",operation="CREATE",rejection_code="400",type="admit"} 17

一些開源的社群整合方案則包含 kube-prometheus-stack 套件,可以透過 Helm 工具快速的安裝套件,提供完整的 Prometheus 和 Grafana 監控解決方案,包含預設的 API Server 和 Webhook 相關指標儀表板。這種整合式方案不僅能幫助我們監控 webhook 的運作狀態,還能在潛在問題發生前提供預警。

kube-prometheus-stack 範例

此外,針對關鍵性的應用和服務,可以參考是否提供對應的 Prometheus 或對應指標監控應用的可用性,建立特定的監控指標和警報閾值。同時監控相關的資源使用情況,如 CPU、記憶體使用率,以及網絡延遲等指標。

2. 記錄檔分析

除了 API server 指標,你也可以透過紀錄檔分析來主動過濾和發現可能的呼叫失敗錯誤。當遇到 webhook 相關問題時,分析API server的日誌可以提供寶貴的線索。從 API Server 觸發 webhook 的操作中便可以窺知一二,Kubernetes 在 Mutating 以及 Validating 階段存在 dispatcher.go 副程式進行觸發操作,在目前版本中 (Kubernetes v1.33),皆以 failed to call webhook 作為錯誤訊息輸出 3 4

if err != nil {
  var status *apierrors.StatusError
  if se, ok := err.(*apierrors.StatusError); ok {
    status = se
  } else {
    status = apierrors.NewServiceUnavailable("error calling webhook")
  }
  return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("failed to call webhook: %w", err), Status: status}
}

因此,我們可以透過上述輸出作為關鍵字。當 Webhook 發生故障時,API Server 通常會記錄詳細的錯誤信息,包括連接超時、TLS 錯誤或服務不可用等問題。並且在雲原生環境中配置集中式日誌收集與分析系統。

以下是在 AWS CloudWatch Log Insight 中過濾 API Server 日誌的範例查詢,這個簡單的查詢示例過濾了所有包含 failed to call webhook 的 kube-apiserver 日誌 5

fields @timestamp, @message, @logStream
| filter @logStream like /kube-apiserver/
| filter @message like 'failed to call webhook'

以下是以 CloudWatch 為例,將過濾的結果轉換成指標,用於監控或是設定告警等 5

CloudWatch Webhook 監控範例

若是自建或是私有地端的 Kubernetes 叢集環境,同樣地也可以持續過濾這些 API Server 日誌,整合自動告警機制,即時發送通知捕捉這些錯誤。

3. 健康狀態監控

定期檢查 webhook 服務的健康狀態是預防問題的第一道防線:

  • 設置基本的 Liveness Probe 和 Readiness Probe
  • 監控服務的響應時間和錯誤率
  • 追蹤 webhook 服務的資源使用情況(CPU、記憶體等)

最佳實踐

在本節中,我們將探討防止和應對 Kubernetes Webhook 故障的最佳實踐。這些建議來自於實際生產環境中的經驗,旨在幫助您建立更穩健的 Kubernetes 環境。以下是幾個關鍵領域的最佳實踐建議:

1. 資源配置最佳化

  • 為 webhook 服務設置適當的資源請求和限制
  • 為關鍵 webhook 服務運行多個副本並且實施水平自動擴展(HPA)以應對負載變化
  • 使用 Pod Disruption Budget 確保服務可用性

2. 安全性考量

  • 定期輪換 TLS 證書,尤其是在使用自簽憑證的情況下
  • 使用 cert-manager 實施自動化的憑證管理時,務必關注憑證的有效期限和更新狀態

3. 其他優化考量

  • 在專用命名空間 (Namespace) 中運行自定義 webhook 服務,避免故障時影響所有 Namespace (例如:正確使用 namespaceSelector 和 objectSelector 等避免故障時影響所有資源,甚至造成 deadlock)
  • 對於 Kubernetes v1.30 之後的版本,考慮使用 ValidatingAdmissionPolicy 取代傳統的驗證 webhook,降低外部應用的依賴,以改善系統穩定性

這是一個簡易的 ValidatingAdmissionPolicy 範例,描述當建立或更新 Deployment 時,副本數量應小於或等於五個。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: "demo-policy.example.com"
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
    - apiGroups:   ["apps"]
      apiVersions: ["v1"]
      operations:  ["CREATE", "UPDATE"]
      resources:   ["deployments"]
  validations:
    - expression: "object.spec.replicas <= 5"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: "demo-binding-test.example.com"
spec:
  policyName: "demo-policy.example.com"
  validationActions: [Deny]
  matchResources:
    namespaceSelector:
      matchLabels:
        environment: test

這類資源可以在 Kubernetes Control Plane 層面完成您原本依賴外部 Webhook 服務的機制,無需呼叫外部 Webhook,從而降低因外部因素導致無法呼叫 Webhook 的風險。

📝 註:MutatingAdmissionPolicy 與 ValidatingAdmissionPolicy 類似,但提供修改資源的能力,目前在 Kubernetes v1.32 版本中處於 Alpha 階段。這項功能將使得開發者能夠直接在 Kubernetes API 中定義資源修改規則,而無需部署和維護外部 webhook 服務,進一步減少系統故障點。詳細訊息,請參考 KEP-39626

總結

Kubernetes Admission Webhook 提供了強大的擴展能力,但同時也引入了潛在的風險點。通過了解常見的故障模式、實施有效的監控策略、採用最佳實踐,我們可以顯著提高系統的穩定性和可靠性。

本篇文章已經詳細介紹了 Kubernetes Webhook 的故障管理,從基本概念到診斷方法,再到監控策略和最佳實踐。我們看到了如何識別常見的故障模式,以及如何通過指標監控和日誌分析來預防問題。特別值得注意的是,隨著 Kubernetes 的發展,像 ValidatingAdmissionPolicy 這樣的新功能正在減少對外部 webhook 的依賴,從而提高系統的穩定性。

希望這篇文章能為你提供有價值的見解和實用的工具,幫助你更有效地管理 Kubernetes 環境中的 webhook 服務。

📧 訂閱 EasonTechTalk:獲得技術洞察與實戰分享

如果你對了解雲端技術和相關趨勢有興趣,歡迎訂閱 EasonTechTalk 電子報以取得更新,讓我們一起在技術路上持續精進!

參考資源

Eason Cao
Eason Cao Eason is an engineer working at FANNG and living in Europe. He was accredited as AWS Professional Solution Architect, AWS Professional DevOps Engineer and CNCF Certified Kubernetes Administrator. He started his Kubernetes journey in 2017 and enjoys solving real-world business problems.
comments powered by Disqus