bigywater.tech
Kubernetes · 8

准入 Webhook:mutating 与 validating 的调用顺序和失败策略

准入 webhook 是挂在 API Server 请求链上的外部函数:谁先谁后?服务挂了集群还能不能创建对象?慢了会怎样?这篇自己写一个 HTTPS webhook 服务放进集群,让每个请求打一行日志,把调用顺序、failurePolicy、超时和各种“不调用”的情形都数了出来。

a → b → validate
两个变更 webhook 与一个校验 webhook 的实际调用顺序(b-second 是先创建的配置)
connection refused
服务缩到 0、failurePolicy=Fail 时创建对象被拒绝的原因,只花了几十毫秒
1.11 s
timeoutSeconds=1、服务端睡 4 秒时,请求失败或放行的时刻

调用顺序

实测 · 本机执行输出
  配置创建顺序:b-second(Mutating)→ a-first(Mutating)→ v-check(Validating);创建 ConfigMap c1:退出码 0,耗时 86 ms
    webhook 收到第 1 个请求:/mutate/a   操作=CREATE 看到的 order 注解=None
    webhook 收到第 2 个请求:/mutate/b   操作=CREATE 看到的 order 注解='a'
    webhook 收到第 3 个请求:/validate   操作=CREATE 看到的 order 注解='a,b'
  c1 最终的 order 注解:'a,b'
  创建带 deny=true 的 c2:退出码 1;Error from server: error when creating "STDIN": admission webhook "v.k2.example.com" denied the request: validate webhook 拒绝:对象带有 deny=true 标签(它看到的 order 注解是 'a,b')
  kubectl get cm c2:Error from server (NotFound): configmaps "c2" not found

先创建配置 b-second,再创建 a-first,服务端日志里 /mutate/a 先于 /mutate/b,最后才是 /validate:变更 webhook 是串行的,后一个看到的是前一个改过的对象(b 看到的 order 注解已经是 a);校验 webhook 看到的是最终对象(a,b)。这次的顺序与创建先后无关,表现为按配置对象名字的字母顺序。

校验 webhook 拒绝时,报错是 admission webhook "v.k2.example.com" denied the request: 加上服务自己返回的 message;对象没有被创建。

服务不可用时

实测 · 本机执行输出
  服务正常,failurePolicy=Fail:退出码 0;耗时 176 ms;configmap/p0 created
  服务停掉,failurePolicy=Fail:退出码 1;耗时 86 ms;Error from server (InternalError): error when creating "STDIN": Internal error occurred: failed calling webhook "fpf.k2.example.com": failed to call webhook: Post "https://hook.k2.svc:443/validate?timeout=10s": dial tcp 10.96.122.136:443: connect: connection refused
  服务停掉,failurePolicy=Ignore:退出码 0;耗时 75 ms;configmap/p2 created
  kubectl get cm -n k2-app p1 p2:NAME   DATA   AGE | p2     1      0s | Error from server (NotFound): configmaps "p1" not found

把服务缩到 0 个副本(等到旧 Pod 真正消失)再创建对象:failurePolicy: Fail 的 webhook 让请求失败,Ignore 的则放行。失败的报错是 dial tcp 10.96.122.136:443: connect: connection refused——Service 的 ClusterIP 没有后端,连接立刻被拒,所以几十毫秒就返回。要注意缩容到 0 之后旧 Pod 如果还在收尾,apiserver 复用的长连接仍会把请求送过去,所以实验里要等 Pod 消失再测。

集群里任何会被 Fail 的 webhook 命中的对象,在它的服务挂掉时都创建不了——包括 webhook 自己的 Pod,这是配置 webhook 时最常见的“把集群锁死”的原因。

太慢了会怎样

实测 · 本机执行输出
  timeoutSeconds=1,Fail:退出码 1;耗时 1.11 s;Error from server (InternalError): error when creating "STDIN": Internal error occurred: failed calling webhook "t1.k2.example.com": failed to call webhook: Post "https://hook.k2.svc:443/slow?timeout=1s": context deadline exceeded
  timeoutSeconds=1,Ignore:退出码 0;耗时 1.11 s;configmap/s2 created
  timeoutSeconds=10,Fail:退出码 0;耗时 4.13 s;configmap/s3 created

服务端每次睡 4 秒:timeoutSeconds: 1 时 1.11 秒就放弃,报 failed calling webhook … context deadline exceeded;这时 Fail 拒绝请求,Ignore 放行。timeoutSeconds: 10 则老老实实等了 4.13 秒。超时之后服务端其实还在处理,如果它有副作用就已经发生了,所以带副作用的 webhook 要自己保证幂等。

哪些请求不会调用

实测 · 本机执行输出
  在 k2-app 创建普通 ConfigMap(规则命中):webhook 被调用 1 次(dryRun=False)
  在没有 k2-test 标签的 k2-other 创建(namespaceSelector 不命中):webhook 被调用 0 次
  创建带 skip 标签的对象(objectSelector 不命中):webhook 被调用 0 次
  创建名字以 skip- 开头的对象(matchConditions 的 CEL 为 false):webhook 被调用 0 次
  更新已有对象 n1(规则里只有 CREATE):webhook 被调用 0 次
  --dry-run=server 创建(sideEffects: None):webhook 被调用 1 次(dryRun=True)

用服务端日志数出的调用次数:规则命中的普通创建 1 次;namespaceSelector 不命中的命名空间 0 次;objectSelector 不命中 0 次;matchConditions 的 CEL 表达式为 false 0 次;规则里只写了 CREATE 时更新对象 0 次。--dry-run=server 照样调用(sideEffects: None),日志里 dryRun 为 True——服务端必须不产生副作用,或者把 dry-run 识别出来。

分步演示

准入 webhook
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);webhook 服务是 python:3.12-alpine 里的标准库 HTTPS 服务(openssl 生成的临时 CA 和服务端证书,SAN 为 hook.k2.svc),以 Deployment + Service 部署在 k2 命名空间,按 URL 路径决定行为(/mutate/ 往 order 注解里追加标签,/validate 拒绝带 deny=true 的对象,/slow 睡 4 秒),每个请求打一行 JSON 日志;所有 webhook 都用 namespaceSelector 限定在带 k2-test=true 标签的 k2-app 命名空间,实验后全部删除;每种情形各一次,耗时是 kubectl 命令的整体耗时
    • 没有验证:变更 webhook 的顺序规则(官方不保证顺序,这里只观察到 a 先于 b,没有测同一个配置对象里多个 webhook 的先后);reinvocationPolicy(变更后再次调用);对 Pod、CRD 或 apiserver 自己的资源配置 webhook 的影响;URL 方式(clientConfig.url)的 webhook;webhook 返回 warnings 和 auditAnnotations;超时之后服务端是否继续执行
    京ICP备2026067502号-1京公网安备11010502063487号