LabHub
学习 学习路径 课程

KCA — Kyverno 认证助理

策略还在,Webhook 却消失了

在 LabHub 中继续学习

一句话总结

策略文件仍然存在、Webhook 已在 API 服务器中注册,以及该 Webhook 能够响应,是三个不同的事实。在 Webhook 故障实验中,必须分别观察这三项。

概念图: 一句话总结 · 为什么需要它 · 它如何工作 · 命名空间就是实验范围

为什么需要它

部署负责人说:“安全策略设置为 Fail,但关闭引擎后,违规 Pod 还是创建成功了。”能否立即断定这是 Kubernetes 缺陷?控制器正常关闭时,调用注册可能已经被移除。如果根本没有可调用的 Webhook,就没有真正测试 failurePolicy 如何处理该 Webhook 的调用失败。

2026-09-12,我们也在 LabHub 专用验证 VM 中观察到这种情况。把控制器缩容到 replicas=0 并确认 Pod 消失后,请求仍然通过。当时保存的 ValidatingWebhookConfiguration 列表中没有对应的资源 Webhook。我们没有把它作为“Fail 已失效”的证据。后来,在保留调用注册的情况下,只短暂停止同一个控制器进程的响应,匹配请求在 3 秒后因超时被拒绝。正常关闭与停止响应不是同一种实验。

它如何工作

本单元讨论在 Kyverno v1.19.1 与 k3s v1.35.8+k3s1 上复现的现象。不要将其概括为其他版本或安装选项在正常关闭时也一定会删除 Webhook。迁移实验时,必须重新确认安装版本和实际注册状态。

首先区分三项设置。

Ignore 并不表示“不验证”。如果服务器正常判断出违规并返回拒绝响应,该拒绝仍然有效。因此,进行 Ignore 实验时,也必须先向正常服务器发送违规请求,确认它会被拒绝。没有这条基线,只看到故障期间请求成功,就无法区分策略是否从一开始便未生效。

Kyverno ValidatingPolicy 官方说明介绍了策略字段和自动生成行为。该 API 为 policies.kyverno.io/v1。不要原样搬用旧式 ClusterPolicy 示例中的字段位置,应对照已安装的 CRD 与 kubectl explain。通过 Kubernetes 自身 ValidatingAdmissionPolicy 执行的路径也彼此独立,不要把它混入引擎进程停止实验。

命名空间就是实验范围

在目标命名空间中应用一条要求 Pod 具有 environment 标签的创建规则,对照命名空间则放在选择范围之外。namespaceSelector 选择的是命名空间标签,而不是 Pod 标签。Pod 上的 environment 与用于选择命名空间的 kubernetes.io/metadata.name 属于不同层次。

只阅读策略声明并不够。还要在生成的 Webhook 中检查真实 service 路径、failurePolicy、timeoutSeconds、namespaceSelector 和 CREATE pods 规则。如果存在额外 objectSelector 或 matchConditions,请求可能绕过验证,因此也要一并检查。范围外的正常行为是对照组,而不是范围内未发生故障的证据。

Kubernetes 动态准入官方文档区分了请求匹配与 Webhook 调用失败处理。准确说法不是“所有创建请求都会被阻止”,而是“与该 Webhook 匹配的请求会因调用失败而被阻止”。

两个相同请求与一个对照组

每次实验都要使用新的 Pod 名称。复用已有名称会产生 AlreadyExists,并与策略拒绝或超时混淆。除了 API 响应,还要查询并记录对象是否真实保存以及 UID。不要仅凭一行成功消息推断对象已保存。

条件 目标范围内带正常标签的请求 目标范围内的违规请求 范围外的违规请求
Webhook 正常,Deny 保存 明确的策略拒绝 单独检查范围
保留注册、停止响应,Fail 超时且未保存 超时且未保存 保存
保留注册、停止响应,Ignore 等待时限后保存 等待时限后保存 保存
Webhook 恢复 单独确认现有 Pod 再次明确拒绝 按恢复范围检查

表格中的两个故障行是在上述专用 VM 中观察到的结果。我们为每次请求保存了原始错误。Fail 错误来自调用相应 Webhook 时发生 context deadline exceeded;正常违规错误则由相应策略返回 denied the request。两者在终端里都像失败,但原因不同。DNS 错误或 connection refused 又属于其他故障类型,不能用本次仅停止响应的实验成功来代替验证。

已运行的 Pod 为何会留下

admission 是请求写入存储前的关口。本次规则针对 CREATE pods,并关闭 background 评估。我们并没有创建一个会自动删除之前已允许并正在运行的 Pod 的控制器。因此,应单独观察现有 Pod 的 UID 保持不变且状态仍为 Running。不能把以同名重新创建的 Pod 算作“存活”。

实际现场中的表现

假设星期五发布前,安全引擎响应开始变慢。切换为 Ignore 可能改善部署可用性,却会引入接受未经评估变更的风险。保持 Fail 可能导致相应请求停止,却能关闭未经验证便写入存储的路径。无论选择哪一项,都必须先确定目的、范围和可容忍的故障时长。这不能用“大多数时候 Ignore 更好”一句话决定。

Kubernetes Webhook 设计指南中的 fail-open 建议,是在同时考虑变更 Webhook 与最终状态验证的语境下提出的。不要把它理解成对所有安全验证 Webhook 无条件使用 Ignore 的建议。还必须同时设计高可用、缩小调用范围、充足资源与故障告警。

replicas 恢复与策略恢复并非同一时刻

把控制器副本数恢复为 1,并不表示验证会立刻恢复。应分别确认新 Pod 启动、领导者接管、Webhook 注册和真实策略响应。本单元第一次学生路径测试中,replicas 在 23:49:53 UTC 恢复,但新进程直到 23:50:27 才获得 leader Lease。20 秒的注册等待在此之前就已结束。这不是 Fail 对策略违规的判定,而是实验运行器的就绪等待失败。

Kyverno v1.19.1 的领导者选举实现使用 Lease,并配置为退出时不立即释放。实际等待时间随安装方式和负载变化。不要把观察到的 34 秒视为所有环境的常量,应检查日志与注册状态。即使恢复期间等待失败,实验仍会保留缩容期间观察到的策略、注册与请求结果。点击重试按钮前,应先区分哪些内容已经恢复、哪些仍未得到确认。

制造故障的工具也需要恢复设计

暂停只应用于学生专用 VM 内的单个 Kyverno 容器。实验会检查容器 ID、Pod UID 与 PID 启动时间,并通过 Linux pidfd 绑定信号,避免因数字 PID 被复用而影响其他进程。独立监视器会在 20 秒内自动恢复进程,且不会把暂停与恢复拆分到两个步骤按钮之间。即使实验运行器被强制终止,或在返回就绪响应前消失,监视器仍会尝试恢复目标。

这套装置不能替代正常关闭流程、高可用测试或生产故障演练。连监视器本身也被终止,或 VM 被关闭,属于其他故障域。评分器不会重新制造故障,只会读取已保存的结果。基于文件的评分也不是能够阻止恶意 root 伪造全部证据的安全认证;它用于发现学习过程中观测依据缺失或彼此矛盾的情况。

下一项实验将做什么

第一项实验处理正常缩容与注册缺失。我们会记录目标范围和对照范围,亲自编写 ValidatingPolicy,然后观察正常基线及正常缩容时的注册状态。还会区分恢复请求、确认注册与真实拒绝响应的时间,并通过服务器 dry-run 验证“通过验证”和“写入存储”是不同阶段。

第二项实验在新的 VM 中独立开始,不需要第一项实验的文件。我们会重新建立策略和正常基线,再通过保留调用注册的短暂响应中断比较 Fail 与 Ignore。两项实验都会分别记录策略存在、注册、响应和现有 Pod 存活情况。必须先有原始材料,再得出结论。

还要确认 dry-run 在哪里执行

根据 Kubernetes 的服务器 dry-run 官方说明,服务器 dry-run 会在不保存变更的情况下执行该请求的验证路径。相比之下,客户端 dry-run 在本地构造输出,因此无法证明服务器 Webhook 是否会拒绝请求。检查文件语法、服务器接受请求,以及对象真实写入存储,是三种不同的验证。

本实验会使用唯一名称,分别对正常请求和违规请求执行 server dry-run,并单独查询同名 Pod。正常响应中的 kind、名称、namespace 与标签必须正确,同时不得存在保存结果。违规请求必须是带有策略标识的明确拒绝。不要因为成功输出中出现 Pod 对象就记录为已保存,也不要把 AlreadyExists 记录成策略拒绝。接收请求的 Webhook 也必须支持 dry-run 和副作用处理,因此迁移到其他安装环境时,还应检查真实 Webhook 的 sideEffects 设置。