詳解:深入理解 Kubernetes 的

在 Kubernetes 的准入控制系統中,變更型 Webhook(Mutating Webhook)扮演著關鍵角色,它允許我們在資源被持久化到 etcd 之前動態修改這些資源。這種機制為資源管理提供了極大的靈活性,讓我們能夠實作自動注入 sidecar、預設值設定、資源標準化等功能。

變更型 Webhook 設定資源解析

讓我們來看一個 MutatingWebhookConfiguration 資源的清單範例,並深入瞭解每個欄位的功能:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: example-mutating-webhook-config  # 資源名稱
webhooks:
- name: pod-mutator.example.com  # 准入 webhook 名稱,當請求被拒絕時會顯示給使用者
  clientConfig:
    url: "https://example.com/mutate"  # webhook 服務的 URL
    # 或者使用 service 參考 Kubernetes 叢集內的服務
    # service:
    #   namespace: webhook-namespace
    #   name: webhook-service
    #   path: "/mutate"
    #   port: 8443
    caBundle: "base64編碼的CA證書"  # 用於驗證 webhook 伺服器的 CA 證書
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE", "UPDATE"]
    resources: ["pods"]
    scope: "Namespaced"  # 指定 webhook 適用於名稱空間資源或叢集資源
  failurePolicy: Ignore  # 當 webhook 不可用時的處理策略:Ignore 或 Fail
  timeoutSeconds: 5  # webhook 呼叫的超時時間
  admissionReviewVersions: ["v1", "v1beta1"]  # webhook 支援的 AdmissionReview 版本
  sideEffects: None  # 指定 webhook 是否有副作用:None, NoneOnDryRun
  reinvocationPolicy: Never  # 控制是否在同一請求中多次呼叫 webhook
  matchPolicy: Equivalent  # 定義如何比對資源:Exact 或 Equivalent
  • name:資源的唯一識別符,用於在 API 操作中參照此設定
  • webhooks.name:准入 webhook 的名稱,當請求被拒絕時會顯示給使用者,通常採用網域名稱格式以避免衝突
  • clientConfig:定義如何連線到 webhook 服務
    • url:外部 webhook 服務的 URL
    • service:叢集內 webhook 服務的參考
    • caBundle:用於驗證 webhook 伺服器的 CA 證書,確保通訊安全
  • rules:定義 webhook 處理哪些資源和操作的規則集
  • failurePolicy:當 webhook 不可用時的處理策略
    • Ignore:繼續處理請求,即使 webhook 不可用
    • Fail:如果 webhook 不可用,則拒絕請求
  • timeoutSeconds:webhook 呼叫的超時間,預設為 10 秒
  • admissionReviewVersions:webhook 支援的 AdmissionReview API 版本列表
  • sideEffects:指定 webhook 是否有副作用,影響乾執行(dry run)操作
  • reinvocationPolicy:控制是否在同一請求中多次呼叫 webhook
  • matchPolicy:定義如何比對資源版本

變更型 Webhook 的工作流程

當 API 伺服器收到建立或更新資源的請求時,變更型 Webhook 的處理流程如下:

  1. API 伺服器接收請求並進行基本驗證
  2. 請求透過認證和授權檢查
  3. 變更型准入控制器處理請求
  4. 符合規則的請求被傳送到設定的 webhook
  5. Webhook 服務接收請求,可能對資源進行修改
  6. Webhook 回傳修改後的資源和准入回應
  7. API 伺服器應用這些修改
  8. 請求繼續進行驗證型 webhook 處理(如果有設定)
  9. 最終資源被持久化到 etcd

實用的變更型 Webhook 應用場景

變更型 Webhook 在 Kubernetes 生態系統中有許多實用的應用場景:

  1. Sidecar 容器自動注入:例如 Istio 使用變更型 webhook 自動將 Envoy 代理注入到 Pod 中
  2. 預設資源限制設定:自動為沒有指定資源限制的容器設定預設值
  3. 標籤和註解自動增加:為資源增加標準化的標籤和註解
  4. 安全上下文強制執行:確保容器以非特權模式執行
  5. 設定預處理:動態修改設定對映或金鑰以適應環境需求

設計高用性的變更型 Webhook

在生產環境中,webhook 服務的可用性至關重要。以下是一些最佳實踐:

  1. 佈署多副本:使用 Deployment 確保 webhook 服務有多個副本
  2. 設定合理的 failurePolicy:根據 webhook 的重要性選擇適當的失敗策略
  3. 實施超時控制:設定合理的 timeoutSeconds 值,避免長時間阻塞 API 伺服器
  4. 監控和警示:監控 webhook 的效能和可用性,設定適當的警示
  5. 漸進式佈署:新的 webhook 邏輯應該先在非關鍵名稱空間測試

變更型 Webhook 與驗證型 Webhook 的協同工作

在 Kubernetes 的准入控制流程中,變更型 webhook 先執行,然後是驗證型 webhook。這種設計允許我們先修改資源,然後驗證修改後的資源是否符合策略要求。

例如,一個變更型 webhook 可能會為 Pod 增加安全上下文設定,而後續的驗證型 webhook 則確保所有 Pod 都有適當的安全上下文設定。這種協同工作模式使得資源管理既靈活又安全。

故障排除技巧

當 webhook 設定出現問題時,可能會導致 API 伺服器拒絕請求或資源無法正確建立。以下是一些常見的故障排除技巧:

  1. 檢查 webhook 服務日誌:瞭解 webhook 處理請求時發生了什麼
  2. 檢查 API 伺服器日誌:檢視 API 伺服器與 webhook 通訊的詳細資訊
  3. 使用 kubectl explain:瞭解 MutatingWebhookConfiguration 資源的欄位定義
  4. 測試 webhook 連線性:確保 API 伺服器可以連線到 webhook 服務
  5. 使用 kubectl get events:檢視與 webhook 相關的事件

變更型 Webhook 是 Kubernetes 中強大的擴充套件點,透過深入理解其設定和工作原理,我們可以實作複雜的資源管理策略,提高叢集的自動化程度和安全性。在設計 webhook 時,應始終考慮其對 API 伺服器效能的影響,並實施適當的高用性策略。

深入解析 Kubernetes Admission Webhook 的拒絕機制

理解 Admission Webhook 拒絕流程

當 Kubernetes 中的 Admission Webhook 拒絕請求時,這意味著資源建立或修改操作被阻止。這種拒絕機制是 Kubernetes 安全控制和策略執行的關鍵部分。當 admission reviews 被拒絕時,系統會根據設定的策略回傳特定錯誤訊息,並阻止該操作繼續進行。

failurePolicy: Fail  # 可設為 Fail 或 Ignore

這個設定決定了當 webhook 無法連線或回傳錯誤時的行為。設為 Fail 時,API 請求會被拒絕;設為 Ignore 時,即使 webhook 失敗,API 請求仍會繼續處理。在生產環境中,根據安全需求和可用性考量來選擇適當的策略至關重要。

設定 Webhook 客戶端

Webhook 的客戶端設定決定了 API 伺服器如何連線到 webhook 服務:

clientConfig:
  service:
    namespace: kube-system  # webhook 服務所在的名稱空間
    name: validation-webhook  # webhook 服務名稱
    path: "/validate"  # 可選,指定 webhook 端點路徑
    port: 443  # 可選,指定服務連線埠
  caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t..."  # CA 證書,用於驗證 webhook 服務

這個設定區塊定義了 API 伺服器如何找到並連線 webhook 服務。namespacename 指定了 webhook 服務的位置,而 caBundle 包含了用於 TLS 驗證的 CA 證書。這確保了 API 伺服器只會連線到受信任的 webhook 服務,防止中間人攻擊。

處理 Webhook 拒絕情況

當 webhook 拒絕請求時,可以透過以下方式進行設定來控制行為:

rules:
- apiGroups: ["apps"]
  apiVersions: ["v1"]
  operations: ["CREATE", "UPDATE"]
  resources: ["deployments"]
  scope: "Namespaced"

這個規則定義了 webhook 會處理哪些資源操作。在這個例子中,webhook 會檢查 apps/v1 API 組中的 deployments 資源的 CREATEUPDATE 操作。scope 欄位指定這只適用於名稱空間範圍的資源。透過精確定義規則,可以限制 webhook 的影響範圍,避免不必要的效能開銷。

設定拒絕回應

當 webhook 拒絕請求時,可以回傳詳細的錯誤訊息:

# webhook 回應範例
{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<與請求相同的UID>",
    "allowed": false,
    "status": {
      "code": 403,
      "message": "佈署缺少必要的安全上下文設定"
    }
  }
}

這是一個典型的拒絕回應。allowed 設為 false 表示請求被拒絕,status 包含了錯誤程式碼和訊息。提供清晰的錯誤訊息對於幫助使用者理解為什麼請求被拒絕至關重要,這樣他們可以修正問題並重新提交請求。

處理 Webhook 超時

Webhook 超時也可能導致請求被拒絕:

timeoutSeconds: 5  # webhook 必須在 5 秒內回應

這個設定指定了 API 伺服器等待 webhook 回應的最長時間。如果 webhook 在指定時間內沒有回應,API 伺服器會根據 failurePolicy 決定是拒絕還是忽略請求。設定合理的超時值對於防止 webhook 問題影響整個叢集的可用性非常重要。

實作高用性 Webhook

為了避免 webhook 成為單點故障,應該實作高用性設計:

# 佈署多副本的 webhook
apiVersion: apps/v1
kind: Deployment
metadata:
  name: validation-webhook
  namespace: kube-system
spec:
  replicas: 3  # 執行多個副本
  selector:
    matchLabels:
      app: validation-webhook
  template:
    metadata:
      labels:
        app: validation-webhook
    spec:
      containers:
      - name: webhook
        image: webhook-image:v1
        resources:
          limits:
            memory: "128Mi"
            cpu: "100m"
        readinessProbe:  # 確保只有健康的 pod 接收流量
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 10

這個佈署設定建立了 webhook 服務的多個副本,提高了可用性。readinessProbe 確保只有健康的 pod 會接收流量,這對於防止部分故障的 pod 導致請求被拒絕非常重要。在生產環境中,還應考慮跨節點甚至跨可用區佈署 webhook 副本,以提高容錯能力。

監控 Webhook 拒絕情況

監控 webhook 的拒絕情況對於瞭解系統行為至關重要:

# Prometheus 監控設定範例
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: webhook-monitor
  namespace: monitoring
spec:
  selector:
    matchLabels:
      app: validation-webhook
  endpoints:
  - port: metrics
    interval: 15s
    path: /metrics

這個設定設定了 Prometheus 監控,收集 webhook 的指標資料。透過監控 webhook 的拒絕率、回應時間等指標,可以及時發現問題並進行調整。建立適當的告警也很重要,例如當拒絕率突然上升時發出通知,這可能表明設定變更導致了意外的拒絕。

除錯 Webhook 拒絕問題

當遇到意外的 webhook 拒絕時,可以使用以下方法進行除錯:

# 檢查 webhook 設定
kubectl get validatingwebhookconfigurations -o yaml

# 檢查 webhook 日誌
kubectl logs -n kube-system -l app=validation-webhook

# 使用 kubectl 的 --v 標誌取得更多詳細資訊
kubectl apply -f deployment.yaml --v=8

這些命令有助於診斷 webhook 拒絕問題。檢查 webhook 設定可以確認規則是否正確設定;檢視 webhook 日誌可以瞭解拒絕的具體原因;使用 kubectl 的詳細日誌模式可以看到更多關於 API 請求和回應的資訊。在複雜環境中,還可能需要使用網路抓包工具來分析 API 伺服器和 webhook 之間的通訊。

緩解 Webhook 拒絕的影響

為了減少 webhook 拒絕對系統的影響,可以實施以下策略:

# 使用 namespaceSelector 排除關鍵名稱空間
namespaceSelector:
  matchExpressions:
  - key: kubernetes.io/metadata.name
    operator: NotIn
    values: ["kube-system", "kube-public"]

這個設定使用 namespaceSelector 排除了 kube-systemkube-public 名稱空間,確保這些關鍵名稱空間中的操作不會被 webhook 阻止。這是一種重要的安全措施,可以防止 webhook 問題導致整個叢集無法管理。在生產環境中,應該仔細考慮哪些名稱空間需要排除,以平衡安全性和可用性。

實作漸進式佈署策略

為了安全地更新 webhook 邏輯,應該採用漸進式佈署策略:

# 使用 objectSelector 限制 webhook 範圍
objectSelector:
  matchLabels:
    webhook-validation: enabled

這個設定使用 objectSelector 限制 webhook 只處理帶有特定標籤的資源。這允許漸進式推出新的驗證邏輯:首先只對少量資源啟用,確認沒有問題後再擴大範圍。這種方法可以大降低引入新 webhook 邏輯時的風險,特別是在大型生產環境中。

Kubernetes Admission Webhook 的拒絕機制是實施叢集安全策略和治理的強大工具。透過正確設定 webhook 的客戶端設定、失敗策略、規則和超時,可以建立一個既安全又可靠的驗證系統。同時,實施高用性設計、監控和漸進式佈署策略可以確保 webhook 本身不會成為系統的瓶頸或單點故障。在設計 webhook 時,需要平衡安全需求和系統可用性,確保關鍵操作不會因為 webhook 問題而被阻止。

Webhook 在 Kubernetes 中的應用與實作

Webhook 是 Kubernetes 中一種強大的擴充套件機制,允許外部服務接收關於叢集內事件的通知並對其做出反應。這種機制使得 Kubernetes 能夠與外部系統進行無縫整合,實作更複雜的自動化工作流程。

Webhook 的基本概念與組成

Webhook 在 Kubernetes 中主要由兩個關鍵元素組成:

  1. webhook URL - 這是外部服務的端點,Kubernetes 會向此 URL 傳送 HTTP 請求
  2. caBundle - PEM 編碼的 CA 憑證,用於驗證 webhook 伺服器的 TLS 憑證

這兩個元素共同確保了 Kubernetes 與外部服務之間通訊的安全性和可靠性。

Webhook 的設定範例

以下是一個基本的 webhook 設定範例:

webhooks:
  - name: example-webhook.domain.com
    clientConfig:
      url: "https://example-webhook.domain.com/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ0F3SUJBZ0lJT..."
    rules:
      - apiGroups: ["apps"]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["deployments"]
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions: ["v1", "v1beta1"]
    timeoutSeconds: 5

這個設定了一個定義 webhook,它會在 Kubernetes 叢集中的 Deployment 資源被建立或更新時被觸發。具體來說:

  • name: webhook 的唯一識別名稱
  • clientConfig: 包含 webhook 服務的連線資訊
    • url: webhook 服務的端點 URL
    • caBundle: 用於驗證 webhook 伺服器 TLS 憑證的 CA 憑證
  • rules: 定義何時觸發 webhook
    • 只有 apps/v1 API 群組中的 deployments 資源在 CREATEUPDATE 操作時才會觸發
  • failurePolicy: 當 webhook 無法連線時的行為,設為 Fail 表示操作將被拒絕
  • sideEffects: 指定 webhook 是否有副作用,None 表示沒有
  • admissionReviewVersions: webhook 支援的 AdmissionReview 版本
  • timeoutSeconds: webhook 請求的超時間,這裡設為 5 秒

Webhook 的型別

Kubernetes 中主要有兩種型別的 webhook:

1. 驗證型 Webhook (Validating Webhook)

驗證型 webhook 用於驗證資源是否符合特定條件,它們可以拒絕不符合條件的請求,但不能修改請求內容。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: validation-webhook
webhooks:
  - name: validate.example.com
    clientConfig:
      url: "https://webhook.example.com/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
    failurePolicy: Fail

這個設定建立了一個驗證型 webhook,它會檢查所有 Pod 的建立和更新操作。當 Pod 資源被建立或更新時,Kubernetes API 伺服器會向 https://webhook.example.com/validate 傳送請求,等待驗證結果。如果 webhook 回傳拒絕,則操作將被阻止。

2. 修改型 Webhook (Mutating Webhook)

修改型 webhook 不僅可以驗證資源,還可以在資源被持久化到 etcd 之前修改它們。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: mutation-webhook
webhooks:
  - name: mutate.example.com
    clientConfig:
      url: "https://webhook.example.com/mutate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
    failurePolicy: Ignore

這個設定建立了一個修改型 webhook,它可以在 Pod 被建立時修改其規格。例如,它可以自動注入 sidecar 容器、增加標籤或修改資源限制。與驗證型 webhook 不同,修改型 webhook 在驗證型 webhook 之前執行,這樣驗證型 webhook 就可以驗證修改後的資源。

Webhook 的實際應用場景

1. 政策執行

Webhook 可以用來強制執行組織的政策,例如:

  • 確保所有容器都有資源限制
  • 禁止使用 latest 標籤的映像
  • 要求所有資源都有特定的標籤
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: policy-enforcer
webhooks:
  - name: policy.example.com
    clientConfig:
      url: "https://policy-enforcer.example.com/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: ["*"]
        apiVersions: ["*"]
        operations: ["CREATE", "UPDATE"]
        resources: ["*"]

這個設定建立了一個全域政策執行 webhook,它會檢查所有資源的建立和更新操作。這種 webhook 可以實作組織範圍的政策,例如確保所有工作負載都符合安全標準或資源管理準則。

2. 自動注入 Sidecar

修改型 webhook 常用於自動注入 sidecar 容器,例如 Istio 的服務網格:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: sidecar-injector
webhooks:
  - name: sidecar-injector.istio.io
    clientConfig:
      service:
        name: istio-sidecar-injector
        namespace: istio-system
        path: "/inject"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE"]
        resources: ["pods"]
    namespaceSelector:
      matchLabels:
        istio-injection: enabled

這個設定建立了一個 sidecar 注入器 webhook,它會在標記為 istio-injection: enabled 的名稱空間中自動將 Istio sidecar 容器注入到新建立的 Pod 中。這種自動注入機制使得服務網格的佈署變得透明,開發人員不需要修改他們的應用程式佈署設定。

實作自定義 Webhook 伺服器

以下是使用 Go 語言實作一個簡單的驗證型 webhook 伺服器的範例:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    
    admissionv1 "k8s.io/api/admission/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/serializer"
)

var (
    runtimeScheme = runtime.NewScheme()
    codecs        = serializer.NewCodecFactory(runtimeScheme)
    deserializer  = codecs.UniversalDeserializer()
)

// 驗證 Pod 是否有必要的標籤
func validatePod(pod *corev1.Pod) (bool, string) {
    // 檢查是否有 "app" 標籤
    if _, ok := pod.Labels["app"]; !ok {
        return false, "Pod 必須有 'app' 標籤"
    }
    return true, ""
}

// 處理 webhook 請求
func handleValidate(w http.ResponseWriter, r *http.Request) {
    // 讀取請求內容
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "讀取請求失敗", http.StatusBadRequest)
        return
    }
    
    // 解析 AdmissionReview
    var admissionReview admissionv1.AdmissionReview
    if _, _, err := deserializer.Decode(body, nil, &admissionReview); err != nil {
        http.Error(w, "解析請求失敗", http.StatusBadRequest)
        return
    }
    
    // 準備回應
    var admissionResponse *admissionv1.AdmissionResponse
    
    // 處理 Pod 資源
    if admissionReview.Request.Kind.Kind == "Pod" {
        var pod corev1.Pod
        if err := json.Unmarshal(admissionReview.Request.Object.Raw, &pod); err != nil {
            admissionResponse = &admissionv1.AdmissionResponse{
                Allowed: false,
                Result: &metav1.Status{
                    Message: "無法解析 Pod 資源",
                },
            }
        } else {
            // 驗證 Pod
            allowed, message := validatePod(&pod)
            admissionResponse = &admissionv1.AdmissionResponse{
                Allowed: allowed,
                Result: &metav1.Status{
                    Message: message,
                },
            }
        }
    } else {
        // 非 Pod 資源直接允許
        admissionResponse = &admissionv1.AdmissionResponse{
            Allowed: true,
        }
    }
    
    // 設定 UID
    admissionResponse.UID = admissionReview.Request.UID
    
    // 準備回應
    admissionReview.Response = admissionResponse
    resp, err := json.Marshal(admissionReview)
    if err != nil {
        http.Error(w, "編碼回應失敗", http.StatusInternalServerError)
        return
    }
    
    // 傳送回應
    w.Header().Set("Content-Type", "application/json")
    w.Write(resp)
}

func main() {
    // 設定路由
    http.HandleFunc("/validate", handleValidate)
    
    // 啟動伺服器
    fmt.Println("啟動 webhook 伺服器在 :8443...")
    err := http.ListenAndServeTLS(":8443", "tls.crt", "tls.key", nil)
    if err != nil {
        panic(err)
    }
}

這個 Go 程式實作了一個簡單的驗證型 webhook 伺服器,它會檢查所有 Pod 是否有 app 標籤。具體來說:

  1. 首先設定了必要的 Kubernetes 客戶端函式庫,用於解析 AdmissionReview 請求
  2. validatePod 函式檢查 Pod 是否有 app 標籤,如果沒有則拒絕請求
  3. handleValidate 函式處理 webhook 請求:
    • 讀取並解析請求內容
    • 如果是 Pod 資源,則進行驗證
    • 準備並傳送 AdmissionResponse
  4. main 函式設定 HTTP 路由並啟動 TLS 伺服器

這個 webhook 伺服器需要有 TLS 憑證才能執行,因為 Kubernetes API 伺服器只接受 HTTPS 連線。

佈署 Webhook 伺服器到 Kubernetes

以下是將自定義 webhook 伺服器佈署到 Kubernetes 的範例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-validator
  namespace: webhook-system
spec:
  replicas: 2
  selector:
    matchLabels:
      app: pod-validator
  template:
    metadata:
      labels:
        app: pod-validator
    spec:
      containers:
      - name: validator
        image: example/pod-validator:latest
        ports:
        - containerPort: 8443
        volumeMounts:
        - name: tls
          mountPath: "/etc/webhook/certs"
          readOnly: true
      volumes:
      - name: tls
        secret:
          secretName: webhook-tls
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-validator
webhooks:
  - name: pod-validator.webhook-system.svc
    clientConfig:
      service:
        name: pod-validator
        namespace: webhook-system
        path: "/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions: ["v1", "v1beta1"]

這個 YAML 設定包含三個部分:

  1. Deployment - 佈署 webhook 伺服器的 Pod,包含兩個副本以提高用性
  2. Service - 建立一個服務,使 Kubernetes API 伺服器能夠連線到 webhook 伺服器
  3. ValidatingWebhookConfiguration - 設定 webhook,指定何時觸發以及如何連線到 webhook 伺服器

注意 webhook 設定使用 service 而不是 url,這是在 Kubernetes 叢集內佈署 webhook 伺服器的推薦方式,因為它使用叢集內部 DNS 解析,更加可靠。

Webhook 安全性考量

實作 webhook 時,安全性是一個重要考量:

  1. TLS 憑證 - webhook 伺服器必須使用有效的 TLS 憑證,與 Kubernetes API 伺服器必須信任該憑證
  2. 最小許可權 - webhook 伺服器應該只有完成其任務所需的最小許可權
  3. 超時設定 - 設定合理的超時間,避免 webhook 故障導致整個叢集不可用
  4. 故障策略 - 根據 webhook 的重要性選擇適當的 failurePolicy
  5. 資源限制 - 為 webhook 伺服器設定適當的資源限制,避免資源耗盡

生成 TLS 憑證的方法

以下是使用 OpenSSL 生成 webhook 伺服器 TLS 憑證的範例:

# 生成 CA 私鑰和憑證
openssl genrsa -out ca.key 2048
openssl req -new -x509 -key ca.key -out ca.crt -subj "/CN=Webhook CA"

# 生成伺服器私鑰
openssl genrsa -out server.key 2048

# 生成伺服器憑證簽名請求 (CSR)
openssl req -new -key server.key -out server.csr -subj "/CN=pod-validator.webhook-system.svc"

# 生成伺服器憑證
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

# 建立 Kubernetes Secret
kubectl create secret tls webhook-tls --cert=server.crt --key=server.key -n webhook-system

# 取得 caBundle 值
cat ca.crt | base64 | tr -d '\n'

這個指令碼執行以下操作:

  1. 生成自簽名 CA 憑證,用於簽署 webhook 伺服器憑證
  2. 生成 webhook 伺服器的私鑰和憑證簽名請求
  3. 使用 CA 憑證簽署伺服器憑證
  4. 建立包含伺服器憑證和私鑰的 Kubernetes Secret
  5. 輸出 CA 憑證的 base64 編碼,用於填充 webhook 設定中的 caBundle 欄位

注意伺服器憑證的 CN (Common Name) 必須與 webhook 服務的完整 DNS 名稱比對,這裡是 pod-validator.webhook-system.svc

Webhook 效能最佳化

為了確保 webhook 不會成為系統瓶頸,可以考慮以下最佳化:

  1. 資源請求與限制 - 為 webhook 伺服器設定適當的 CPU 和記憶體資源
  2. 水平擴充套件 - 佈署多個 webhook 伺服器副本以分散負載
  3. 快取機制 - 實作快取以減少重複計算
  4. 選擇性觸發 - 使用 namespaceSelectorobjectSelector 限制 webhook 的觸發範圍
  5. 非阻塞操作 - 在 webhook 伺服器中使用非阻塞 I/O 和並發處理
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: optimized-webhook
webhooks:
  - name: validate.example.com
    clientConfig:
      service:
        name: webhook-service
        namespace: webhook-system
        path: "/validate"
      caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURJVENDQWdtZ..."
    rules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
    namespaceSelector:
      matchExpressions:
      - key: webhook-validation
        operator: In
        values: ["enabled"]
    objectSelector:
      matchExpressions:
      - key: app.kubernetes.io/managed-by
        operator: NotIn
        values: ["system"]
    timeoutSeconds: 2
    failurePolicy: Ignore

這個最佳化的 webhook 設定使用了以下技術:

  1. namespaceSelector - 只在標記為 webhook-validation: enabled 的名稱空間中觸發 webhook
  2. objectSelector - 排除由系統管理的資源,減少不必要的驗證
  3. timeoutSeconds: 2 - 設定較短的超時間,避免長時間阻塞 API 請求
  4. failurePolicy: Ignore - 當 webhook 無法連線時允許操作繼續,提高系統彈性

這些最佳化可以顯著減少 webhook 對系統的影響,特別是在大型叢集中。

Webhook 除錯技巧

當 webhook 不按預期工作時,以下是一些有用的除錯技巧:

  1. 檢查 webhook 伺服器日誌 - 檢視 webhook 伺服器的日誌以瞭解請求處理情況
  2. 檢查 API 伺服器日誌 - API 伺服器日誌可能包含有關 webhook 連線問題的資訊
  3. 使用 kubectl describe - 檢查 webhook 設定的狀態和可能的錯誤
  4. 測試 webhook 連線 - 使用 curl 直接測試 webhook 伺服器的連線性
  5. 臨時停用 webhook - 在緊急情況下,可以刪除 webhook 設定以還原系統功能
# 檢查 webhook 設定
kubectl describe validatingwebhookconfiguration pod-validator

# 檢查 webhook 伺服器日誌
kubectl logs -l app=pod-validator -n webhook-system

# 檢查 API 伺服器日誌
kubectl logs -n kube-system -l component=kube-apiserver

# 臨時停用 webhook
kubectl delete validatingwebhookconfiguration pod-validator

這些命令可以幫助診斷 webhook 問題:

  1. kubectl describe 命令顯示 webhook 設定的詳細資訊,包括可能的警告或錯誤
  2. 檢視 webhook 伺服器的日誌可以瞭解請求處理情況和可能的錯誤
  3. API 伺服器日誌可能包含有關 webhook 連線問題的資訊
  4. 在緊急情況下,可以刪除 webhook 設定以還原系統功能,這是一個重要的"緊急開關"

實際案例:使用 Webhook 實作自動 Pod 安全策略

以下是一個使用 webhook 實作 Pod 安全策略的實際案例:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    
    admissionv1 "k8s.io/api/admission/v1"
    corev1 "k8s.io/api/core/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/apimachinery/pkg/runtime/serializer"
)

var (
    runtimeScheme = runtime.NewScheme()
    codecs        = serializer.NewCodecFactory(runtimeScheme)
    deserializer  = codecs.UniversalDeserializer()
)

// 驗證 Pod 安全性
func validatePodSecurity(pod *corev1.Pod) (bool, string) {
    // 檢查特權容器
    for _, container := range pod.Spec.Containers {
        if container.SecurityContext != nil && 
           container.SecurityContext.Privileged != nil && 
           *container.SecurityContext.Privileged {
            return false, fmt.Sprintf("容器 %s 不允許使用特權模式", container.Name)
        }
    }
    
    // 檢查 hostNetwork
    if pod.Spec.HostNetwork {
        return false, "不允許使用 hostNetwork"
    }
    
    // 檢查 hostPID 和 hostIPC
    if pod.Spec.HostPID {
        return false, "不允許使用 hostPID"
    }
    if pod.Spec.HostIPC {
        return false, "不允許使用 hostIPC"
    }
    
    // 檢查 capabilities
    for _, container := range pod.Spec.Containers {
        if container.SecurityContext != nil && 
           container.SecurityContext.Capabilities != nil {
            for _, cap := range container.SecurityContext.Capabilities.Add {
                if cap == "SYS_ADMIN" || cap == "NET_ADMIN" {
                    return false, fmt.Sprintf("容器 %s 不允許增加 %s capability", container.Name, cap)
                }
            }
        }
    }
    
    return true, ""
}

// 處理 webhook 請求
func handleValidate(w http.ResponseWriter, r *http.Request) {
    // 讀取請求內容
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        http.Error(w, "讀取請求失敗", http.StatusBadRequest)
        return
    }
    
    // 解析 AdmissionReview
    var admissionReview admissionv1.AdmissionReview
    if _, _, err := deserializer.Decode(body, nil, &admissionReview); err != nil {
        http.Error(w, "解析請求失敗", http.StatusBadRequest)
        return
    }
    
    // 準備回應
    var admissionResponse *admissionv1.AdmissionResponse
    
    // 處理 Pod 資源
    if admissionReview.Request.Kind.Kind == "Pod" {
        var pod corev1.Pod
        if err := json.Unmarshal(admissionReview.Request.Object.Raw, &pod); err != nil {
            admissionResponse = &admissionv1.AdmissionResponse{
                Allowed: false,
                Result: &metav1.Status{
                    Message: "無法解析 Pod 資源",
                },
            }
        } else {
            // 檢查是否豁免名稱空間
            if pod.Namespace == "kube-system" {
                admissionResponse = &admissionv1.AdmissionResponse{
                    Allowed: true,
                }
            } else {
                // 驗證 Pod 安全性
                allowed, message := validatePodSecurity(&pod)
                admissionResponse = &admissionv1.AdmissionResponse{
                    Allowed: allowed,
                    Result: &metav1.Status{
                        Message: message,
                    },
                }
            }
        }
    } else {
        // 非 Pod 資源直接允許
        admissionResponse = &admissionv1.AdmissionResponse{
            Allowed: true,
        }
    }
    
    // 設定 UID
    admissionResponse.UID = admissionReview.Request.UID
    
    // 準備回應
    admissionReview.Response = admissionResponse
    resp, err := json.Marshal(admissionReview)
    if err != nil {
        http.Error(w, "編碼回應失敗", http.StatusInternalServerError)
        return
    }
    
    // 傳送回應
    w.Header().Set("Content-Type", "application/json")
    w.Write(resp)
}

func main() {
    // 設定路由
    http.HandleFunc("/validate", handleValidate)
    
    // 啟動伺服器
    log.Println("啟動 Pod 安全策略 webhook 伺服器在 :8443...")
    err := http.ListenAndServeTLS(":8443", "/etc/webhook/certs/tls.crt", "/etc/webhook/certs/tls.key", nil)
    if err != nil {
        log.Fatalf("啟動伺服器失敗: %v", err)
    }
}

這個 webhook 實作了一個簡單的 Pod 安全策略,它會檢查以下安全問題:

  1. 特權容器 - 禁止使用 privileged: true
  2. 主機網路 - 禁止使用 hostNetwork: true
  3. 主機 PID 和 IPC - 禁止使用 hostPID: truehostIPC: true
  4. 危險的 capabilities - 禁止增加 SYS_ADMINNET_ADMIN 等敏感 capabilities

這個 webhook 還包含一個特殊處理:它會豁免 kube-system 名稱空間中的 Pod,因為系統元件通常需要這些特權。

這種實作方式比使用 Kubernetes 內建的 PodSecurityPolicy 更加靈活,因為它可以根據需要增加自定義的安全檢查邏輯。

Webhook 與其他 Kubernetes 擴充套件機制的比較

Kubernetes 提供了多種擴充套件機制,每種機制都有其優缺點:

擴充套件機制優點缺點適用場景
Webhook靈活性高,可以實作複雜的邏輯需要維護外部服務,可能影響 API 伺服器效能複雜的驗證和修改邏輯
CRD/Operator深度整合到 Kubernetes API開發複雜度高,需要較多資源管理複雜的應用生命週期
Aggregated API可以增加全新的 API實作複雜,需要深入瞭解 Kubernetes API增加全新的資源型別
Dynamic Admission Control與 Webhook 類別似,但更加標準化與 Webhook 相同標準化的准入控制

Webhook 是 Kubernetes 中一種強大而靈活的擴充套件機制,它允許叢集管理員實作自定義的驗證和修改邏輯,從而強制執行組織的政策和最佳實踐。透過 webhook,可以實作各種高階功能,如自動注入 sidecar、強制執行安全策略、資源驗證等。

在實作 webhook 時,需要特別注意安全性和效能問題,確保 webhook 不會成為系統的瓶頸或安全風險。透過適當的設計和最佳化,webhook 可以成為 Kubernetes 叢集管理的強大工具,幫助組織實作自動化和標準化的資源管理。

隨著 Kubernetes 生態系統的不斷發展,webhook 的應用場景也在不斷擴充套件,從簡單的資源驗證到複雜的自動化工作流程,webhook 都能發揮重要作用。掌握 webhook 的實作和使用方法,對於 Kubernetes 管理員和開發人員來說都是非常有價值的技能。