【kube-apiserver】resourceVersion 与 Revision 映射:mod revision、continue 与一致性读期望
内容提要
本文解析Kubernetes中resourceVersion字段的语义与常见误用。该字段源自etcd的mod_revision,仅对同一对象有单调性,跨对象比较无意义。文章详述了Watch起点、continue token分页机制及一致性保证,指出不同读路径的一致性差异,并解释了410 Gone错误的来源与处理方式。
延伸解读
resourceVersion 的常见误用与生产风险
文章指出,将不同对象的 resourceVersion 相减来判断新旧是无意义的,因为该字段源自 etcd 的 mod_revision,仅对同一对象有单调性。此外,从 resourceVersion=0 发起 Watch 并不保证获得完整历史,而 continue token 也不是简单的页码参数。这些误用可能导致 List 返回非预期时间点的数据、Watch 从意外位置开始推送,或分页迭代时跳过对象,从而引发静默错误。
continue token 的分页机制与一致性保证
List 分页时,apiserver 在第一次请求时固定一个 etcd revision 快照(snap_rev),后续分页请求都在该快照上读取,确保分页结果的一致性,避免因中间对象变更导致漏读或重读。continue token 是 base64 编码的 JSON,包含 apiVersion、resourceVersion 和 startKey。但 token 不可跨版本使用,若 apiserver 重启或 etcd 发生 compaction,旧 token 会失效,客户端需重新 List。
不同读路径的一致性差异与选择
Kubernetes 的 List/Get 有四种读路径,一致性级别不同:强一致读穿透 etcd 并触发 ReadIndex,保证线性一致;走 cacher 的 rv=0 可能读到略旧版本;走 cacher 的具体 rv 保证不早于该 rv;完全走 cacher 的 rv=0 返回内存快照。若需要精确的线性一致读,应使用不传 resourceVersion 且不走 cacher 的路径,但代价是每次穿透 etcd,增加读负载。
410 Gone 的两种来源与处理方式
410 Gone 表示请求的 startRevision 已不可用,客户端必须全量重新 List。它可能来自 etcd 的 ErrCompacted(revision 被清理)或 cacher 侧 channel 溢出(消费端慢)。排障时需区分来源:前者检查 etcd compaction 配置,后者检查 apiserver watch cache 容量和消费速率。正确处理是丢弃本地状态,从 rv="" 或 rv="0" 重新 List,再以新 List 的 resourceVersion 作为 Watch 起点。
Q&A
Kubernetes 中 resourceVersion 字段的来源是什么?
resourceVersion 字段来源于 etcd 的 mod_revision,即对象最后一次被修改时的全局 Revision。apiserver 在解码 etcd 存储时,将 mod_revision 转换为字符串并写入 metadata.resourceVersion。
为什么不能比较两个不同对象的 resourceVersion 来判断哪个更新?
因为 resourceVersion 只对同一对象有单调性,不同对象的 mod_revision 之间没有顺序关系,跨对象比较没有意义。
List 响应中的 resourceVersion 代表什么?
List 响应中的 resourceVersion 是 List 操作所读取的 etcd revision 快照值,来自 etcd Range 响应的 Header.Revision,而不是列表中各对象 mod_revision 的最大值。这个值可以作为后续 Watch 的起点。
Watch 请求中 resourceVersion 参数取不同值时的语义是什么?
不传(empty)表示从当前 etcd 版本开始,不保证初始同步完整性;"0" 表示任意最新版本(可能来自 cache,可能略旧);具体值(如 "12345")表示从该 revision 之后开始推送事件,保证不早于该版本。
continue token 在 List 分页中是如何工作的?
客户端发起带 limit 的 List 请求,apiserver 调用 etcd Range 获取一页数据,并将下一页起始 key 和快照 revision 打包成 continue token(base64 编码)返回。后续请求携带该 token,apiserver 解码后在同一 revision 快照上继续读取,保证分页结果的一致性。
为什么 continue token 可能失效并导致 410 Gone?
如果 apiserver 重启或 etcd 发生 compaction 清理了 continue token 中的快照 revision,客户端再请求时会收到 410 Gone。此时客户端必须重新从头 List。
Kubernetes 中 List/Get 有哪四种读路径,它们的一致性级别有何不同?
四种路径:1) 强一致读(不传或传当前 rv)走 etcd ReadIndex,保证线性一致;2) 走 cacher 的 rv=0 可能略旧;3) 走 cacher 的 rv=具体值保证不早于指定 rv;4) 走 cacher 的 rv="0" 完全返回内存快照。
410 Gone 错误的来源有哪些?
410 Gone 可以来自两处:1) etcd 的 ErrCompacted,当 Watch 的 startRevision 早于 etcd 的 compact revision 时返回;2) cacher 侧 channel 溢出或 watch 过期,导致 cacher 强制关闭 Watch 并发送 410。
收到 410 Gone 后客户端应如何处理?
客户端应丢弃本地状态,从 rv="" 或 rv="0" 重新 List,再用新 List 的 ListMeta.ResourceVersion 作为 Watch 起点。这是 client-go Informer 内置的重连逻辑。