Controller-runtime 的 informer分析

informer分析

整个controller-runtime中,我认为informer的功劳是最大的。所以要深入分析一下

informerMap的创建

首先我们在创建的Manager的时候会创建cluster,那么就是在这个时候进行InformersMap的创建操作

cluster, err := cluster.New(func New(config *rest.Config, opts ...Option) (Cluster, error) {
  
  // 这里对options.NewCache进行了赋值(赋给方法) cache.New
	options = setOptionsDefaults(options)

	// 创建缓存
	cache, err := options.NewCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod, Namespace: options.Namespace})
}
    

进入New方法中我们就可以看到NewInformersMap方法。

im := internal.NewInformersMap(config, opts.Scheme, opts.Mapper, *opts.Resync, opts.Namespace, selectorsByGVK, disableDeepCopyByGVK)



func NewInformersMap(config *rest.Config,
	scheme *runtime.Scheme,
	mapper meta.RESTMapper,
	resync time.Duration,
	namespace string,
	selectors SelectorsByGVK,
	disableDeepCopy DisableDeepCopyByGVK,
) *InformersMap {
  
  // 总共初始化了三个informer管理器(就是将结构单独放在一起来管理) 分别是有结构,无结构,元数据。
	return &InformersMap{
		structured:   newStructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),
		unstructured: newUnstructuredInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),
		metadata:     newMetadataInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy),

		Scheme: scheme,
	}
}

// 我们拿structured 来分析,其余两个跟它不尽相同
func newStructuredInformersMap(config *rest.Config, scheme *runtime.Scheme, mapper meta.RESTMapper, resync time.Duration,
	namespace string, selectors SelectorsByGVK, disableDeepCopy DisableDeepCopyByGVK) *specificInformersMap {
	return newSpecificInformersMap(config, scheme, mapper, resync, namespace, selectors, disableDeepCopy, createStructuredListWatch)
}

// 整体就是返回一个specificInformersMap对象。
// 请注意createStructuredListWatch 这个参数,这是一个方法,里面存放了list与watch方法。
// specificInformersMap类型中有一个变量createListWatcher,就是用来绑定它的。当真正开始Start的时候,会调用它返回*cache.ListWatch

资源监听创建

初始化完成cache后会在doWatch中使用

// 这里我只粘贴了一块,因为下面逻辑基本一样,只是监听的资源不一样

func (blder *Builder) doWatch() error {
   // 返回 Reconcile 类型
   typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)
   if err != nil {
      return err
   }
  // 这里注意 因为src是Kind类型,所以在调用src.Start()时是调用的Kind中的方法
   src := &source.Kind{Type: typeForSrc}
   hdler := &handler.EnqueueRequestForObject{}
   allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
  //开始监听
   if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
      return err
   }
   
   ***
  }

func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate)
{
  ***
  // 在doWatch这个环节,!c.Started会变成true,那么会将src进行封装与c.startWatches进行append。资源监控会在manager.start 中进行start
  if !c.Started {
		c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct})
		return nil
	}
  // kind类型
  return src.Start(c.ctx, evthdler, c.Queue, prct...)
}

informer的创建和获取

那么该创建的该初始化的都弄完了接下来就该启动了

// 对你没有看错这是Controller的Start。那它跟informer有什么关联那。
// 首先我们要明白,我们刚才创建的informerMap,并不是informer,可以简单的将,仅仅创建了一个存放informer的Map结构。那么在哪里开始将informer进行创建存放那? 请往下看

func (c *Controller) Start(ctx context.Context) error {
  
  // 大家可还记得在doWatch环节中对src有一个append的操作。那么这里就用上了这些src
  for _, watch := range c.startWatches {
     c.Log.Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))

    // 资源Start
     if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {
        return err
     }
  }
}

// 一定要记住src,是kind类型,我就因为没有选对结构走了很多弯路
func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,
                      prct ...predicate.Predicate) error {
  
  // 这里就是获取informer
  i, lastErr = ks.cache.GetInformer(ctx, ks.Type)
  
  
  // 向informer添加监听事件,这一步会创建监听者,下面会用到
  i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
}


接下来开始 src.start() 的分析

func (ip *informerCache) GetInformer(ctx context.Context, obj client.Object) (Informer, error) {
	// 没错存储在informermap 中的key就是使用的GVK
   gvk, err := apiutil.GVKForObject(obj, ip.Scheme)
   _, i, err := ip.InformersMap.Get(ctx, gvk, obj)
   return i.Informer, err
}
// 这里我们可以看到典型的mapget获取元素,我们只讨论structured一种
func (m *InformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *MapEntry, error) {
	switch obj.(type) {
      ***
	default:
		return m.structured.Get(ctx, gvk, obj)
	}
}


func (ip *specificInformersMap) Get(ctx context.Context, gvk schema.GroupVersionKind, obj runtime.Object) (bool, *MapEntry, error) {
	// 找到了就返回
	i, started, ok := func() (*MapEntry, bool, bool) {
		ip.mu.RLock()
		defer ip.mu.RUnlock()
		i, ok := ip.informersByGVK[gvk]
		return i, ip.started, ok
	}()

  // 没找到那就创建再添加到Map中呗
	if !ok {
		var err error
		if i, started, err = ip.addInformerToMap(gvk, obj); err != nil {
			return started, nil, err
		}
	}

	return started, i, nil
}

// 其实看到这里,informer的创建基本上已经非常明亮了
func (ip *specificInformersMap) addInformerToMap(gvk schema.GroupVersionKind, obj runtime.Object) (*MapEntry, bool, error) {
	ip.mu.Lock()
	defer ip.mu.Unlock()
  // 再判断一遍有没有,万一有嘞
	if i, ok := ip.informersByGVK[gvk]; ok {
		return i, ip.started, nil
	}

	// Create a NewSharedIndexInformer and add it to the map.
  // 英文注释我都不删除了,到了最关键的地方
  // NewSharedIndexInformer,到这里我想大家应该眼前一亮了。终于要到头了
	var lw *cache.ListWatch
  // 这里调用的是我们创建informerMap时的最后一个参数传入的方法
	lw, err := ip.createListWatcher(gvk, ip)
	if err != nil {
		return nil, false, err
	}
  // 创建informer  创建informer 创建informer 重要的事情要说三遍
	ni := cache.NewSharedIndexInformer(lw, obj, resyncPeriod(ip.resync)(), cache.Indexers{
		cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
	})
	rm, err := ip.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		return nil, false, err
	}

	i := &MapEntry{
		Informer: ni,
		Reader: CacheReader{
			indexer:          ni.GetIndexer(),
			groupVersionKind: gvk,
			scopeName:        rm.Scope.Name(),
			disableDeepCopy:  ip.disableDeepCopy.IsDisabled(gvk),
		},
	}
	ip.informersByGVK[gvk] = i

	// 这里判断当前的map中的informer是否都启动了,如果都启动了那就启动这个创建好的,否则等一起
	if ip.started {
		go i.Informer.Run(ip.stop)
	}
	return i, ip.started, nil
}

informer的启动

让我们将视角转移到Manager的启动

func (cm *controllerManager) Start(ctx context.Context) (err error) {
  // 这里一个ADD,没有Start,没有run,那么的空旷,跟informer有什么关系那。
  // 各位看官别急,让我细细道来
  // 首先cm.cluster是一个结构体,他里面有Start方法,划重点Start方法
  // cm.add就是将该结构体添加到runnables数组中,等待循环调用Start
  // Controller的start也是以这种方式进行调用的
  // 虽然思想不错,有结构化。but 真的特别难找
  if err := cm.add(cm.cluster); err != nil {
          return fmt.Errorf("failed to add cluster to runnables: %w", err)
      }
}

让我们进入cluster的Start中进行深层次受虐吧

func (c *cluster) Start(ctx context.Context) error {
   defer c.recorderProvider.Stop(ctx)
  // 嗯,那么的简单,那么的Start。
  // 这里我们可以看到cluster的Start其实就是cacheStart
  // cacheStart不就是informerStart的嘛!!!
   return c.cache.Start(ctx)
}

// 这里我们就可以看到三种管理器的Start,老规矩只研究structured
func (m *InformersMap) Start(ctx context.Context) error {
	go m.structured.Start(ctx)
	go m.unstructured.Start(ctx)
	go m.metadata.Start(ctx)
	<-ctx.Done()
	return nil
}

func (ip *specificInformersMap) Start(ctx context.Context) {
	func() {
		// 你瞧,你看,这个for循环这么的漂亮好看
      	// 遍历我们上面创建的informer进行Run呗
		for _, informer := range ip.informersByGVK {
			go informer.Informer.Run(ctx.Done())
		}
		ip.started = true
		close(ip.startWait)
	}()
	<-ctx.Done()
}

informer结构讲解

接下来的内容虽然跟上面的有关联,但是我认为它更倾向于informer的结构思想分析

首先请想象一下informer结构图,Reflector监听事件然后将事件放到Deltafifo,fifo提醒更新indexer,informer触发监听事件将even存放到workqueue中,然后处理…

// 好进入informer的Run环节
func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {

  // 创建 fifo ,好已经看到一个结构了
   fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
      KnownObjects:          s.indexer,
      EmitDeltaTypeReplaced: true,
   })

   cfg := &Config{
      Queue:            fifo,
      ListerWatcher:    s.listerWatcher,
      ObjectType:       s.objectType,
      FullResyncPeriod: s.resyncCheckPeriod,
      RetryOnError:     false,
      ShouldResync:     s.processor.shouldResync,
	  // 这里请注意 ,这里就是控制器循环读取fifo中的数据并触发的方法,没错是在这里配置的
      Process:           s.HandleDeltas,
      WatchErrorHandler: s.watchErrorHandler,
   }

  
  processorStopCh := make(chan struct{})
  var wg wait.Group
  defer wg.Wait()   
  // 这一步操作就是监听事件,并如何通知给监听者操作。
  // 里面运行两个函数
  // listener.run 调用handler方法将p.nextCh事件传入的workqueue中
  // listener.pop 更新p.nextCh
  wg.StartWithChannel(processorStopCh, s.processor.run)
  
   func() {
      s.startedLock.Lock()
      defer s.startedLock.Unlock()

      s.controller = New(cfg)
      s.controller.(*controller).clock = s.clock
      s.started = true
   }()
  // 控制器运行
   s.controller.Run(stopCh)
}


func (c *controller) Run(stopCh <-chan struct{}) {
	// 好,又发现一个结构 Reflector
	r := NewReflector(
		c.config.ListerWatcher,
		c.config.ObjectType,
		c.config.Queue,
		c.config.FullResyncPeriod,
	)
  
   // 启动 Reflector
	wg.StartWithChannel(stopCh, r.Run)

   // 控制器循环调用的方法
	wait.Until(c.processLoop, time.Second, stopCh)
	wg.Wait()
}

先说 r.Run,就是监听事件然后进行判断事件类型

// watchHandler watches w and keeps *resourceVersion up to date.
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
   eventCount := 0
loop:
   for {
      select {
      case <-stopCh:
         return errorStopRequested
      case err := <-errc:
         return err
      case event, ok := <-w.ResultChan():
         if !ok {
            break loop
         }
         if event.Type == watch.Error {
            return apierrors.FromObject(event.Object)
         }
         if r.expectedType != nil {
            if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
               utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))
               continue
            }
         }
         if r.expectedGVK != nil {
            if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {
               utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))
               continue
            }
         }
         meta, err := meta.Accessor(event.Object)
         if err != nil {
            utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
            continue
         }
         newResourceVersion := meta.GetResourceVersion()
         switch event.Type {
         case watch.Added:
           // 将事件添加到fifo中
            err := r.store.Add(event.Object)
            if err != nil {
               utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))
            }
         case watch.Modified:
           // 同理
            err := r.store.Update(event.Object)
            if err != nil {
               utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))
            }
         case watch.Deleted:
            // TODO: Will any consumers need access to the "last known
            // state", which is passed in event.Object? If so, may need
            // to change this. 同理
            err := r.store.Delete(event.Object)
            if err != nil {
               utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))
            }
         case watch.Bookmark:
            // A `Bookmark` means watch has synced here, just update the resourceVersion
         default:
            utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
         }
         *resourceVersion = newResourceVersion
         r.setLastSyncResourceVersion(newResourceVersion)
         if rvu, ok := r.store.(ResourceVersionUpdater); ok {
            rvu.UpdateResourceVersion(newResourceVersion)
         }
         eventCount++
      }
   }
   return nil
}

再说 c.processLoop 最终会调用HandleDeltas

func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
   // 
   for _, d := range obj.(Deltas) {
      switch d.Type {
      case Sync, Replaced, Added, Updated:
         s.cacheMutationDetector.AddObject(d.Object)
         if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
            if err := s.indexer.Update(d.Object); err != nil {
               return err
            }

            isSync := false
            switch {
            case d.Type == Sync:
               // Sync events are only propagated to listeners that requested resync
               isSync = true
            case d.Type == Replaced:
               if accessor, err := meta.Accessor(d.Object); err == nil {
                  if oldAccessor, err := meta.Accessor(old); err == nil {
                     // Replaced events that didn't change resourceVersion are treated as resync events
                     // and only propagated to listeners that requested resync
                     isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()
                  }
               }
            }
           // 通知监听者
           s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
         } else {
           // indexer 更改
            if err := s.indexer.Add(d.Object); err != nil {
               return err
            }
            s.processor.distribute(addNotification{newObj: d.Object}, false)
         }
      case Deleted:
         if err := s.indexer.Delete(d.Object); err != nil {
            return err
         }
         s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
      }
   }
   return nil
}

Controller对事件的处理

既然informer也将事件发送到workqueue中了,那么Controller是如何进行处理的那?

在 Controller.Start 中

for i := 0; i < c.MaxConcurrentReconciles; i++ {
   go func() {
      defer wg.Done()
     // 这一步就是读取队列并进行处理操作
      for c.processNextWorkItem(ctx) {
      }
   }()
}


func (c *Controller) processNextWorkItem(ctx context.Context) bool {
	obj, shutdown := c.Queue.Get()
	c.reconcileHandler(ctx, obj)
	return true
}


func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {
  	// 更新Metrics
	reconcileStartTS := time.Now()
	defer func() {
		c.updateMetrics(time.Since(reconcileStartTS))
	}()

	// 获取req 如果无效则 forget
	req, ok := obj.(reconcile.Request)
	if !ok {
		c.Queue.Forget(obj)
		return
	}
	ctx = logf.IntoContext(ctx, log)

	// 这一步就是运行我们自定义的Reconcile 
   
	result, err := c.Reconcile(ctx, req)
   // 运行完后的操作
	switch {
      // 失败则进行重试环节,这个在我上篇文章中有说明,重试时间计算。
	case err != nil:
		c.Queue.AddRateLimited(req)

	case result.RequeueAfter > 0:
       // 重试
		c.Queue.Forget(obj)
		c.Queue.AddAfter(req, result.RequeueAfter)
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeueAfter).Inc()
	case result.Requeue:
		c.Queue.AddRateLimited(req)
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelRequeue).Inc()
	default:
		// 处理完成,从queue中删除
		c.Queue.Forget(obj)
		ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelSuccess).Inc()
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值