最可靠低延迟监控Kubernetes Pod状态的方式是使用client-go的Watch或Informer机制;Watch需先List获取ResourceVersion再启动,Informer则自动缓存与重连,避免轮询浪费资源和错过瞬时状态。

client-go 的 Watch() 方法最轻量,但容易因 ResourceVersion 失效、类型断言 panic 或连接中断而静默失败;生产环境必须用 Informer 或 controller-runtime,它们自动处理缓存、重连、去重和事件分发。
Watch() 必须带 ResourceVersion,否则 410 Gone
API Server 要求每次 Watch 请求必须携带有效的 ResourceVersion,空字符串只允许首次调用(服务端会自动设为当前最新),后续重连必须复用上一次事件中对象的 Object.GetResourceVersion()。不带或过期会导致 HTTP 410 Gone 错误,且不会返回明确提示。
- 先调
List()获取初始资源列表和resourceVersion字段值 - 再用该值发起
Watch(),URL 形如/api/v1/pods?watch=true&resourceVersion=12345 - 收到
watch.Event后,若event.Type == watch.Error,检查event.Object.(*metav1.Status).Reason是否为Expired,是则需重新List()再Watch()
Informer 自动管理缓存与重连,但启动前必须 Run()
Informer 封装了 List + Watch + 本地缓存(cache.Indexer)+ 断线重试逻辑,适合长期运行的服务。但它不会自动启动——漏掉 informer.Run(stopCh) 是常见静默失效原因。
- 调用
factory.CoreV1().Pods(namespace).Informer()后,必须显式启动:go informer.Run(stopCh) -
stopCh是context.WithCancel得到的chan struct{},程序退出前要close(stopCh),否则 goroutine 泄漏 - 注册回调时,
OnAdd/OnUpdate参数是*v1.Pod指针,但 Informer 内部会复用内存,不能长期保存指针,需深拷贝或立即提取关键字段(如Name,Namespace,Phase)
监听 CRD 必须用 controller-runtime,裸 Watch 会丢事件
CRD 资源一旦注册进集群,其变更必须经由 controller-runtime 的 Informer 体系分发。直接对 CRD 调 Watch() 或轮询,既无法断点续传,也不走本地缓存,多副本还会重复处理。
-
CRD YAML中spec.preserveUnknownFields: false必须设为false,否则controller-runtime启动时 Informer 初始化失败且无日志 - RBACK rules 必须同时包含
get、list、watch权限,缺watch则控制器完全收不到事件 - 若只关心
Status字段变更(比如其他组件更新了状态),需显式在Watches中监听子资源:.Watches(&source.Kind{Type: &myappv1.MyApp{}}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(predicate.GenerationChangedPredicate{}))
处理 Deleted 事件别直接断言 *v1.Pod
watch.Event.Object 在 Deleted 类型下不是 *v1.Pod,而是 *metav1.Status;正确取删除前对象要用 event.DeletedObject,但它也可能是 *unstructured.Unstructured 或 *metav1.PartialObjectMetadata,硬断言必 panic。
立即学习“go语言免费学习笔记(深入)”;
- 统一用
meta.IsListType(event.Object)和meta.IsObject(event.Object)判断类型 - 对
Added/Modified:先if pod, ok := event.Object.(*v1.Pod); ok { ... } - 对
Deleted:用deletedObj := event.DeletedObject,再通过meta.GetPartialObjectMetadata(deletedObj)或转unstructured.Unstructured提取GetName()和GetNamespace()
ResultChan() 关闭,但你没检测 channel 关闭就继续 range,导致循环退出且无任何错误提示;Informer 启动后没等 HasSynced() 就开始查缓存,拿到的是空数据;CRD 的 RBAC 缺 watch 权限,控制器日志里连 “failed to list” 都不打——它压根没尝试发起请求。


















