必须使用 admissionv1.AdmissionReview(v1beta1 已废弃),Webhook 必须启用 HTTPS,策略应外部化配置,删除操作需支持绕过机制,日志打印前须重置 request body。

AdmissionReview 必须用 v1 版本,v1beta1 已彻底失效
如果你的 Webhook 服务启动后收不到任何请求,或者日志里出现 json: cannot unmarshal object into Go struct field AdmissionReview.request,八成是还在用已废弃的 admissionv1beta1.AdmissionReview。Kubernetes 从 1.26 起已完全移除 v1beta1 版本,API Server 直接拒绝解析该格式的 body。
必须做三件事:
- 导入正确包:
admissionv1 "k8s.io/api/admission/v1",不是admissionv1beta1 - 定义 handler 时,用
admissionv1.AdmissionReview解析整个请求体 - 解析前先检查
review.Request.Kind.Group和review.Request.Kind.Version,对未知类型直接返回Allowed: true避免 panic
Webhook 必须走 HTTPS,HTTP 会被 API Server 静默丢弃
哪怕本地 Minikube 或 kind 集群测试,也不能用 http.ListenAndServe。API Server 强制校验 TLS,一旦发现非 HTTPS endpoint,根本不会发请求——你看到的 Failed to connect to webhook 或 admission webhook "xxx" does not support the version 往往是表象,真实原因是连接被拒绝前连 request 都没发出。
关键配置点:
立即学习“go语言免费学习笔记(深入)”;
- Go 服务启动必须用
http.ListenAndServeTLS(":443", "/etc/webhook/certs/tls.crt", "/etc/webhook/certs/tls.key") -
clientConfig.caBundle字段填的是 Base64 编码后的 CA 证书 PEM 内容(不是路径、不是原始文本) - 证书必须由集群信任的 CA 签发;自签证书需提前注入到 kube-apiserver 的
--client-ca-file或通过ca.crt挂载进 Pod
策略逻辑不能硬编码,否则等于放弃 Webhook 的动态性
把“禁止特权容器”写成 if pod.Spec.Containers[i].SecurityContext.Privileged == true { return deny() },看似能跑,但每次策略变更都要改代码、构建镜像、滚动更新——这违背了准入控制“策略即配置”的设计初衷。
更可持续的做法:
- Webhook 只做协议适配和路由分发,具体判断交给外部引擎(如 OPA + Rego)
- 若坚持纯 Go 实现,至少把规则抽成 JSON/YAML,挂载为 ConfigMap,并用
fsnotify监听热重载 - 确保 Webhook Pod 的 ServiceAccount 有权限读取对应命名空间下的
configmaps(否则规则加载失败且无提示)
删除类操作需绕过机制,否则无法清理自身资源
当你要删一个被自己 Webhook 拦截的资源(比如带 deny-all=true annotation 的 Pod),若没设计绕过逻辑,会陷入死锁:Webhook 拒绝删除请求 → 资源删不掉 → Webhook 一直运行 → 永远删不掉。
标准解法是检查 annotation:
- 在 handler 开头加判断:
if pod.Annotations["webhook.example.com/skip"] == "true" { return allow() } - 删除命令带上 annotation:
kubectl delete pod xxx -o yaml | sed 's/annotations:/annotations:\n webhook.example.com\/skip: "true"/' | kubectl apply -f - - 注意 RBAC:你的 Webhook 本身可能需要
get权限读取资源 annotation,否则解析阶段就 panic
最易被忽略的一点:所有日志中打印 review.Request.Object.Raw 前,必须先用 io.ReadAll(r.Body) 读一次并重建 bytes.NewReader(),否则 client-go 后续解码会报 body closed——这个错误不抛异常,只让请求静默失败。


















