typora/daliy_note/10.9/kubernetes admission webhook.md
2024-12-12 10:48:55 +08:00

8.2 KiB
Raw Blame History

AdmissionWebhook 是 Kubernetes 中的一种动态准入控制器,用于在请求进入 Kubernetes API 服务器时对其进行修改或验证。准入控制器是 Kubernetes 安全模型的一部分,负责拦截请求并对其进行特定的检查或修改,以确保集群的安全性和策略的一致性。

AdmissionWebhook 的作用

  1. 验证Validating Webhooks

    • 这些 Webhook 在资源被创建、更新或删除之前对请求进行检查,以确保请求符合某些策略或规则。
    • 如果验证失败,请求将被拒绝,并返回给用户一个错误信息。
  2. 变更Mutating Webhooks

    • 这些 Webhook 可以在请求到达 API 服务器时修改请求对象。
    • 例如,可以自动填充某些字段,或者根据预定义的规则调整资源配置。

使用场景

  • 策略实施:确保所有部署的应用程序都符合公司或组织的安全和合规策略。例如,确保所有 Pod 都具有指定的标签或资源限制。
  • 自动化变更:自动为资源添加或修改某些属性,例如为每个 Pod 添加一个特定的环境变量。
  • 资源验证:在资源被持久化之前,对其进行额外的验证,以防止错误配置。
  • 审计和日志:记录和监控特定请求,以帮助进行审计和故障排查。

工作原理

  1. 注册 Webhook

    • 使用 MutatingWebhookConfigurationValidatingWebhookConfiguration 资源在 Kubernetes 中注册 Webhook。
  2. 处理请求

    • 当一个请求到达 Kubernetes API 服务器时,配置的 Webhook 会被调用。
    • Webhook 服务接收到请求对象,执行逻辑并返回一个响应,指示是否允许、拒绝或修改请求。
  3. 响应处理

    • 对于 Mutating WebhookAPI 服务器将根据 Webhook 的响应修改请求对象。
    • 对于 Validating WebhookAPI 服务器将检查 Webhook 的响应以决定是否执行请求。

实现示例

要实现一个 AdmissionWebhook通常需要以下步骤

  1. 编写 Webhook 服务器

    • 使用编程语言(例如 Go编写一个 HTTP 服务器,能够处理 Kubernetes 的 AdmissionReview 请求。
  2. 部署 Webhook 服务

    • 将 Webhook 服务器作为一个 Pod 部署到 Kubernetes 集群中。
  3. 创建 Webhook 配置

    • 编写并应用 MutatingWebhookConfigurationValidatingWebhookConfiguration,指向你的 Webhook 服务。

注意事项

  • 安全性:确保 Webhook 服务的安全性,使用 TLS 进行通信,确保只有经过身份验证的请求才能访问。
  • 性能影响Webhook 会影响 API 请求的延迟,因为请求需要经过 Webhook 的处理。尽量优化 Webhook 的处理逻辑以减少开销。
  • 故障处理:设计 Webhook 时需要考虑故障处理,确保 Webhook 不会因为某些错误而导致集群无法正常工作。

通过使用 AdmissionWebhookKubernetes 提供了一个强大的机制来实施自定义策略和自动化配置,帮助运维人员更好地管理和控制集群行为。

举例

当然!下面是一个简单的示例,展示如何实现一个 Kubernetes Admission Webhook。这个示例将创建一个简单的 Mutating Admission Webhook它会在每个 Pod 创建之前向其中添加一个环境变量。

步骤 1: 编写 Webhook 服务器

我们将用 Go 编写一个简单的 HTTP 服务器,作为我们的 Webhook 服务。

package main

import (
    "encoding/json"
    "net/http"
    "log"

    admissionv1 "k8s.io/api/admission/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    corev1 "k8s.io/api/core/v1"
)

func handleAdmission(w http.ResponseWriter, r *http.Request) {
    var admissionReviewReq admissionv1.AdmissionReview
    if err := json.NewDecoder(r.Body).Decode(&admissionReviewReq); err != nil {
        log.Printf("Could not decode request: %v", err)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
    if admissionReviewReq.Request.Resource != podResource {
        log.Printf("Expect resource to be %s", podResource)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    raw := admissionReviewReq.Request.Object.Raw
    pod := corev1.Pod{}
    if err := json.Unmarshal(raw, &pod); err != nil {
        log.Printf("Could not unmarshal raw object: %v", err)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    // 添加环境变量
    env := corev1.EnvVar{Name: "ADDED_BY_WEBHOOK", Value: "true"}
    pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, env)

    // 创建补丁操作
    patchBytes, err := json.Marshal([]map[string]interface{}{
        {"op": "add", "path": "/spec/containers/0/env", "value": pod.Spec.Containers[0].Env},
    })
    if err != nil {
        log.Printf("Could not marshal patch: %v", err)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    admissionReviewResponse := admissionv1.AdmissionReview{
        Response: &admissionv1.AdmissionResponse{
            UID:     admissionReviewReq.Request.UID,
            Allowed: true,
            Patch:   patchBytes,
            PatchType: func() *admissionv1.PatchType {
                pt := admissionv1.PatchTypeJSONPatch
                return &pt
            }(),
        },
    }

    respBytes, err := json.Marshal(admissionReviewResponse)
    if err != nil {
        log.Printf("Could not marshal response: %v", err)
        http.Error(w, "Bad request", http.StatusBadRequest)
        return
    }

    w.Header().Set("Content-Type", "application/json")
    w.Write(respBytes)
}

func main() {
    http.HandleFunc("/mutate", handleAdmission)
    log.Println("Starting server on :8080")
    if err := http.ListenAndServeTLS(":8080", "/path/to/tls.crt", "/path/to/tls.key", nil); err != nil {
        log.Fatalf("Failed to start server: %v", err)
    }
}

步骤 2: 部署 Webhook 服务

  1. 编译和打包服务

    • 将上述代码编译为二进制文件,创建一个 Docker 镜像,并将其推送到容器镜像仓库。
  2. 创建 Kubernetes Deployment 和 Service 编写一个 YAML 文件,将 Webhook 服务部署到 Kubernetes 集群中。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: webhook-server
  template:
    metadata:
      labels:
        app: webhook-server
    spec:
      containers:
      - name: webhook-server
        image: <your-docker-image>
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: webhook-certs
          mountPath: "/etc/webhook/certs"
          readOnly: true
      volumes:
      - name: webhook-certs
        secret:
          secretName: webhook-certs
---
apiVersion: v1
kind: Service
metadata:
  name: webhook-server
spec:
  ports:
  - port: 443
    targetPort: 8080
  selector:
    app: webhook-server

步骤 3: 创建 Webhook 配置

  1. 创建 TLS 证书

    • 为 Webhook 服务创建 TLS 证书,并将其存储在 Kubernetes Secret 中。
  2. 创建 MutatingWebhookConfiguration 编写一个 YAML 文件,为 Kubernetes 注册 Webhook。

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: pod-mutator
webhooks:
  - name: pod-mutator.example.com
    clientConfig:
      service:
        name: webhook-server
        namespace: default
        path: "/mutate"
      caBundle: <base64-encoded-ca-cert>
    rules:
      - operations: ["CREATE"]
        apiGroups: [""]
        apiVersions: ["v1"]
        resources: ["pods"]
    admissionReviewVersions: ["v1"]
    sideEffects: None

注意事项

  • 确保 Webhook 服务的 TLS 证书正确配置,并与 MutatingWebhookConfiguration 中的 caBundle 匹配。
  • 此示例只是一个简单的实现,生产环境中需要考虑安全性、性能优化和错误处理等方面。
  • caBundle 应该是你的 CA 证书的 base64 编码。

通过上述步骤,你可以实现一个简单的 Admission Webhook它在 Pod 创建时自动添加一个环境变量。这个示例可以作为实现更复杂逻辑的起点。