watch 是所有控制器的地基。从一个 resourceVersion 开始 watch 到底能收到什么?版本太旧时发生什么?这篇用真实的事件流把每种情形都跑出来,包括手动压缩 etcd 也没能让旧版本失效这个出乎意料的结果。
watch?resourceVersion=14898(b 创建时的版本)收到 4 个事件:MODIFIED a rv=14899 | DELETED b rv=14900 | ADDED c rv=14901 | MODIFIED c rv=14902 watch?resourceVersion=14902(当前版本)收到 0 个事件 同样从 14902 开始,watch 期间创建再删除 live:ADDED live rv=14914 | DELETED live rv=14915
依次创建 a、b,修改 a,删除 b,创建 c,修改 c。从 b 创建时的版本(14898)开始 watch,恰好收到它之后的 4 个事件:MODIFIED a、DELETED b、ADDED c、MODIFIED c,每个事件里的 resourceVersion 就是那次修改的版本。从当前版本开始 watch 一个事件也没有,直到有新的变化才会收到(创建再删除 live 得到 ADDED、DELETED)。
不带 resourceVersion:ADDED a rv=14899 | ADDED c rv=14902 | ADDED kube-root-ca.crt rv=14896
resourceVersion=0:ADDED a rv=14899 | ADDED c rv=14902 | ADDED kube-root-ca.crt rv=14896
sendInitialEvents=true(流式 list):ADDED a rv=14899 | ADDED c rv=14902 | ADDED kube-root-ca.crt rv=14896 | BOOKMARK rv=14923
BOOKMARK 的注解:{'k8s.io/initial-events-end': 'true'}
(b 在 resourceVersion=0 的结果里不存在:被删除的对象不会以事件的形式“补发”)不带 resourceVersion 或写 0:服务端先把现有对象当作 ADDED 发一遍,再接实时事件。已经删掉的 b 不会补发 DELETED——所以“从头 watch”拿不到历史,只能拿到当前状态。较新的 sendInitialEvents=true 把 list 和 watch 合成一个流,初始对象发完后有一个注解为 k8s.io/initial-events-end: true 的 BOOKMARK,告诉客户端“初始状态完整了”。
记下 burst 的 resourceVersion=14929;再写 1200 次(12 秒);手动压缩 etcd 到 revision 16147(compacted revision 16147)
压缩之后从 14929 开始 watch:收到 1199 个事件,第一个是 MODIFIED rv=14930——压缩没有让它失效,事件是从 apiserver 的 watch 缓存里补发的
累计写入 2200 次(距 14929 已过去 30 秒):watch 收到 2199 个事件,第一个是 MODIFIED
累计写入 3200 次(距 14929 已过去 42 秒):watch 收到 3199 个事件,第一个是 MODIFIED
累计写入 4200 次(距 14929 已过去 55 秒):watch 收到 4199 个事件,第一个是 MODIFIED
累计写入 5200 次(距 14929 已过去 67 秒):watch 收到 5199 个事件,第一个是 MODIFIED
累计写入 6200 次(距 14929 已过去 80 秒):watch 收到 6199 个事件,第一个是 MODIFIED
累计写入 7200 次(距 14929 已过去 92 秒):watch 收到 1 个事件,第一个是 ERROR
出错时服务端发来的事件:{"type": "ERROR", "object": {"kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "too old resource version: 14929 (15739)", "reason": "Expired", "code": 410}}
恢复办法:重新 list 得到 resourceVersion=22275,再从它 watch:MODIFIED burst rv=22279这一节的结果和直觉不一样:写 1200 次后手动把 etcd 压缩到当前 revision,从旧版本 watch 仍然收到全部 1199 个事件——旧事件是从 apiserver 自己的 watch 缓存里补发的,压缩没有让它失效。继续写,累计 6200 次(过去 80 秒)还行,累计 7200 次(92 秒)才得到一个 ERROR 事件:code: 410、reason: Expired、too old resource version: 14929 (15739),括号里是缓存里最老的版本。恢复办法是重新 list 拿到新的 resourceVersion,再从它 watch。
watch?labelSelector=app=x 的事件序列(对象 sel 先后:标签改成 app=y → 改回 app=x → 改 data → 删除):
ADDED sel rv=22281
DELETED sel rv=22285
ADDED sel rv=22286
MODIFIED sel rv=22287
DELETED sel rv=22288带 labelSelector 的 watch 看到的是“满足条件的对象集合”的变化:对象标签从 app=x 改成 app=y,对这个 watch 来说对象消失了,收到 DELETED;改回来又是 ADDED;条件内的修改才是 MODIFIED。客户端缓存收到 DELETED 并不代表对象真的被删除了。