【kube-apiserver】Watch cache / cacher:dispatch、bookmark 与穿透 etcd
内容提要
本文介绍Kubernetes v1.30.3中kube-apiserver的watch cache机制,核心是Cacher组件:它通过watchCache环形缓冲存储事件,用Ready门控制启动同步,dispatch实现内存多路分发,bookmark维持资源版本推进。当缓存未命中或未就绪时回退到etcd,可能引发List风暴,需调整缓存容量和bookmark频率优化。
延伸解读
生产排障:Watch 事件延迟不一定是 etcd 问题
当 Pod 更新成功但 controller 未收到 Watch 事件时,常见误判是 etcd 故障。实际上,可能是 cacher 的 Ready 门未打开,或客户端 resourceVersion 过旧导致 cacher 决定直穿 etcd,而 etcd 本身正常。排查时应先检查 apiserver 日志中的 cacher not ready 提示,并确认客户端使用的 resourceVersion 是否仍在 watchCache 窗口内。
watchCache 容量与 List 风暴的关联
watchCache 默认容量为 100,高写入速率资源(如 Event)可能迅速覆盖环形缓冲,导致客户端 startRev 滑出窗口,收到 410 Gone 后触发重新 List。若多个控制器同时重试,会形成 List 风暴。增大 --watch-cache-sizes 可缓解,但需权衡内存占用。
bookmark 机制对减少 List 风暴的关键作用
bookmark 事件定期推送,使客户端(client-go Reflector)能持续推进本地 resourceVersion,断线重连时可直接从 ring buffer 续接,避免从 RV=0 重新全量 List。若 bookmark 频率与 etcd compaction 间隔不匹配,客户端可能因 RV 过期而被迫重新 List,引发风暴。
Q&A
Kubernetes 中 kube-apiserver 的 watch cache 机制是什么?
watch cache 是 kube-apiserver 中的一层缓存,位于 etcd3 store 之上,通过 Cacher 组件实现。它维护一个 watchCache 环形缓冲区和本地对象存储,将 etcd 的 Watch 事件在内存中多路分发给多个客户端,避免每个客户端都直接连接 etcd,从而减少 etcd 压力。
Cacher 的 Ready 门是什么?它如何影响请求处理?
Cacher 的 Ready 门是一个同步机制,Cacher 启动后需要先完成一次对 etcd 的全量 List,将数据装入本地缓存,然后才打开 Ready 门。在 Ready 门打开之前,List 和 Watch 请求会阻塞等待,防止客户端在缓存未同步时得到空数据。如果 Ready 门延迟打开,大量请求会堆积,一旦打开可能引发 List 风暴。
watch cache 中的 dispatch 机制是如何工作的?
当 etcd 的 Watch 事件到达 Cacher 后,processEvent 会将其写入 watchCache 环形缓冲区,更新本地对象存储,然后调用 dispatchEvent 遍历所有已注册的 cacheWatcher,将事件投递给每个 watcher。每个 watcher 有自己的输入 channel,如果 channel 满,watcher 会进入重试路径,最终可能被强制关闭并返回错误。
bookmark 在 watch cache 中起什么作用?
bookmark 是 Watch 协议中的进度通知,当 Watch 流上长时间没有新事件时,服务端会发送一个 BOOKMARK 事件,只携带 resourceVersion,不含对象内容。Cacher 定期(默认约 60 秒)向已同步的 watcher 推送 bookmark,客户端使用它更新本地缓存的 resourceVersion,以便断线重连时从该版本续接,避免从零开始全量 List,从而减少 List 风暴。
什么情况下 watch cache 会穿透到 etcd?
当 Watch 请求的 resourceVersion 早于环形缓冲区中最旧的版本(已滑出窗口)时,无法从缓存回放,Cacher 会判断是否返回 410 Gone 或直接错误。此外,List 请求如果要求一致性读(rv 为空),也会直接透传到 etcd3 store。如果大量请求穿透,会增加 etcd 的读压力。
如何避免 Kubernetes 中的 List 风暴?
List 风暴的典型触发场景包括 apiserver 重启后 Ready 门延迟打开,以及环形缓冲区容量过小导致大量 watcher 收到 410 Gone 后重新 List。避免方法包括:针对高写入速率资源增大 --watch-cache-sizes 参数,确保 bookmark 频率与 etcd compaction 间隔匹配,以及使用 APF 流控对 List 操作限速。