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