在client-go中,informer是一个核心的概念,用于从Kubernetes API服务器中读取对象。它可以监视一个或多个API对象,并在对象发生变化时自动更新本地缓存。
Informer主要组成部分:
-
sharedIndexInformer
sharedIndexInformer是informer的核心组件。它负责从API服务器获取对象并更新本地缓存。在更新缓存后,它会触发事件处理器,以便通知其他组件对象已经更新。
-
informerSyncHandler
informerSyncHandler是sharedIndexInformer的事件处理程序,它将缓存中的对象与API服务器中的对象进行比较,并更新缓存。当处理完所有的更新操作后,informerSyncHandler会将更新后的对象发送到事件队列。
-
informerEventHandler
informerEventHandler是事件处理程序的接口,用于接收事件并执行特定的操作。当sharedIndexInformer接收到更新事件时,它将调用informerEventHandler来处理事件
-
indexer
indexer是informer的本地缓存,用于存储从API服务器获取的对象。indexer使用map数据结构存储对象,其中键是对象的名称,值是对象本身。此外,indexer还使用索引数据结构(例如Set、List)来优化查找和筛选操作。每个indexer都关联一个ObjectStore,ObjectStore是一个更高级别的抽象,用于管理一组API对象
-
watcher
watcher是informer的事件源,用于从API服务器中获取对象并将它们发送到事件队列中。watcher实现了Kubernetes API服务器上的watch机制,它会定期向API服务器发送请求,以获取当前对象的状态。在获取状态后,watcher将对比上一次请求的结果,找到任何发生变化的对象,并将它们发送到事件队列中
在运行时,informer将indexer和watcher组合在一起,以便实现对象的监视和更新。当watcher从API服务器中获取到对象时,它会将它们添加到indexer中。如果对象已经存在于indexer中,则watcher将更新对象的状态。一旦对象被更新,informerSyncHandler将被触发,以便将更新后的对象发送到事件队列中。在事件处理程序中,可以通过调用indexer对象的方法来访问缓存中的对象
画了一个可能不太标准的图:

了解了Informer架构和Reflector、DeltaFIFO、Indexer几个组件后,再回头来看示例中的SharedInformer使用,它如何将前面几个组件关联起来
示例程序
- 创建Informer对象
- 注册事件处理程序
- 启动Informer
以下面代码为例:
|
|
执行逻辑
- NewSharedInformerFactory创建Informer对象,函数中传入了clientset和defaultResync参数,clientset是访问APIServer的具体实现,resyncPeriod指定了informer在从API服务器中获取数据之间等待的时间,sharedInformerFactory的informers字段是一个map结构,每种资源类型为key,对应value是SharedIndexInformer
|
|
- 每种kubernetes内置资源都已经实现了资源对应的Informer,通过
sharedInformerFactory.Apps().V1().Deployments()链式调用构造出deployInformer,遵循组、版本、资源(即GVK)格式,其他k8s资源的informer创建也是一样的操作
|
|
deployInformer.Informer()用deployInformer创建deployment对应的SharedIndexInformer。这里面初始化了重要的两个对象:cache.Indexers和ListerWatcher
|
|
- 创建 DeploymentLister,它里面就一个字段indexer,indexer是索引器对象,可以在本地缓存中根据索引取数。如从本地缓存indexer中获取 default 名称空间的所有 deployment 列表:
deployments, err := deployLister.Deployments("default").List(labels.Everything())
|
|
至此,我们有了sharedIndexInformer,其已拥有重要的字段cache.Indexers和ListerWatcher已初始化,但目前还没有启动ListerWatcher去从APIServer拿数据,没有把这些数据包装为Delta缓存下来,没有存入到Indexer,也没有后续对数据的处理逻辑
-
给SharedIndexInformer添加事件处理器方法,
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{AddFunc: onAdd, UpdateFunc: onUpdate, DeleteFunc: onDelete})这里面把事件对象处理函数onAdd、onUpdate、onDelete用ResourceEventHandlerFuncs结构体封装,然后由processorListener管理,这个processorListener也是比较核心的一个对象,拥有一个RingGrowingBuffer和两个通道addCh、nextCh,它实现了事件的缓冲和处理,无事件时阻塞等待,有事件时发送给handlers,处理不过来呢就先放到环形缓冲区中。processorListener会由sharedProcessor管理,若sharedProcessor已启动,则开启俩协程分别运行新添加的processorListener的run和pop两个方法,这两个方法利用无缓冲通道相互依赖,没有事件的时候都处于阻塞状态。
其中,run方法里面用for range不断轮询nextCh通道,接收事件数据并交给我们添加的对应的EventHandlerFunc处理,那么nextCh通道里面的事件从哪里来呢?从addCh通道来
pop方法的实现就很巧妙,从addCh通道接收到数据后,发送给nextCh通道,并由run方法消费(随即交给EventHandlerFunc处理),run消费的慢喽那么后续的notification会被放到ringbuffer里面。addCh通道的数据从哪里来呢?是DeltaFIFO,controller.processLoop方法调用DeltaFIFO的pop方法,最终将Deltas交给了addCh通道
|
|
|
|
至此,我们添加了事件的处理函数,并等待事件的到来。但目前还是没有启动ListerWatcher去从APIServer拿数据,没有把这些数据包装为Delta缓存下来,没有存入到Indexer
- 启动SharedIndexInformer,
sharedInformerFactory.Start(stopper),在此过程中,首先构造了DeltaFIFO、Config和Controller三个对象,并执行controller.Run(stopCh),Run方法中构造了Reflector对象,并启动Reflector开始ListAndWatch,并执行controller.processLoop方法,它从DeltaFIFO中pop出对象,交给sharedIndexInformer.HandleDeltas()处理
|
|
Controller通过DeltaFIFO.Pop()函数弹出Deltas,并由sharedIndexInformer.HandleDeltas()函数处理,此函数的逻辑就是更新indexer,并分发Deltas到事件处理器,分发实际上就是把Deltas对象发送到processorListener的addCh通道,至此从监听事件到消费事件形成一个完整的处理流程
|
|
总结
controller是Informer机制的控制核心,它把Reflector、DeltaFIFO、ResourceEventHandlerFuncs、Indexer、Pop等组件串了起来,使其成为一个运行中的完整功能。