You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kubernetes client-go Informer监听Deployment副本变更未触发事件

问题核心原因

你的代码存在4个致命问题,直接导致事件捕获失效、程序异常崩溃:

  • 你使用SharedInformerFactory创建informer,却绕过factory直接调用informer.Run()启动,factory上配置的namespace过滤、FieldSelector过滤规则完全不生效,informer会尝试拉取集群全量Deployment数据。如果你配置的RBAC权限没有集群级Deployment的list/watch权限,会直接静默list失败,本地缓存永远无法建立,自然收不到任何事件。
  • 启动informer后没有等待本地缓存同步完成。informer的工作逻辑是先全量list目标对象推送Add事件,再从最新resourceVersion开始watch增量Update事件,缓存未同步完成时不会处理后续watch事件,甚至可能触发空指针导致无日志panic。
  • 事件处理函数中直接写panic逻辑,只要遇到一次类型断言异常(比如informer断连重连时会传入cache.DeletedFinalStateUnknown类型的墓碑对象),整个进程直接退出。如果程序部署在K8s集群中,Pod会被自动重启,表现就是无错误信息的崩溃循环。
  • 逻辑漏洞:你在函数中写了defer close(stopper),如果这段代码不是写在main函数的主协程中,函数返回时会直接关闭stopper通道,informer会立刻停止运行,根本等不到后续扩缩容事件。
修正后可直接运行的代码
package main

import (
	"time"

	"github.com/sirupsen/logrus"
	appsv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/tools/cache"
)

func main() {
	// 前置变量:clientSet、targetName、targetNamespace、sendUpdates 提前初始化完成
	labelOptions := informers.WithTweakListOptions(func(opts *v1.ListOptions) {
		opts.FieldSelector = "metadata.name=" + targetName
	})
	factory := informers.NewSharedInformerFactoryWithOptions(
		clientSet,
		5*time.Minute, // 重同步周期设为5分钟更合理,避免频繁请求API Server
		informers.WithNamespace(targetNamespace),
		labelOptions,
	)
	deploymentInformer := factory.Apps().V1().Deployments().Informer()

	// 先注册事件处理器,再启动informer,避免漏掉启动窗口期的事件
	_, err := deploymentInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			// 兼容处理墓碑对象
			dep, ok := obj.(*appsv1.Deployment)
			if !ok {
				tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
				if !ok {
					logrus.Errorf("AddFunc got unexpected object type: %T", obj)
					return
				}
				dep, ok = tombstone.Obj.(*appsv1.Deployment)
				if !ok {
					logrus.Errorf("AddFunc got invalid tombstone object type: %T", tombstone.Obj)
					return
				}
			}

			replicas := int(*dep.Spec.Replicas)
			logrus.Infof("Initial deployment synced, current replicas: %d", replicas)
			sendUpdates()
		},
		UpdateFunc: func(oldObj, newObj interface{}) {
			// 兼容处理新旧对象的墓碑场景
			oldDep, ok := oldObj.(*appsv1.Deployment)
			if !ok {
				tombstone, ok := oldObj.(cache.DeletedFinalStateUnknown)
				if !ok {
					logrus.Errorf("UpdateFunc got unexpected old object type: %T", oldObj)
					return
				}
				oldDep, ok = tombstone.Obj.(*appsv1.Deployment)
				if !ok {
					logrus.Errorf("UpdateFunc got invalid old tombstone object type: %T", tombstone.Obj)
					return
				}
			}

			newDep, ok := newObj.(*appsv1.Deployment)
			if !ok {
				tombstone, ok := newObj.(cache.DeletedFinalStateUnknown)
				if !ok {
					logrus.Errorf("UpdateFunc got unexpected new object type: %T", newObj)
					return
				}
				newDep, ok = tombstone.Obj.(*appsv1.Deployment)
				if !ok {
					logrus.Errorf("UpdateFunc got invalid new tombstone object type: %T", tombstone.Obj)
					return
				}
			}

			// 资源版本相同说明是informer重同步事件,直接跳过
			if oldDep.ResourceVersion == newDep.ResourceVersion {
				return
			}
			// 跳过正在删除的Deployment事件
			if newDep.DeletionTimestamp != nil {
				return
			}

			oldReplicas := int(*oldDep.Spec.Replicas)
			newReplicas := int(*newDep.Spec.Replicas)
			if oldReplicas != newReplicas {
				logrus.Infof("Deployment replicas changed from %d to %d", oldReplicas, newReplicas)
				sendUpdates()
			}
		},
		DeleteFunc: func(obj interface{}) {
			logrus.Infof("Deployment %s/%s deleted", targetNamespace, targetName)
		},
	})
	if err != nil {
		logrus.Fatalf("Failed to add event handler: %v", err)
	}

	stopper := make(chan struct{})
	defer close(stopper)
	// 统一通过factory启动所有informer,不要单独调用informer.Run
	factory.Start(stopper)

	// 必须等待缓存同步完成,这步不能省略
	logrus.Info("Waiting for deployment informer cache sync...")
	if !cache.WaitForCacheSync(stopper, deploymentInformer.HasSynced) {
		logrus.Fatalf("Failed to sync deployment informer cache")
	}
	logrus.Info("Deployment informer cache synced, start listening events")

	// 阻塞运行直到收到退出信号
	<-stopper
}
关键注意事项
  • 事件处理逻辑不要执行长耗时阻塞操作,否则会卡住整个informer的事件分发队列,导致事件延迟甚至丢失。如果有耗时逻辑,提前把数据丢到带缓冲的channel里,启动单独的worker协程处理。
  • 确认你的ServiceAccount绑定的RBAC权限,拥有目标namespace下Deployment的list、watch权限,否则informer无法正常拉取数据。
  • 不需要额外监听HPA资源,HPA触发扩缩容时一定会修改Deployment的Spec.Replicas字段,只要informer缓存同步正常就不会丢事件。

内容的提问来源于stack exchange,提问作者Syd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 17:21:10