Admission Webhook 链冲突排查
问题场景
一个 Pod 创建失败,报错却指向 Gatekeeper。
业务同学第一反应是合规策略太严;平台同学去看 Kyverno,发现镜像策略也命中了;再往前翻,Istio sidecar 已经注入过一轮。
最后发现:不是某一个组件坏了,而是 Istio、Kyverno、Gatekeeper 三条 Admission Webhook 链叠在一起,把 Pod 对象改成了策略无法接受的形态。
请求到底经过了什么
一个创建 Pod 的请求进入 kube-apiserver 后,经过两段 Admission:

用户 YAML → Mutating Webhooks(按字母序依次执行)→ Validating Webhooks(看到的是所有 Mutation 之后的最终对象)→ etcd
关键认知:Gatekeeper(Validating)拒绝的不是业务原始 YAML,而是已经被 Istio、Kyverno 改过的对象。
三个组件分别动哪里
| 组件 | Webhook 类型 | 修改内容 |
|---|---|---|
Istio istio-sidecar-injector |
Mutating | 加 istio-init、istio-proxy 容器,修改 annotations、volumes、volumeMounts、securityContext |
| Kyverno | Mutating + Validating | 改镜像仓库、加 imagePullSecrets、补资源限制、打标签;也做策略校验 |
| Gatekeeper | 主要 Validating(开启 mutation 后也可 Mutating) | 通过 Assign/AssignMetadata/ModifySet 改对象;ConstraintTemplate 做准入校验 |
一个 Pod 在真正写入 etcd 前,可能已经被改了很多次。问题不在于"谁改了对象",而在于谁先改、改了哪个路径、后一个组件是否假设对象结构没变。
坑 1:JSON Patch 的路径太脆
[{ "op": "replace", "path": "/spec/containers/0/image", "value": "registry.k8s.io/pause:3.9" }]
/spec/containers/0 是一个很脆的假设。如果前面某个 webhook 改了 containers 数组(Istio 追加了 istio-proxy),后面的 patch 仍然按固定 index 修改,就可能改错容器。
稳妥做法:
- 镜像策略按 container name 或镜像字段匹配,不依赖数组下标
- 对注入容器设置明确的排除规则
- 把最终 Pod 对象作为校验对象,而不是原始 YAML
坑 2:annotations 被整体覆盖
Istio 写入 sidecar.istio.io/status,Kyverno 写入策略处理注解。如果某个 webhook 用 replace 整个 annotations 对象而不是 add 单个 key:
// ❌ 危险:替换整个 annotations,清掉其他组件写入的内容
[{ "op": "replace", "path": "/metadata/annotations", "value": { "team": "platform" } }]
// ✅ 安全:只改自己负责的 key
[{ "op": "add", "path": "/metadata/annotations/team", "value": "platform" }]
红线:Webhook 只应该管理自己负责的字段。
坑 3:Gatekeeper 拒绝的是"最终对象"
典型故障表现:
- 不开 Istio 注入,Pod 正常创建
- 开启 namespace label istio-injection=enabled 后,Pod 全部失败
- 错误信息指向 Gatekeeper
- Kyverno 和 Istio 日志里看不出明显异常
根因:Gatekeeper 策略要求所有容器镜像必须来自企业私有仓库,但 istio-proxy 使用的是 docker.io/istio/proxyv2,策略没有豁免,就拒绝了所有开启 sidecar 的工作负载。
解决方向:
- 排除 istio-system 命名空间
- 对 istio-proxy 单独放行
- 对 Mesh 运行时镜像仓库设置白名单
- 对业务容器和平台注入容器使用不同约束
合规策略不能只看"所有 containers"。在服务网格集群里,它必须区分业务容器和平台容器。
排查流程
第一步:列出所有 Webhook 配置
kubectl get mutatingwebhookconfigurations
kubectl get validatingwebhookconfigurations
# 查看关键字段
kubectl get mutatingwebhookconfigurations -o json \
| jq '.items[] | {
name: .metadata.name,
webhooks: [.webhooks[] | {
name, failurePolicy, reinvocationPolicy,
namespaceSelector, objectSelector
}]
}'
重点看四个字段:
- name:影响链路里的相对顺序
- namespaceSelector:是否误匹配了系统命名空间
- objectSelector:是否只作用在必要对象上
- failurePolicy:组件异常时是 Fail 还是 Ignore
第二步:用审计日志追踪谁改了对象
grep '"resource":"pods"' /var/log/kubernetes/audit.log \
| grep '"verb":"create"' \
| grep 'admission'
排查时建议记录三份对象:
1. 用户提交的原始 YAML
2. Mutating 之后的最终 Pod
3. Validating 拒绝时的错误信息
第三步:隔离复现
kubectl create namespace webhook-lab
kubectl label namespace webhook-lab istio-injection=enabled
# 逐步开启组件,每加一层都抓一次最终对象
# 1. 只开 Istio 注入 → 确认 Pod 能创建
# 2. 打开 Kyverno mutation → 看最终镜像和 annotations
# 3. 打开 Gatekeeper validation → 看是否被拒绝
# 4. 再打开 Gatekeeper mutation(如果集群使用了)
kubectl get pod -n webhook-lab -o yaml
reinvocationPolicy:谨慎使用
Kubernetes 支持 reinvocationPolicy: IfNeeded——如果后面的 Mutating Webhook 修改了对象,前面已执行过的 Webhook 有机会被重新调用。
当 Istio、Kyverno、Gatekeeper mutation 都存在时,重新调用可能让对象经历第二轮修改,最终对象更难预测。
开启条件:
- mutation 必须是幂等的
- patch 只作用于自己负责的字段
否则排查成本翻倍。
failurePolicy 分级策略
很多组件默认 failurePolicy: Fail。安全上没错,但在生产集群里全 Fail 容易造成连锁故障(webhook service 网络不通、证书过期、endpoint 缺失都会导致 Pod 创建失败)。
# 检查 failurePolicy
kubectl get validatingwebhookconfigurations -o json \
| jq '.items[] | {config: .metadata.name, webhooks: [.webhooks[] | {name, failurePolicy}]}'
分级建议:
| 场景 | 建议 failurePolicy |
|---|---|
| 强安全边界(镜像准入、特权容器拦截) | Fail |
| 辅助标签、默认值、非关键增强 | Ignore |
| kube-system、istio-system 等系统命名空间 | 明确排除 |
原则:Fail 的 Webhook 必须具备生产级可用性(多副本、PDB、证书过期告警)。
编排规范(六条铁律)
- namespaceSelector 必须精确:业务策略不扫 kube-system,实验策略不扫全集群
- Mutation 只改自己的字段:不 replace 整个 annotations/labels/containers/volumes,能 add 单 key 就不覆盖整个 map
- 策略里必须识别平台注入容器:
istio-proxy、安全 Agent、日志 sidecar 不套业务容器的镜像规则 - 数组字段不依赖固定 index:containers、initContainers、volumeMounts 基于 name 匹配
- Webhook 配置进入变更流程:新增 Webhook 像变更网络策略/RBAC 一样经过评审(匹配哪些资源?改哪些字段?failurePolicy?影响系统命名空间吗?和现有 Webhook 有字段重叠吗?)
- 审计日志保留足够长:Admission 问题经常是偶发的,没有 audit log 很难还原
排查清单(10 步速查)
- 看报错来自 Mutating 还是 Validating
- 列出所有 MutatingWebhookConfiguration
- 列出所有 ValidatingWebhookConfiguration
- 检查 namespaceSelector 和 objectSelector
- 查看 failurePolicy 和 webhook service endpoints
- 在隔离 namespace 逐个开启组件复现
- 对比原始 YAML 和最终 Pod YAML
- 查 audit log 里的 patch 和拒绝原因
- 检查策略是否误伤
istio-proxy - 检查是否有 replace 整个 annotations/labels/containers 的行为
核心认知:Gatekeeper 拒绝的不是原始 Pod,而是所有 Mutating Webhook 改完之后的 Pod。