侧边栏壁纸
博主头像
汪洋

即使慢,驰而不息,纵会落后,纵会失败,但一定可以达到他所向的目标。 - 鲁迅

  • 累计撰写 223 篇文章
  • 累计创建 84 个标签
  • 累计收到 270 条评论

Kubernetes - Validating Webhook

汪洋
2025-08-19 / 0 评论 / 0 点赞 / 40 阅读 / 6,914 字

继上篇 Kubernetes - webhook mutating 之后,另一个 webhook 机制今日花时间把它整理出来 - Validating Webhook

一、Validating Webhook 核心逻辑

Validating Webhook 不修改资源,仅验证资源是否符合规则。若不符合,返回 allowed: false 并给出拒绝原因。

二、实现

1、golang 逻辑

package main

import (
	"encoding/json"
	"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/types"
)

// 必须包含的标签
var requiredLabels = []string{"app", "environment"}

func main() {
	http.HandleFunc("/validate", validateHandler)
	log.Println("Starting validating webhook server on :443")

	// 生产环境需使用合法 TLS 证书
	if err := http.ListenAndServeTLS(":443", "tls.crt", "tls.key", nil); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

func validateHandler(w http.ResponseWriter, r *http.Request) {
	var review admissionv1.AdmissionReview
	if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
		log.Printf("Error decoding request: %v", err)
		http.Error(w, "Invalid request body", http.StatusBadRequest)
		return
	}

	// 解析 Pod 对象
	pod := &corev1.Pod{}
	if err := json.Unmarshal(review.Request.Object.Raw, pod); err != nil {
		log.Printf("Error unmarshalling pod: %v", err)
		sendResponse(w, review.Request.UID, false, "Failed to parse Pod object")
		return
	}

	// 验证标签是否存在
	missingLabels := checkRequiredLabels(pod)
	if len(missingLabels) > 0 {
		msg := "Pod is missing required labels: " + strings.Join(missingLabels, ", ")
		sendResponse(w, review.Request.UID, false, msg)
		return
	}

	// 验证通过
	sendResponse(w, review.Request.UID, true, "")
}

// 检查 Pod 是否包含所有必需的标签
func checkRequiredLabels(pod *corev1.Pod) []string {
	var missing []string
	for _, label := range requiredLabels {
		if _, exists := pod.ObjectMeta.Labels[label]; !exists {
			missing = append(missing, label)
		}
	}
	return missing
}

// 发送验证响应
func sendResponse(w http.ResponseWriter, uid types.UID, allowed bool, message string) {
	response := admissionv1.AdmissionReview{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "admission.k8s.io/v1",
			Kind:       "AdmissionReview",
		},
		Response: &admissionv1.AdmissionResponse{
			UID:     uid,
			Allowed: allowed,
		},
	}

	// 若拒绝,添加提示信息
	if !allowed && message != "" {
		response.Response.Result = &metav1.Status{
			Message: message,
		}
	}

	w.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(w).Encode(response); err != nil {
		log.Printf("Error encoding response: %v", err)
		http.Error(w, "Error sending response", http.StatusInternalServerError)
	}
}

2、镜像封装

FROM docker.cnb.cool/wangyanglinux/docker-images-chrom/alpine:3.17

WORKDIR /root/

# 从构建阶段复制二进制文件

COPY ./validating /root

RUN mkdir  /etc/tls

# 暴露端口
EXPOSE 443

# 运行应用
CMD ["./validating"]

本项目已上传至阿里云镜像仓库,镜像地址为:registry.cn-hangzhou.aliyuncs.com/wangyangshare/share:validating

3、生成包含 SAN 的 TLS 证书

创建 openssl.cnf 配置文件(确保 SAN 正确)

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[dn]
CN = pod-validation-svc.default.svc

[req_ext]
subjectAltName = DNS:pod-validation-svc.default.svc

生成证书

# 生成 CA 和服务器证书
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 3650 -out ca.crt -subj "/CN=validation-ca"
openssl genrsa -out tls.key 2048
openssl req -new -key tls.key -out tls.csr -config openssl.cnf
openssl x509 -req -in tls.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out tls.crt -days 3650 -extensions req_ext -extfile openssl.cnf

创建 Secret 存储证书

kubectl create secret tls pod-validation-tls --cert=tls.crt --key=tls.key

4、Deployment 和 Service 配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-validation-webhook
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pod-validation-webhook
  template:
    metadata:
      labels:
        app: pod-validation-webhook
    spec:
      containers:
      - name: webhook
        image: registry.cn-hangzhou.aliyuncs.com/wangyangshare/share:validating
        ports:
        - containerPort: 443
        volumeMounts:
        - name: tls-cert
          mountPath: /etc/tls
          readOnly: true
      volumes:
      - name: tls-cert
        secret:
          secretName: pod-validation-tls
---
apiVersion: v1
kind: Service
metadata:
  name: pod-validation-svc
  namespace: default
spec:
  selector:
    app: pod-validation-webhook
  ports:
  - port: 443
    targetPort: 443

5、ValidatingWebhookConfiguration 配置

创建 validating-webhook-config.yaml(注意替换 caBundle)

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-validation-webhook
webhooks:
- name: pod-validation.default.svc
  clientConfig:
    service:
      name: pod-validation-svc
      namespace: default
      path: "/validate"
    caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURBekNDQWV1Z0F3SUJBZ0lKQU91N1YzZm9jQkd0TUEwR0NTcUdTSWIzRFFFQkN3VUFNQmd4RmpBVUJnTlYKQkFNTURYWmhiR2xrWVhScGIyNHRZMkV3SGhjTk1qVXdPREU1TURZMU9EUXhXaGNOTXpVd09ERTNNRFkxT0RReApXakFZTVJZd0ZBWURWUVFEREExMllXeHBaR0YwYVc5dUxXTmhNSUlCSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DCkFROEFNSUlCQ2dLQ0FRRUEzRkZ5OEY2Z2RZYXNHZElRMERlWkRCSGJSV0tkTmRsZHdJREpxZjJHVXZVOUtnd3gKV1VicEZCV0t0MjdyekpCSWVFU3lHZ2kreEl1dCtuR2w0T2dRU0g3V1V3U2VsTUJXKzdtaWk3RkdjWlpuQzJMQQo4amU4V3cxdmRKZVZNSmc4ZmkxeDBWYXhZUXF6bGIxbjlkMk5nUUtJQjdIZlJyRVB0Z1RXTklmQ3pWby9tZlhvCjRlNUhZYldPSTArTVd0ekFQQ1VlUkZzcXZRb1k1RkhGVTB5SUQwcWhvNTFENjR3ZkVwNUlSTXB0NjJIbzRwTVcKNzJ1dXIvNXI0RFRIdnRFSU5XS25ydzF1MWNETllkUFNGRTlQR3ZYaElJMEltbWhQTEJJMklNeUlXTGswNnlWdAorQnRKVThBSjExOUUrTUY3VUtzSGExeERUd211cWdhNStMSnl6d0lEQVFBQm8xQXdUakFkQmdOVkhRNEVGZ1FVCjFYT1JiOTh0U3Jpc3RiM0VIVGkwdWZJVGw5VXdId1lEVlIwakJCZ3dGb0FVMVhPUmI5OHRTcmlzdGIzRUhUaTAKdWZJVGw5VXdEQVlEVlIwVEJBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWxZTnlWb3pXU3RqaAo2Z09qU0NjRk82dFAzK245SCtLc01vTHNxZThuRDQvOUZEYkMraDlodit1Q2l0dHZjbS9MSjBobXd1dnpWSFVICm1oV0YrV2RhNWFwZXlmYnJGS1g5aTNlbzBBbWhRSEdEV2tjNHdkMGtEbDhhY3VqVkJod0twM3BNQktldFZ3aUUKcW5OaFBSMUJUN3hVU2FwcXN5Sk5scGpGZzlXejZtSVIzRzN1S2F5d3FtNE5ZanE4bWlqUUZjRWQrUlRhNDVQWgpla2xETytiNndmeEdCb01pcy9BeDcrZXYrTHhTZDRXemZoUktIM21YZHIxc2cwWFcybU9zc2tiQ0RIczlmekxYCkRIRkt4N0tVNHBaMnluMS9oT0dkbnQvMFFPQld4NkFpUW1MNFVYcERmVWtKYXJqc1JQazNCenBFZDFvL1NjTE4KMDBGNzBZWmRmUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
  rules:
  - operations: ["CREATE", "UPDATE"]  # 对创建和更新操作进行验证
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  failurePolicy: Fail  # Webhook 调用失败时拒绝请求
  sideEffects: None
  admissionReviewVersions: ["v1"]
  timeoutSeconds: 5

其中 caBundle 替换为自己的 cat ca.crt | base64 -w 0 的输出

三、测试验证效果

创建一个缺少标签的 Pod(应被拒绝)

apiVersion: v1
kind: Pod
metadata:
  name: test-invalid-pod
spec:
  containers:
  - name: nginx
    image: docker.cnb.cool/wangyanglinux/docker-images-chrom/myapp:v4.0

执行 kubectl apply -f test-pod.yaml 后,会收到类似以下错误:

Error from server: error when creating "test-pod.yaml": admission webhook "pod-validation.default.svc" denied the request: Pod is missing required labels: app, environment

创建一个包含所有标签的 Pod(应通过验证):

apiVersion: v1
kind: Pod
metadata:
  name: test-valid-pod
  labels:
    app: nginx
    environment: prod
spec:
  containers:
  - name: nginx
    image: docker.cnb.cool/wangyanglinux/docker-images-chrom/myapp:v4.0

执行 kubectl apply -f test-pod.yaml 后,Pod 会正常创建。

四、核心说明

  • 验证逻辑:示例中验证 Pod 必须包含 app 和 environment 标签,你可以根据需求修改 requiredLabels 和 checkRequiredLabels 函数(例如:禁止特权容器、检查资源限制等)。
  • 触发时机:通过 rules 配置触发验证的资源(如 pods)和操作(CREATE/UPDATE)。
  • 安全性:依赖 TLS 证书确保通信安全,caBundle 必须正确配置为 CA 证书的 base64 编码。

通过这种方式,你可以自定义各种验证规则,强制集群资源符合团队规范。

0

评论区