webhook证书轮换不能由kube-controller-manager自动处理,因其clientconfig.cabundle为静态base64字段,kubernetes不监听变更或自动重载;更新cabundle需手动重启apiserver(不可行),故须外部控制器监控过期、生成新证书、更新cabundle并同步webhook server证书。

Webhook证书轮换为什么不能靠 kube-controller-manager 自动处理
因为 ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 中的 clientConfig.caBundle 是静态 Base64 字符串,Kubernetes 不会主动监听证书变更或触发重载。即使你用 kubectl apply 更新了 caBundle,也得手动重启相关组件(比如 apiserver)才能生效——但实际中根本不能随便重启 apiserver。
所以真正的轮换必须由外部控制器完成,核心逻辑是:监控证书过期时间 → 生成新证书 → 更新 webhook 配置的 caBundle 字段 → 确保 webhook server 自身也切换到新证书。
用 cert-manager + 自定义 Issuer 实现自动签发和注入
cert-manager 是目前最稳妥的选择,但它默认不直接更新 caBundle。你需要组合使用:
- 一个
Certificate资源,指定secretName: webhook-tls,用于 webhook server 的服务端证书 - 一个
Secret类型为kubernetes.io/tls,由 cert-manager 自动填充tls.crt/tls.key - 一个
CAInjector(自定义控制器)或用cert-manager-webhook-ca-injector这类轻量工具,从webhook-tlsSecret 中提取 CA 证书,Base64 编码后 patch 到ValidatingWebhookConfiguration的clientConfig.caBundle
注意:cert-manager 的 Certificate 必须用 isCA: true 的 Issuer(比如 SelfSigned 或 CA 类型),否则签不出能当 CA 用的根证书;而 webhook server 实际使用的 TLS 证书必须由该 CA 签发,且 SAN 包含 service DNS 名(如 webhook-svc.webhook-ns.svc)。
Go 控制器怎么安全地 patch caBundle 字段
直接调用 Update 整体替换 ValidatingWebhookConfiguration 有风险:可能覆盖掉其他字段(比如 rules 或 namespaceSelector),尤其在多人协作或 CI/CD 频繁更新时容易出错。
推荐用 Patch + JSON Merge Patch 或 Strategic Merge Patch:
patchData := map[string]interface{}{
"clientConfig": map[string]interface{}{
"caBundle": base64.StdEncoding.EncodeToString(caPEM),
},
}
payload, _ := json.Marshal(patchData)
_, err := c.AdmissionregistrationV1().ValidatingWebhookConfigurations().
Patch(context.TODO(), "my-webhook", types.StrategicMergePatchType, payload, metav1.PatchOptions{})
关键点:
- 必须用
types.StrategicMergePatchType,不是types.MergePatchType,否则clientConfig下其他字段(如service)会被清空 - patch 前先做一次
Get,对比当前caBundle是否已是最新的,避免无谓的 etcd 写操作 - 务必加
ResourceVersion条件(用metav1.Preconditions{ResourceVersion: oldRV}),防止并发写冲突
Webhook server 自身如何热加载新证书
Go 里标准 http.Server 不支持运行时替换 TLS 证书。常见做法是监听 webhook-tls Secret 变更,然后优雅重启 server:
- 用
client-go的Informer监听corev1.Secret,过滤namespace和name - 每次 Secret 更新后,解析
tls.crt和tls.key,调用tls.X509KeyPair()验证是否有效 - 验证通过后,调用
server.Close()关闭旧 listener,再用新证书启动新http.Server(注意复用同一端口需设置SO_REUSEPORT或提前关闭)
更稳妥的做法是双 listener:旧证书 listener 继续服务存量连接,新证书 listener 启动后,等所有旧连接自然断开再彻底下线——但这需要你在 handler 层做连接生命周期管理,复杂度明显上升。
真正容易被忽略的是:证书轮换窗口期里,apiserver 和 webhook server 必须同时信任新旧两套 CA,否则会出现间歇性校验失败。这意味着你得在轮换流程中预留“双 CA 共存”阶段,并确保 caBundle 字段里只放当前生效的 CA(不能拼接多个),所以轮换顺序必须是:先更新 webhook server 证书 → 再更新 caBundle → 最后清理旧证书。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











