PAYATHON 2026

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,不要尝试推算更新后的版本。

备注:内容仅供参考。