TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

寻找无人使用的 Webhook、每6秒查询一次的 Operator:KubeVirt 主仓库 PR 在 2 天 16 小时内合并的过程

结果:已合并 (2026-10-20 16:05, kubevirt/kubevirt#19499). 截至 2026-10-29,暂无包含此变更的版本发布。 实际上,此内容取自 GitHub 的 PR、评审、行内评论、时间线、检查记录,KubeVirt 源码(PR 的基础提交 580886af),以及贡献记录的实测原始数据(operator 指标原值、reconcile 间隔)和草稿。时间均为韩国时间。

目录

一句话总结

PR kubevirt/kubevirt#19499 virt-operator: stop GETting the disabled ContainerPath webhook on every reconcile
修改内容 三个文件。在启动前同步列表中增加一行,删除直接 GET 的 12 行,扩展现有的单元测试。增加 10 行,删除 13 行
反应 评审员留下要求去掉一个注释的行内评论并批准,最初引入该代码的 approver /approve
结果 PR 公开 10-17 23:37 → 评审 10-19 16:54 → 批准 22:23 → 合并 10-20 16:05,约 2 天 16 小时 27 分钟。通过 42 个检查
꺼진 웹훅을 reconcile 마다 찾던 길 KubeVirt v1.9.0 리더 virt-operator, 59일 누적 카운터(실측) ① 고치기 전: 게이트가 꺼져 있으면(기본값) reconcile 인포머 캐시 조회 없음 → 직접 GET API 서버 404 약 6초마다 한 번 (중앙 6.055초, 236구간) GET mutatingwebhookconfigurations 404 843,995 LIST nodes(reconcile 마다 한 번, 비교용) 843,995 operator 전체 요청 15,509,446 중 404 가 5.4% ② 고친 뒤(#19499) 시작 전에 웹훅 인포머 동기화 캐시만 믿고 조회 없음 → 요청 없음 있음 → 지움 게이트를 켰다 끈 경우 웹훅은 캐시에 보이므로 예전처럼 지운다
图:每次 reconcile 都去寻找关闭的 Webhook 的路径以及修复后

1. 问题所在:59 天内 843,995 次 404

TT Lab 使用 KubeVirt 运行学员的练习 VM。在 2026-10-17 仅通过读取命令浏览 TT Lab 集群的 KubeVirt 组件时 (贡献记录的 measure/kv),我们直接获取了 leader virt-operator 导出的 kubevirt_rest_client_requests_total 指标。 当时的版本是 KubeVirt v1.9.0、Kubernetes v1.34.10,该 operator 已经运行了 59 天。计数器是进程启动后累计的值。

请求 59 天累计
GET mutatingwebhookconfigurations → 404 843,995
LIST nodes → 200(每次 reconcile 一次,用于对比) 843,995
operator 的所有 REST 请求 15,509,446
非 2xx 的 operator 请求 843,999

收到 404 的 GET 请求数量与每次 reconcile 进行一次的 LIST nodes 在个位数上完全一致。这意味着每次 reconcile 准确无误地产生一次 404。这个 404 占 operator 总请求的 5.4%(843,995 ÷ 15,509,446),除了 4 个之外,占了所有非 2xx 响应(共 843,999 个)的全部。

统计当天 leader operator 日志中的 237 行「Handling KubeVirt resource」,reconcile 间隔最小 6.007 秒、中位数 6.055 秒、最大 6.088 秒(共 236 个区间)。59 天除以 6.055 秒大约是 84 万 2 千次,与计数器基本吻合(计算)。reconcile 每 6 秒运行一次本身是由于 Lease 续期引起的独立问题,此前已有相关 issue(#19342)和 修复 PR(#19417,截至 2026-10-29 处于开启状态)。我们发现的是叠加在之上的另一个问题。无论 reconcile 每几秒运行一次,这个 GET 请求从一开始就是完全没有必要的。

虽然没有造成明显的危害,但正如 PR 正文中所写,这些 404 请求在 VirtOperatorRESTErrorsBurst 警报监控的错误率下方总是垫底约 5% 的基础底噪。由于远未达到警报阈值(80%)因此不会触发警报,但它们会掩盖仪表盘上的真正错误。

2. 原因:源码中的哪一行

行号以 PR 的基础提交 580886af 为准。

那么,「缓存可能尚未填满」的担忧是有道理的吗?controller 在开始工作之前会等待多个 Informer 同步。 kubevirt.go 第 169–194 行 的 hasSynced 列表中。其中包含了验证 Webhook Informer(第 180 行 ValidationWebhook),但唯独漏掉了变更 Webhook Informer(MutatingWebhook)。因此,对缓存的不信任是有原因的,需要修复的地方应该是同步列表,而不是 GET 请求。

这个直接 GET 是随 Webhook 一起在 #16662(VEP 165,2026-02-24 合并)中引入的。

3. 做了什么修改:diff 要点

 # pkg/virt-operator/kubevirt.go: 시작 전에 기다리는 인포머 목록
 			informers.ValidationWebhook.HasSynced() &&
+			informers.MutatingWebhook.HasSynced() &&

 # pkg/virt-operator/resource/apply/admissionregistration.go
-	// Feature gate is disabled, delete the webhook if it exists
+	// Feature gate is disabled, delete the webhook if it exists.
 	_, exists, _ := r.stores.MutatingWebhookCache.Get(webhook)
-	if !exists {
-		// Also check directly in case cache is not populated
-		_, err := r.k8sClient.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(
-			context.Background(), webhook.Name, metav1.GetOptions{})
-		... (404 면 nil, 그 밖의 오류면 반환, 있으면 exists = true)
-	}

一旦缓存变得可信,缓存中不存在就意味着没有需要删除的内容,因此无需发送 API 请求。如果是开启过又关闭了门控的情况,由于 operator 创建的 Webhook 始终挂载着 Informer 所挑选的 managed-by 标签,因此会显示在缓存中并像以前一样被删除。PR 正文中也说明了这一选择的代价:如果有人手动剥离了该标签,operator 将无法看到该 Webhook 并将其删除。operator 通过标签挑选的其他对象也采用相同的方式处理。虽然也考虑过「仅在首次同步之前保留 GET」的方案,但因为需要维护额外的一个状态,最终还是选择了只需增加一行的同步方案。

测试扩展了原有的单元测试「should not create virt-launcher-pod-mutator webhook when disabled」。我们设置了当伪客户端以该 Webhook 名称记录 get 哪怕一次时就判定为失败。PR 正文中记载的结果如下(Go 1.27.2, linux/amd64, GOFLAGS=-mod=vendor, main 6e80bca).

测试 结果
仅修改测试但未修复状态,「ContainerPathVolumes feature gate」相关 3 个中 1 个失败
修复后的同一组测试 通过
go test ./pkg/virt-operator/... 通过

其中有一次是 ./pkg/virt-operator/resource/apply/ 的 TestInstallStrategy 失败了,但在修复版和未修复版中重新运行均未复现,这也如实记录在了正文中。该输出的原始文件未单独保存在记录中。我们并未将修复后的 operator 部署到 TT Lab 集群中重新测量 404 是否消失(未实测)。因为 TT Lab 集群仍然是 v1.9.0,且目前没有包含此变更的版本发布。

关于 AI 的使用,我们作了如下声明。KubeVirt 的 AI 贡献政策建议使用 AI 工具时在提交说明尾部(Assisted-by:·Co-authored-by:·Generated-by:)中予以说明,同时依然要求进行 DCO 签名。 Assisted-by: Claude 我们在提交中附加了 Signed-off-by 和 47a37b0a ,并在 PR 模板的「AI Contributions」栏中勾选,并在正文末尾添加了「借助 Claude 追踪了指标中的原因并编写了变更和测试的草稿,本人已审查每一行代码,上述数字测量自我本人的集群」这一段落。有一个细节没有按原则进行:根据评审修改后重新生成的提交

在 GitHub 上显示为未签名提交(unsigned)。似乎是在重写提交时漏掉了原则 3 的 SSH 签名。

4. 反应:一条行内评论,两个批准 时间
10-17 23:37 日 size/S·area/operator·sig/control-plane PR 公开。prow 分配了
10-17 23:38 标签和两名评审员 /ok-to-test CodeRabbit 提示「此仓库已关闭自动评审」并跳过。prow 提示「受信任的贡献者无法通过 /test all 开启测试,协作者需要输入
10-19 16:52 」 /test all 评审员的行内评论。在 16:54 的评审中附带了 /cc
10-19 22:03 以及一名 approver
10-19 22:23 提交了修改注释的 commit,并于 22:13 回复了行内评论 lgtm 评审员批准,附带
10-19 23:27 标签 /approveapprover
10-20 16:05 ,机器人运行了 2 阶段 e2e 共 8 项(sig-network·storage·compute·operator, Windows, vGPU, IPv6)

合并

评审的内容只有一条行内评论。最初提交的版本中,在被删除的 GET 位置有五行注释。解释内容为:「controller 等待 Webhook Informer 同步,由于 Webhook 始终挂载着标签,因此如果缓存中没有则说明无需删除,如果此时向 API Server 查询,会导致所有不使用该门控的集群在每次 reconcile 时产生 404 GET」。评审员表示不确定这是否算是一个严重问题。由于 Webhook 的创建或删除会由事件处理器捕获并重新加入队列,因此意见是注释可以去掉。

确实如此。即使在同步前的短暂间隙缓存为空,一旦 Webhook 出现,Informer 也会再次触发 reconcile。也就是说,注释夸大了这个 GET 的作用。删除了五行注释并修改提交重新上传后,简短回复了「Informer 在添加和删除时无论如何都会重新放回队列,注释夸大了作用,已移除」。结果,PR 的代码仅保留了增加的一行和删除的 GET 块。 virt-operator approver 是 /approve 目录下 OWNERS 中的人员,也是引入该直接 GET 的 #16662 的作者。留下的留言是

以及一句感谢。包含 2 阶段 e2e 在内的 42 个检查全部通过,第二天下午 tide 进行了合并。记录中将此 PR 记载为选定 KubeVirt 作为主力技术栈后进入主仓库的第一笔合并。

Titi 的一句话。 据说看门大叔每隔 6 秒就会敲一次仓库的门,并问道:“这里有箱子吗?”可仓库里从一开始就没有那个箱子。虽然只要看一下笔记本就行,但因为笔记本的列表中唯独漏掉了那个仓库,所以才无法信任。帮他在笔记本上补记了一行之后,持续了两个月、敲了 84 万多次门的事情就消失了。至于那张写着“为什么需要敲门”的长篇大论的字条,因为别人说“那个可以拿掉”,所以就撕掉了。

链接

所有资料均于 2026-10-29 查阅。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论