Kubernetes 如何从初始快照开始监听后续变更?
结论
先发起一次 list 请求,保存列表响应中的 metadata.resourceVersion,再从这个版本启动 watch。不要用各对象 metadata.resourceVersion 中数值最大的一个代替列表版本。resourceVersion 应作为不透明字符串使用,不要自行比较数值大小。
获取快照并启动 watch
下面以 kubernetes_asyncio 的 CustomObjectsApi 为例。plural 是自定义资源的复数名称。代码先处理列表中的对象,再将程序标记为 ready,最后从列表响应中的版本启动 watch:
snapshot = await api.list_custom_object_for_all_namespaces(
group, version, plural
)
for item in snapshot.get("items", []):
apply_snapshot_item(item)
resource_version = snapshot["metadata"]["resourceVersion"]
ready = True
watcher = watch.Watch()
async for event in watcher.stream(
api.list_custom_object_for_all_namespaces,
group,
version,
plural,
resource_version=resource_version,
timeout_seconds=300,
):
event_type = event["type"]
item = event["object"]
if event_type in ("ADDED", "MODIFIED"):
upsert(item)
elif event_type == "DELETED":
remove(item)
实际项目中,应等快照处理完成、watch 正常接管后,再对外报告初始化成功。如果初始化期间有并发读取者,也要等快照应用完毕再发布 ready 状态。watch 可能因超时结束,之后需要从保存的最新版本重新建立连接,并在处理事件时更新本地状态。
收到 410 时如何恢复
410 Expired 表示请求的版本已不在 API server 可提供的历史范围内。此时应重新 list,构建新快照,再从新列表响应的 metadata.resourceVersion 启动 watch,不能依赖旧版本继续补齐事件。如果快照构建期间服务仍对外提供数据,应使用适合自身缓存或存储的替换策略,避免新旧快照混用。
如果列表使用分页,应通过 continue token 获取所有页面,再将快照视为完整。各分页响应属于同一列表快照,应使用该列表返回的版本启动 watch,不要从各页面的对象版本中挑选最大值。
版本字段注意事项
原代码将 _resource_version 注释为整数并计算最大值,需要改为使用 list 响应中的 metadata.resourceVersion,并原样传给 watch。若该版本已过期,就重新 list,不要尝试推算更新后的版本。
备注:内容仅供参考。
