继上篇 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 编码。
通过这种方式,你可以自定义各种验证规则,强制集群资源符合团队规范。
评论区