Reflector
Informer启动后会连接API Server并进行全量资源查询,之后会对资源对象进行监听。以上操作主要是由Reflector实现的。源码路径为k8s.io/client-go/tools/cache/reflector.go。
Reflector使用的List/Watch方法主要分为2部分,第一部分用来获取全量的资源列表;第二部分是对资源对象进行监控。
首先看一下Reflector的结构体定义,示例如下所示。
type Reflector struct {
name string
expectedTypeName string
expectedType reflect.Type
expectedGVK *schema.GroupVersionKind
store Store
listerWatcher ListerWatcher
backoffManager wait.BackoffManager
initConnBackoffManager wait.BackoffManager
resyncPeriod time.Duration
ShouldResync func() bool
clock clock.Clock
paginatedResult bool
lastSyncResourceVersion string
isLastSyncResourceVersionUnavailable bool
lastSyncResourceVersionMutex sync.RWMutex
WatchListPageSize int64
watchErrorHandler WatchErrorHandler
}
其中,listerWatcher这个字段是一个接口类型,Reflector在run方法中会调用ListAndWatch方法,而ListerAndWatch方法中实际执行的就是ListerWatcher的Lister方法和Watcher方法,分别用来获取全量的资源对象和对后面的事件变更进行监控。ListerWatcher定义如下所示。
type ListerWatcher interface {
Lister
Watcher
}
Lister和Watcher也是接口,Lister接口的代码如下所示。
type Lister interface {
List(options metav1.ListOptions) (runtime.Object, error)
}
Watcher接口的代码如下所示。
type Watcher interface {
Watch(options metav1.ListOptions) (watch.Interface, error)
}
而实际上r.ListerWacher.List真正调用的是Pod、Deployment等资源对象的Informer下的ListFunc函数和WatchFunc函数,源码路径为k8s.io/client-go/informers/core/v1/pods.go。
Pod Informer的ListFunc函数和WatchFunc函数代码如下所示。
func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { return cache.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { tweakListOptions(&options) } return client.CoreV1().Pods(namespace).List(context.TODO(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } return client.CoreV1().Pods(namespace).Watch(context.TODO(), options) }, }, &corev1.Pod{}, resyncPeriod, indexers, ) }
ListFunc函数和WatchFunc函数是通过Clientset客户端与API Server交互后获得的,这也是为什么8.2.1节的代码示例中,在生成NewSharedInformerFactory之前需要先获得Clientset,示例如下所示。
//创建KController对象 func NewKubeController(kubeConfig *restclient.Config, clientset *kubernetes. Clientset, defaultResync time.Duration) *KubeController { kc := &KubeController{kubeConfig: kubeConfig, clientset: clientset} //需要传入Clientset kc.factory = informers.NewSharedInformerFactory(clientset, defaultResync) ... kc.deploymentInformer = kc.factory.Apps().V1().Deployments() kc.deploymentsLister = kc.deploymentInformer.Lister() kc.deploymentsSynced = kc.deploymentInformer.Informer().HasSynced kc.podInformer = kc.factory.Core().V1().Pods() kc.podsLister = kc.podInformer.Lister() kc.podsSynced = kc.podInformer.Informer().HasSynced ... return kc }
List/Watch的逻辑比较长,下面分为2部分来分析核心的函数调用,第一部分示例如下所示。
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error { klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name) var resourceVersion string options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()} if err := func() error { initTrace := trace.New("Reflector ListAndWatch", trace.Field{"name", r.name}) defer initTrace.LogIfLong(10 * time.Second) var list runtime.Object var paginatedResult bool var err error listCh := make(chan struct{}, 1) panicCh := make(chan interface{}, 1) //调用r.listerWatcher.List获取资源数据 go func() { defer func() { if r := recover(); r != nil { panicCh <- r } }() pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) { return r.listerWatcher.List(opts) })) switch { case r.WatchListPageSize != 0: pager.PageSize = r.WatchListPageSize case r.paginatedResult: case options.ResourceVersion != "" && options.ResourceVersion != "0": pager.PageSize = 0 } list, paginatedResult, err = pager.List(context.Background(), options) if isExpiredError(err) || isTooLargeResourceVersionError(err) { r.setIsLastSyncResourceVersionUnavailable(true) list, paginatedResult, err = pager.List(context.Background(), metav1. ListOptions{ResourceVersion: r.relistResourceVersion()}) } close(listCh) }() select { case <-stopCh: return nil case r := <-panicCh: panic(r) case <-listCh: } if err != nil { return fmt.Errorf("failed to list %v: %v", r.expectedTypeName, err) } //调用listMetaInterface.GetResourceVersion获取资源的版本号 if options.ResourceVersion == "0" && paginatedResult { r.paginatedResult = true } r.setIsLastSyncResourceVersionUnavailable(false) initTrace.Step("Objects listed") listMetaInterface, err := meta.ListAccessor(list) if err != nil { return fmt.Errorf("unable to understand list result %#v: %v", list, err) } resourceVersion = listMetaInterface.GetResourceVersion() initTrace.Step("Resource version extracted") //调用meta.ExtractList 将资源转换成对象列表 items, err := meta.ExtractList(list) if err != nil { return fmt.Errorf("unable to understand list result %#v (%v)", list, err) } initTrace.Step("Objects extracted") //调用r.syncWith 将资源对象列表中的资源对象和对应的版本号存入DeltaFIFO if err := r.syncWith(items, resourceVersion); err != nil { return fmt.Errorf("unable to sync list result: %v", err) } initTrace.Step("SyncWith done")
第一部分的主体逻辑和调用函数如下。
·调用r.listerWatcher.List获取资源数据。
·调用listMetaInterface.GetResourceVersion获取资源的版本号。
·调用meta.ExtractList将资源转换成对象列表。
·调用r.syncWith将资源对象列表中的资源对象和对应的版本号存入DeltaFIFO。
第二部分示例如下所示。
//调用r.setLastSyncResourceVersion设置最新的资源版本号 r.setLastSyncResourceVersion(resourceVersion) initTrace.Step("Resource version updated") return nil }(); err != nil { return err } resyncerrc := make(chan error, 1) cancelCh := make(chan struct{}) defer close(cancelCh) go func() { resyncCh, cleanup := r.resyncChan() defer func() { cleanup() }() for { select { case <-resyncCh: case <-stopCh: return case <-cancelCh: return } if r.ShouldResync == nil || r.ShouldResync() { klog.V(4).Infof("%s: forcing resync", r.name) if err := r.store.Resync(); err != nil { resyncerrc <- err return } } cleanup() resyncCh, cleanup = r.resyncChan() } }() for { select { case <-stopCh: return nil default: } timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0)) options = metav1.ListOptions{ ResourceVersion: resourceVersion, TimeoutSeconds: &timeoutSeconds, AllowWatchBookmarks: true, } start := r.clock.Now() //调用r.listerWatcher.Watch进行资源对象的事件监控 w, err := r.listerWatcher.Watch(options) if err != nil { if utilnet.IsConnectionRefused(err) { <-r.initConnBackoffManager.Backoff().C() continue } return err } //触发事件后调用r.watchHandler将资源对象存入DeltaFIFO中并更新版本号 if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil { if err != errorStopRequested { switch { case isExpiredError(err): t observed object. } } return nil } } }
第二部分的主体逻辑和调用函数如下。
·调用r.setLastSyncResourceVersion设置最新的资源版本号。
·调用r.listerWatcher.Watch进行资源对象的事件监控。
·触发事件后调用r.watchHandler将资源对象存入DeltaFIFO中并更新版本号。
List/Watch方法逻辑中调用的函数较多,感兴趣的读者可以查阅源码。
关于Watch的底层实现,其实是使用HTTP长连接来监听事件的,并且使用了HTTP的分块传输编码(Chunked Transfer Encoding)。根据百度百科上的介绍,分块传输编码是HTTP中的一种数据传输机制,允许HTTP由网页服务器发送给客户端应用(通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在HTTP 1.1版本中提供。
通常,HTTP应答消息中的数据是整个发送的,Content-Length消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及哪里是后续应答消息的开始。然而,使用分块传输编码将数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的大小。通常,数据块的大小是固定的,但也不总是这种情况。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)