转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com

由于这部分的代码是在client-go 中,所以使用的源码版本是client-go 1.19

这次讲解我用了很一些图,尽可能的把这个模块给描述清楚,如果感觉对你有所帮助不妨发一封邮件激励一下我~

Informer机制

机制设计

Informer主要有两个作用:

  1. 通过一种叫作 ListAndWatch 的方法,把 APIServer 中的 API 对象缓存在了本地,并负责更新和维护这个缓存。ListAndWatch通过 APIServer 的 LIST API“获取”所有最新版本的 API 对象;然后,再通过 WATCH API 来“监听”所有这些 API 对象的变化;
  2. 注册相应的事件,之后如果监听到的事件变化就会调用事件对应的EventHandler,实现回调。

Informer运行原理如下:

根据流程图来解释一下Informer中几个组件的作用:

  • Reflector:用于监控指定的k8s资源,当资源发生变化时,触发相应的变更事件,如Added事件、Updated事件、Deleted事件,并将器资源对象放到本地DeltaFIFO Queue中;
  • DeltaFIFO:DeltaFIFO是一个先进先出的队列,可以保存资源对象的操作类型;
  • Indexer:用来存储资源对象并自带索引功能的本地存储,Reflector从DeltaFIFO中将消费出来的资源对象存储至Indexer;

Reflector 包会和 apiServer 建立长连接,并使用 ListAndWatch 方法获取并监听某一个资源的变化。List 方法将会获取某个资源的所有实例,Watch 方法则监听资源对象的创建、更新以及删除事件,然后将事件放入到DeltaFIFO Queue中;

然后Informer会不断的从 Delta FIFO Queue 中 pop 增量事件,并根据事件的类型来决定新增、更新或者是删除本地缓存;接着Informer 根据事件类型来触发事先注册好的 Event Handler触发回调函数,然后然后将该事件丢到 Work Queue 这个工作队列中。

实例

将到了go-client部分的代码,我们可以直接通过实例来进行上手跑动,Informers Example代码示例如下:

  1. package main
  2. import (
  3. "flag"
  4. v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  5. "k8s.io/client-go/informers"
  6. "k8s.io/client-go/kubernetes"
  7. "k8s.io/client-go/tools/cache"
  8. "k8s.io/client-go/tools/clientcmd"
  9. "k8s.io/client-go/util/homedir"
  10. "log"
  11. "path/filepath"
  12. "time"
  13. )
  14. func main() {
  15. var kubeconfig *string
  16. //如果是windows,那么会读取C:\Users\xxx\.kube\config 下面的配置文件
  17. //如果是linux,那么会读取~/.kube/config下面的配置文件
  18. if home := homedir.HomeDir(); home != "" {
  19. kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
  20. } else {
  21. kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
  22. }
  23. flag.Parse()
  24. config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
  25. if err != nil {
  26. panic(err)
  27. }
  28. clientset, err := kubernetes.NewForConfig(config)
  29. if err != nil {
  30. panic(err)
  31. }
  32. stopCh := make(chan struct{})
  33. defer close(stopCh)
  34. //表示每分钟进行一次resync,resync会周期性地执行List操作
  35. sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute)
  36. informer := sharedInformers.Core().V1().Pods().Informer()
  37. informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  38. AddFunc: func(obj interface{}) {
  39. mObj := obj.(v1.Object)
  40. log.Printf("New Pod Added to Store: %s", mObj.GetName())
  41. },
  42. UpdateFunc: func(oldObj, newObj interface{}) {
  43. oObj := oldObj.(v1.Object)
  44. nObj := newObj.(v1.Object)
  45. log.Printf("%s Pod Updated to %s", oObj.GetName(),nObj.GetName())
  46. },
  47. DeleteFunc: func(obj interface{}) {
  48. mObj := obj.(v1.Object)
  49. log.Printf("Pod Deleted from Store: %s", mObj.GetName())
  50. },
  51. })
  52. informer.Run(stopCh)
  53. }

要运行这段代码,需要我们将k8s服务器上的~/.kube代码拷贝到本地,我是win10的机器所以拷贝到C:\Users\xxx\.kube中。

informers.NewSharedInformerFactory会传入两个参数,第1个参数clientset是用于与k8s apiserver交互的客户端,第2个参数是代表每分钟会执行一次resync,resync会周期性执行List将所有资源存放再Informer Store中,如果该参数是0,则禁用resync功能。

通过informer.AddEventHandler函数可以为pod资源添加资源事件回调方法,支持3种资源事件回调方法:

  • AddFunc
  • UpdateFunc
  • DeleteFunc

通过名称我们就可以知道是新增、更新、删除时会回调这些方法。

在我们初次执行run方法的时候,可以会将监控的k8s上pod存放到本地,并回调AddFunc方法,如下日志:

  1. 2020/10/17 15:13:10 New Pod Added to Store: dns-test
  2. 2020/10/17 15:13:10 New Pod Added to Store: web-1
  3. 2020/10/17 15:13:10 New Pod Added to Store: fluentd-elasticsearch-nwqph
  4. 2020/10/17 15:13:10 New Pod Added to Store: kube-flannel-ds-amd64-bjmt2
  5. 2020/10/17 15:13:10 New Pod Added to Store: kubernetes-dashboard-65665f84db-jrw6k
  6. 2020/10/17 15:13:10 New Pod Added to Store: mongodb
  7. 2020/10/17 15:13:10 New Pod Added to Store: web-0
  8. ....

源码解析

初始化

shared Informer初始化

shared Informer初始化的时候会调用到informers.NewSharedInformerFactory进行初始化。

文件位置:informers/factory.go

  1. func NewSharedInformerFactory(client kubernetes.Interface, defaultResync time.Duration) SharedInformerFactory {
  2. return NewSharedInformerFactoryWithOptions(client, defaultResync)
  3. }
  4. func NewSharedInformerFactoryWithOptions(client kubernetes.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory {
  5. factory := &sharedInformerFactory{
  6. client: client,
  7. namespace: v1.NamespaceAll,
  8. defaultResync: defaultResync,
  9. informers: make(map[reflect.Type]cache.SharedIndexInformer),
  10. startedInformers: make(map[reflect.Type]bool),
  11. customResync: make(map[reflect.Type]time.Duration),
  12. }
  13. // Apply all options
  14. for _, opt := range options {
  15. factory = opt(factory)
  16. }
  17. return factory
  18. }

NewSharedInformerFactory方法最终会调用到NewSharedInformerFactoryWithOptions初始化一个sharedInformerFactory,在初始化的时候会初始化一个informers,用来缓存不同类型的informer。

informer 初始化

informer初始化会调用sharedInformerFactory的方法进行初始化,并且可以调用不同资源的Informer。

  1. podInformer := sharedInformers.Core().V1().Pods().Informer()
  2. nodeInformer := sharedInformers.Node().V1beta1().RuntimeClasses().Informer()

定义不同资源的Informer可以用来监控node或pod。

通过调用Informer方法会根据类型来创建Informer,同一类资源会共享同一个informer。

文件路径:informers/factory.go


  1. func (f *podInformer) defaultInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
  2. //创建informer
  3. return NewFilteredPodInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
  4. }
  5. func (f *podInformer) Informer() cache.SharedIndexInformer {
  6. //传入上面定义的defaultInformer方法,用于创建informer
  7. return f.factory.InformerFor(&corev1.Pod{}, f.defaultInformer)
  8. }
  9. func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer {
  10. f.lock.Lock()
  11. defer f.lock.Unlock()
  12. //获取informer类型
  13. informerType := reflect.TypeOf(obj)
  14. //查找map缓存,如果存在,那么直接返回
  15. informer, exists := f.informers[informerType]
  16. if exists {
  17. return informer
  18. }
  19. //根据类型查找resync的周期
  20. resyncPeriod, exists := f.customResync[informerType]
  21. if !exists {
  22. resyncPeriod = f.defaultResync
  23. }
  24. //调用defaultInformer方法创建informer
  25. informer = newFunc(f.client, resyncPeriod)
  26. f.informers[informerType] = informer
  27. return informer
  28. }

调用InformerFor方法的时候会传入defaultInformer方法用于创建informer。

InformerFor方法里面首先会去sharedInformerFactory的map缓存中根据类型查找对应的informer,如果存在那么直接返回,如果不存在,那么则会调用newFunc方法创建informer,然后设置到informers缓存中。

下面我们看一下NewFilteredPodInformer是如何创建Informer的:

文件位置:informers/core/v1/pod.go

  1. func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
  2. return cache.NewSharedIndexInformer(
  3. &cache.ListWatch{
  4. ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
  5. if tweakListOptions != nil {
  6. tweakListOptions(&options)
  7. }
  8. //调用apiserver获取pod列表
  9. return client.CoreV1().Pods(namespace).List(context.TODO(), options)
  10. },
  11. WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
  12. if tweakListOptions != nil {
  13. tweakListOptions(&options)
  14. }
  15. //调用apiserver监控pod列表
  16. return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
  17. },
  18. },
  19. &corev1.Pod{},
  20. resyncPeriod,
  21. indexers,
  22. )
  23. }

这里是真正的创建一个informer,并注册了List&Watch的回调函数,list回调函数的api类似下面这样:

  1. result = &v1.PodList{}
  2. err = c.client.Get().
  3. Namespace(c.ns).
  4. Resource("pods").
  5. VersionedParams(&opts, scheme.ParameterCodec).
  6. Timeout(timeout).
  7. Do(ctx).
  8. Into(result)

构造Informer通过NewSharedIndexInformer完成:

  1. func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {
  2. realClock := &clock.RealClock{}
  3. sharedIndexInformer := &sharedIndexInformer{
  4. processor: &sharedProcessor{clock: realClock},
  5. indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers),
  6. listerWatcher: lw,
  7. objectType: exampleObject,
  8. resyncCheckPeriod: defaultEventHandlerResyncPeriod,
  9. defaultEventHandlerResyncPeriod: defaultEventHandlerResyncPeriod,
  10. cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", exampleObject)),
  11. clock: realClock,
  12. }
  13. return sharedIndexInformer
  14. }

sharedIndexInformer里面会创建sharedProcessor,设置List&Watch的回调函数,创建了一个indexer,我们这里看一下NewIndexer是怎么创建indexer的:

  1. func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
  2. return &cache{
  3. cacheStorage: NewThreadSafeStore(indexers, Indices{}),
  4. keyFunc: keyFunc,
  5. }
  6. }

NewIndexer方法创建了一个cache,它的keyFunc是DeletionHandlingMetaNamespaceKeyFunc,即接受一个object,生成它的namepace/name的字符串。cache里面的数据会存放到cacheStorage中,它是一个threadSafeMap用来存储资源对象并自带索引功能的本地存储。

注册EventHandler事件

EventHandler事件的注册是通过informer的AddEventHandler方法进行的。在调用AddEventHandler方法的时候,传入一个cache.ResourceEventHandlerFuncs结构体:

文件位置:tools/cache/shared_informer.go

  1. func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) {
  2. s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
  3. }
  4. func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {
  5. s.startedLock.Lock()
  6. defer s.startedLock.Unlock()
  7. ...
  8. //初始化监听器
  9. listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize)
  10. //如果informer还没启动,那么直接将监听器加入到processor监听器列表中
  11. if !s.started {
  12. s.processor.addListener(listener)
  13. return
  14. }
  15. //如果informer已经启动,那么需要加锁
  16. s.blockDeltas.Lock()
  17. defer s.blockDeltas.Unlock()
  18. s.processor.addListener(listener)
  19. //然后将indexer中缓存的数据写入到listener中
  20. for _, item := range s.indexer.List() {
  21. listener.add(addNotification{newObj: item})
  22. }
  23. }

AddEventHandler方法会调用到AddEventHandlerWithResyncPeriod方法中,然后调用newProcessListener初始化listener。

接着会校验informer是否已经启动,如果没有启动,那么直接将监听器加入到processor监听器列表中并返回;如果informer已经启动,那么需要加锁将监听器加入到processor监听器列表中,然后将indexer中缓存的数据写入到listener中。

需要注意的是listener.add方法会调用processorListener的add方法,这个方法会将数据写入到addCh管道中:

  1. func (p *processorListener) add(notification interface{}) {
  2. p.addCh <- notification
  3. }

addCh管道里面数据是用来处理事件回调的,后面我会说到。

大致的流程如下:

启动Informer模块

最后我们在上面的demo中会使用sharedIndexInformer的Run方法来启动Informer模块。

文件位置:tools/cache/shared_informer.go

  1. func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
  2. defer utilruntime.HandleCrash()
  3. //初始化DeltaFIFO队列
  4. fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
  5. KnownObjects: s.indexer,
  6. EmitDeltaTypeReplaced: true,
  7. })
  8. cfg := &Config{
  9. //设置Queue为DeltaFIFO队列
  10. Queue: fifo,
  11. //设置List&Watch的回调函数
  12. ListerWatcher: s.listerWatcher,
  13. ObjectType: s.objectType,
  14. //设置Resync周期
  15. FullResyncPeriod: s.resyncCheckPeriod,
  16. RetryOnError: false,
  17. //判断有哪些监听器到期需要被Resync
  18. ShouldResync: s.processor.shouldResync,
  19. Process: s.HandleDeltas,
  20. WatchErrorHandler: s.watchErrorHandler,
  21. }
  22. func() {
  23. s.startedLock.Lock()
  24. defer s.startedLock.Unlock()
  25. //异步创建controller
  26. s.controller = New(cfg)
  27. s.controller.(*controller).clock = s.clock
  28. s.started = true
  29. }()
  30. processorStopCh := make(chan struct{})
  31. var wg wait.Group
  32. defer wg.Wait() // Wait for Processor to stop
  33. defer close(processorStopCh) // Tell Processor to stop
  34. wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run)
  35. //调用run方法启动processor
  36. wg.StartWithChannel(processorStopCh, s.processor.run)
  37. defer func() {
  38. s.startedLock.Lock()
  39. defer s.startedLock.Unlock()
  40. s.stopped = true
  41. }()
  42. //启动controller
  43. s.controller.Run(stopCh)
  44. }

这段代码主要做了以下几件事:

  1. 调用NewDeltaFIFOWithOptions方法初始化DeltaFIFO队列;
  2. 初始化Config结果体,作为创建controller的参数;
  3. 异步创建controller;
  4. 调用run方法启动processor;
  5. 调用run方法启动controller;

下面我们看看sharedProcessor的run方法做了什么:

  1. func (p *sharedProcessor) run(stopCh <-chan struct{}) {
  2. func() {
  3. ...
  4. //遍历监听器
  5. for _, listener := range p.listeners {
  6. //下面两个方法是核心的事件call back的方法
  7. p.wg.Start(listener.run)
  8. p.wg.Start(listener.pop)
  9. }
  10. p.listenersStarted = true
  11. }()
  12. ...
  13. }

run方法会调用processorListener的run方法和pop方法,这两个方法合在一起完成了事件回调。

  1. func (p *processorListener) add(notification interface{}) {
  2. p.addCh <- notification
  3. }
  4. func (p *processorListener) pop() {
  5. defer utilruntime.HandleCrash()
  6. defer close(p.nextCh) // Tell .run() to stop
  7. var nextCh chan<- interface{}
  8. var notification interface{}
  9. for {
  10. select {
  11. case nextCh <- notification:
  12. // Notification dispatched
  13. var ok bool
  14. notification, ok = p.pendingNotifications.ReadOne()
  15. if !ok { // Nothing to pop
  16. nextCh = nil // Disable this select case
  17. }
  18. case notificationToAdd, ok := <-p.addCh:
  19. if !ok {
  20. return
  21. }
  22. if notification == nil {
  23. notification = notificationToAdd
  24. nextCh = p.nextCh
  25. } else {
  26. p.pendingNotifications.WriteOne(notificationToAdd)
  27. }
  28. }
  29. }
  30. }

这段代码,我把add方法也贴到这里了,是因为监听的事件都是从这个方法传入的,然后写入到addCh管道中。

pop方法在select代码块中会获取addCh管道中的数据,第一个循环的时候notification是nil,所以会将nextCh设置为p.nextCh;第二个循环的时候会将数据写入到nextCh中。

当notification不为空的时候是直接将数据存入pendingNotifications缓存中的,取也是从pendingNotifications中读取。

下面我们看看run方法:

  1. func (p *processorListener) run() {
  2. stopCh := make(chan struct{})
  3. wait.Until(func() {
  4. for next := range p.nextCh {
  5. switch notification := next.(type) {
  6. case updateNotification:
  7. p.handler.OnUpdate(notification.oldObj, notification.newObj)
  8. case addNotification:
  9. p.handler.OnAdd(notification.newObj)
  10. case deleteNotification:
  11. p.handler.OnDelete(notification.oldObj)
  12. default:
  13. utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))
  14. }
  15. }
  16. // the only way to get here is if the p.nextCh is empty and closed
  17. close(stopCh)
  18. }, 1*time.Second, stopCh)
  19. }

run每秒遍历一次nextCh中的数据,然后根据不同的notification类型执行不同的回调方法,这里会回调到我们在main方法中注册的eventHandler。

下面我们再回到sharedIndexInformer的Run方法中往下走,会运行controller的Run方法。

文件位置:tools/cache/controller.go

  1. func (c *controller) Run(stopCh <-chan struct{}) {
  2. ...
  3. //创建Reflector
  4. r := NewReflector(
  5. c.config.ListerWatcher,
  6. c.config.ObjectType,
  7. c.config.Queue,
  8. c.config.FullResyncPeriod,
  9. )
  10. ...
  11. //启动Reflector
  12. wg.StartWithChannel(stopCh, r.Run)
  13. //每秒中循环调用DeltaFIFO队列的pop方法,
  14. wait.Until(c.processLoop, time.Second, stopCh)
  15. wg.Wait()
  16. }

这里对应Informer运行原理里面Informer上部分创建Reflector并进行监听,和下部分循环调用DeltaFIFO队列的pop方法进行分发。

启动Reflector进行监听

Reflector的Run方法最后会调用到Reflector的ListAndWatch方法进行监听获取资源。ListAndWatch代码会分为两部分,一部分是List,一部分是Watch。

我们先看List部分代码:

代码位置:tools/cache/reflector.go

  1. func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
  2. ...
  3. if err := func() error {
  4. ...
  5. go func() {
  6. defer func() {
  7. if r := recover(); r != nil {
  8. panicCh <- r
  9. }
  10. }()
  11. pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
  12. //根据参数获取pod 列表
  13. return r.listerWatcher.List(opts)
  14. }))
  15. ...
  16. list, paginatedResult, err = pager.List(context.Background(), options)
  17. ...
  18. close(listCh)
  19. }()
  20. ...
  21. //获取资源版本号
  22. resourceVersion = listMetaInterface.GetResourceVersion()
  23. initTrace.Step("Resource version extracted")
  24. //将资源数据转换成资源对象列表
  25. items, err := meta.ExtractList(list)
  26. ...
  27. //将资源对象列表中的资源对象和资源版本号存储至DeltaFIFO队列中
  28. if err := r.syncWith(items, resourceVersion); err != nil {
  29. return fmt.Errorf("unable to sync list result: %v", err)
  30. }
  31. ...
  32. r.setLastSyncResourceVersion(resourceVersion)
  33. return nil
  34. }(); err != nil {
  35. return err
  36. }
  37. ...
  38. }

这部分的代码会分为如下几个部分:

  1. 调用listerWatcher.List方法,获取资源下的所有对象的数据,这个方法会通过api调用到apiServer获取资源列表,代码我在上面已经贴出来了;
  2. 调用listMetaInterface.GetResourceVersion获取资源版本号;
  3. 调用meta.ExtractList方法将资源数据转换成资源对象列表;
  4. 将资源对象列表中的资源对象和资源版本号存储至DeltaFIFO队列中;
  5. 最后调用setLastSyncResourceVersion方法更新资源版本号;

下面看看Watch部分的代码:

  1. func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
  2. ...
  3. for {
  4. ...
  5. //调用clientset客户端api与apiServer建立长连接,监控指定资源的变更
  6. w, err := r.listerWatcher.Watch(options)
  7. ...
  8. //处理资源的变更事件
  9. if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
  10. ...
  11. return nil
  12. }
  13. }
  14. }

这里会循环调用clientset客户端api与apiServer建立长连接,监控指定资源的变更,如果监控到有资源变更,那么会调用watchHandler处理资源的变更事件。

  1. func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
  2. ...
  3. loop:
  4. for {
  5. select {
  6. case <-stopCh:
  7. return errorStopRequested
  8. case err := <-errc:
  9. return err
  10. case event, ok := <-w.ResultChan():
  11. ...
  12. // 获取资源版本号
  13. newResourceVersion := meta.GetResourceVersion()
  14. switch event.Type {
  15. //将添加资源事件添加到DeltaFIFO队列中
  16. case watch.Added:
  17. err := r.store.Add(event.Object)
  18. if err != nil {
  19. utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))
  20. }
  21. //将更新资源事件添加到DeltaFIFO队列中
  22. case watch.Modified:
  23. err := r.store.Update(event.Object)
  24. if err != nil {
  25. utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))
  26. }
  27. //将删除资源事件添加到DeltaFIFO队列中
  28. case watch.Deleted:
  29. err := r.store.Delete(event.Object)
  30. if err != nil {
  31. utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))
  32. }
  33. ...
  34. *resourceVersion = newResourceVersion
  35. r.setLastSyncResourceVersion(newResourceVersion)
  36. eventCount++
  37. }
  38. }
  39. ...
  40. }

watchHandler方法会根据传入的资源类型调用不同的方法转换成不同的Delta然后存入到DeltaFIFO队列中。

processLoop分发DeltaFIFO队列中任务

processLoop方法,以1s为周期,周期性的执行。

文件位置:tools/cache/controller.go

  1. func (c *controller) processLoop() {
  2. for {
  3. obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))
  4. if err != nil {
  5. if err == ErrFIFOClosed {
  6. return
  7. }
  8. if c.config.RetryOnError {
  9. // This is the safe way to re-enqueue.
  10. c.config.Queue.AddIfNotPresent(obj)
  11. }
  12. }
  13. }
  14. }

这里会循环将DeltaFIFO队列中数据pop出队,然后交给Process方法进行处理,Process方法是在上面调用sharedIndexInformer的Run方法的数据设置,设置的方法是sharedIndexInformer的HandleDeltas方法。

  1. func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
  2. s.blockDeltas.Lock()
  3. defer s.blockDeltas.Unlock()
  4. // from oldest to newest
  5. //根据obj的Type类型进行分发
  6. for _, d := range obj.(Deltas) {
  7. switch d.Type {
  8. case Sync, Replaced, Added, Updated:
  9. s.cacheMutationDetector.AddObject(d.Object)
  10. //如果缓存中存在该对象
  11. if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
  12. //更新indexr
  13. if err := s.indexer.Update(d.Object); err != nil {
  14. return err
  15. }
  16. isSync := false
  17. switch {
  18. case d.Type == Sync:
  19. // Sync events are only propagated to listeners that requested resync
  20. isSync = true
  21. case d.Type == Replaced:
  22. //新老对象获取版本号进行比较
  23. if accessor, err := meta.Accessor(d.Object); err == nil {
  24. if oldAccessor, err := meta.Accessor(old); err == nil {
  25. // Replaced events that didn't change resourceVersion are treated as resync events
  26. // and only propagated to listeners that requested resync
  27. isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()
  28. }
  29. }
  30. }
  31. s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
  32. // 如果缓存中不存在该对象
  33. } else {
  34. if err := s.indexer.Add(d.Object); err != nil {
  35. return err
  36. }
  37. s.processor.distribute(addNotification{newObj: d.Object}, false)
  38. }
  39. case Deleted:
  40. if err := s.indexer.Delete(d.Object); err != nil {
  41. return err
  42. }
  43. s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
  44. }
  45. }
  46. return nil
  47. }

HandleDeltas会与indexer缓存交互更新我们从Delta FIFO中取到的内容,之后通过s.processor.distribute()进行消息的分发。

在distribute中,sharedProcesser通过listener.add(obj)向每个listener分发该object。而该函数中又执行了p.addCh <- notification

  1. func (p *sharedProcessor) distribute(obj interface{}, sync bool) {
  2. p.listenersLock.RLock()
  3. defer p.listenersLock.RUnlock()
  4. if sync {
  5. for _, listener := range p.syncingListeners {
  6. listener.add(obj)
  7. }
  8. } else {
  9. for _, listener := range p.listeners {
  10. listener.add(obj)
  11. }
  12. }
  13. }

这里可以结合上面的p.wg.Start(listener.run)p.wg.Start(listener.pop)方法来进行理解,这里将notification传入到addCh管道之后会触发EventHandler事件。

这里我用一张图总结一下informer的Run方法流程:

至此,我们分析完了informer的所有机制。

总结

通过上面分析,我们全面熟悉了k8s是如何通过Informer机制实现ListAndWatch获取并监视 API 对象变化。

熟悉了Informer与Reflector是如何协同进行数据的传递,但是我这里有点遗憾的是限于篇幅,没有去详细的讲解DeltaFIFO队列里面是如何进行数据的存储与获取,实际上这个队列的实现也是非常的有意思的。

对于Indexer来说,我在文章里面也只说到了获取DeltaFIFO队列的数据后更新到Indexer的ThreadSafeMap中,但是并没有讲ThreadSafeMap这个存储是如何做的,里面的索引又是如何建立的,这些各位同学感兴趣的也可以去研究一下。

Reference

https://www.kubernetes.org.cn/2693.html

https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md

https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/

https://mp.weixin.qq.com/s?__biz=MzU1OTAzNzc5MQ==&mid=2247484052&idx=1&sn=cec9f4a1ee0d21c5b2c51bd147b8af59&chksm=fc1c2ea4cb6ba7b283eef5ac4a45985437c648361831bc3e6dd5f38053be1968b3389386e415&scene=21#wechat_redirect

16.深入k8s:Informer使用及其源码分析的更多相关文章

  1. Qt QComboBox之setEditable和currentTextChanged及其源码分析

    目录 Qt QComboBox之setEditable和currentTextChanged以及其源码分析 前言 问题的出现 问题分析 currentTextChanged信号触发 源码分析 Qt Q ...

  2. hadoop之hdfs------------------FileSystem及其源码分析

    FileSystem及其源码分析 FileSystem这个抽象类提供了丰富的方法用于对文件系统的操作,包括上传.下载.删除.创建等.这里多说的文件系统通常指的是HDFS(DistributedFile ...

  3. 9.深入k8s:调度器及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 这次讲解的是k8s的调度器部分的代码,相对来说比较复杂,慢慢的梳理清 ...

  4. 8.深入k8s:资源控制Qos和eviction及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com,源码版本是1.19 又是一个周末,可以愉快的坐下来静静的品味一段源码,这一篇涉及到资源的 ...

  5. 13.深入k8s:Pod 水平自动扩缩HPA及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 Pod 水平自动扩缩 Pod 水平自动扩缩工作原理 Pod 水平自动 ...

  6. 14.深入k8s:kube-proxy ipvs及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 这一篇是讲service,但是基础使用以及基本概念由于官方实在是写的 ...

  7. 15.深入k8s:Event事件处理及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 概述 k8s的Event事件是一种资源对象,用于展示集群内发生的情况 ...

  8. 5.深入Istio源码:Pilot-agent作用及其源码分析

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 本文使用的Istio源码是 release 1.5. 介绍 Sidecar在注入的时候会 ...

  9. pdfmake.js使用及其源码分析

    公司项目在需要将页面的文本导出成DPF,和支持打印时,一直没有做过这样的功能,花了一点时间将其做了出来,并且本着开源的思想和技术分享的目的,将自己的编码经验分享给大家,希望对大家有用. 现在是有一个文 ...

随机推荐

  1. Spring框架学习笔记(1)

    Spring 框架学习笔记(1) 一.简介 Rod Johnson(spring之父) Spring是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架, ...

  2. 一篇文章教你快速上手接口管理工具swagger

    一.关于swagger 1.什么是swagger? swagger是spring fox的一套产品,可以作为后端开发者测试接口的工具,也可以作为前端取数据的接口文档. 2.为什么使用? 相比于传统的接 ...

  3. linux系统漏洞扫描工具lynis

    lynis 是一款运行在 Unix/Linux 平台上的基于主机的.开源的安全审计软件.Lynis是针对Unix/Linux的安全检查工具,可以发现潜在的安全威胁.这个工具覆盖可疑文件监测.漏洞.恶意 ...

  4. Python 3 列表

    列表:是可变的序列,也是一种可以存储各种数据类型的集合,用中括号([])表示列表的开始和结束,元素之间用逗号(,)分隔.列表中每个元素提供一个对应的下标. 1.列表的基本格式表示: 2.列表的不同数据 ...

  5. python字符串和列表小案例

    python 目录 python 一.字符串 1.给定一个字符串,利用切片将字符串反转 2.给定一个字符串,将空格替换为逗号 3.给定一个字符串,大写改为小写 4.str = '' ,li = ['l ...

  6. SpringBoot中使用切面的每次传的参数,进行解析,验签,并返回解码后的参数

    目的,在每次请求的时候,对每次传的参数,进行解析,验签,并返回解码后的参数, 以json传递: 例子背景: IOT平台提供对外可访问的接口, 需要对所有参数的传递做到 不泄露.认证的目的:所以需要在每 ...

  7. 软件工程与UML作业2

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018SE ...

  8. 9.Kafka API使用

  9. java基础整理总结篇(1)

    >>java数据区域,大致分以下几种 寄存器:位于cpu内部,寄存器的数量有限,所以寄存器根据需求分配.不能直接控制它. 堆栈:位于通用RAM(随机访问存储器)中,通过堆栈指针可以从处理器 ...

  10. sql 注入初探

    Sql注入:就是将恶意的sql语句插入到用户输入的参数当中并带入数据库中查询并在浏览器返回不该显示的信息 寻找sql注入点: 1.要有参数值的传递(url当中GET型的.注册用户名的页面.登录框.留言 ...