controller-runtime之Controller启动分析(2)

Controller 是 Controller-runtime 的 核心结构,Controller 管理一个工作队列,并从 source.Sources 中获取 reconcile.Requests 加入队列, 通过执行 reconcile.Reconciler 来处理队列中的每项 reconcile.Requests, 而 reconcile. Reconciler 可以通过读写 Kubernetes 资源来确保集群状态与期望状态一致。

1、Controller 接口定义:

Controller 接口中很重要的两个函数 Watch 与 Start。


// pkg/controller/controller.go
type Controller interface {  
   // 匿名接口,定义了 Reconcile(context.Context,Request)(Result,error)。 
   reconcile.Reconciler
   
   // Watch() 方法会从 source.Source 中获取 Event
   // 根据参数 Eventhandler 来决定如何入队
   // 根据 predictes 进行 Even 过滤
   Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error  
    
    // Controller 的启动方法。实现了 Controller 和 Runnable 接口,则可被Manager管理
   Start(ctx context.Context) error  
  
   // 日志输出   
   GetLogger() logr.Logger  
}

2、Controller 结构体定义:


// pkg/internal/controller/controller.go
type Controller struct {  
   // Name 用于跟踪、记录和监控中控制器的唯一标识,必填字段 
   Name string  
  
   // 运行最大并发 Reconciles 数量,默认值为1 
   MaxConcurrentReconciles int  
   
   // Reconcile() 能在任意时刻被调用,接收一个对象的 Name 与 Namespace,并同步集 群当前实际状态至该对象被设置的期望状态。
   Do reconcile.Reconciler  
  
   // 一旦控制器准备好启动,MakeQueue 就会为这个控制器构造工作队列 
   MakeQueue func() workqueue.RateLimitingInterface  
  
   // 队列通过监听来自 Infomer 的事件,添加对象键到队列中进行处理 
   Queue workqueue.RateLimitingInterface  
  
   // SetFields 用来将依赖关系注入到其他对象,比如 Sources、EventHandlers 以及 Predicates
   SetFields func(i interface{}) error  
  
   // mu is used to synchronize Controller setup   
   mu sync.Mutex  
  
   // Started is true if the Controller has been Started  
   Started bool   
   ctx context.Context   
   CacheSyncTimeout time.Duration    
   // 维护了一个 sources、handlers 以及 predicates 列表以方便在控制器启动的时候启动
   startWatches []watchDescription  
   LogConstructor func(request *reconcile.Request) logr.Logger
   // RecoverPanic 指示是否应恢复由 reconcile 引起的恐慌 
   RecoverPanic bool  
}

Controller 的主要逻辑 Controller.Start() 方法

3、Watch 方法


// watch 就是启动 source.start
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error {  
   c.mu.Lock()  
   defer c.mu.Unlock()  
  
   // Inject Cache into arguments  
   if err := c.SetFields(src); err != nil {  
      return err  
   }  
   if err := c.SetFields(evthdler); err != nil {  
      return err  
   }  
   for _, pr := range prct {  
      if err := c.SetFields(pr); err != nil {  
         return err  
      }  
   }  
  
   // 如果 Controller 还没启动,则把 watches 存放到本地然后返回
   if !c.Started {  
      c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct})  
      return nil  
   }  
  
   c.LogConstructor(nil).Info("Starting EventSource", "source", src) 
   // 调用 src 的 Start 函数 
   return src.Start(c.ctx, evthdler, c.Queue, prct...)  
}

4、Start 方法

start 由manager.Start 触发,消费 workqueue,当 Manager 调用 Start() 方法后,它会 进入 Controller 的启动流程, 经过选举等预处理 Controller 进入 Start() 方法。


func (c *Controller) Start(ctx context.Context) error {  
   // ...
   // 生产工作队列
   c.Queue = c.MakeQueue()  
   go func() {  
      <-ctx.Done()  
      c.Queue.ShutDown()  
   }()  
  
   wg := &sync.WaitGroup{}  
   err := func() error {  
      defer c.mu.Unlock()  
  
      // TODO(pwittrock): Reconsider HandleCrash  
      defer utilruntime.HandleCrash()  
  
      // 启动源之前尝试等待 要同步的缓存,以便他们有机会注册其预期的cache
      for _, watch := range c.startWatches {  
         c.LogConstructor(nil).Info("Starting EventSource", "source", fmt.Sprintf("%s", watch.src))  
  
         if err := watch.src.Start(ctx, watch.handler, c.Queue, watch.predicates...); err != nil {  
            return err  
         }  
      }  
  
      // Start the SharedIndexInformer factories to begin populating the SharedIndexInformer caches  
      c.LogConstructor(nil).Info("Starting Controller")  
  
	  // ...     
      c.startWatches = nil  
  
      // 启动 worker 
      // Controller 根据 MaxConcurrentReconciles 启动多个 Worker 程序, 用于处理队 列中的对象
      c.LogConstructor(nil).Info("Starting workers", "worker count", c.MaxConcurrentReconciles)  
      wg.Add(c.MaxConcurrentReconciles)  
      for i := 0; i < c.MaxConcurrentReconciles; i++ {  
         go func() {  
            defer wg.Done()  
            // 根据MaxConcurrentReconciles并发处理       
            for c.processNextWorkItem(ctx) {  
            }         }()  
      }  
  
      c.Started = true  
      return nil   }()  
   ...
}

processNextWorkItem 方法

Work 程序先从工作队列中获取需要处理的对象,然后调用 reconcileHandler 方法的 Reconcile() 方法进行处理 Reconcile 方法里面再调用 c.Do.Reconcile


// processNextWorkItem 函数,并发处理
func (c *Controller) processNextWorkItem(ctx context.Context) bool {  
   // 从对了取出元素
   obj, shutdown := c.Queue.Get()  
   // 如果队列关闭了,返回false
   if shutdown {  
      // Stop working  
      return false  
   }  
   // 标记为处理完毕
   defer c.Queue.Done(obj)  
  
   ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(1)  
   defer ctrlmetrics.ActiveWorkers.WithLabelValues(c.Name).Add(-1)  

   // 元素处理
   c.reconcileHandler(ctx, obj)  
   return true  
}

reconcileHandler 方法


func (c *Controller) reconcileHandler(ctx context.Context, obj interface{}) {  
   // Update metrics after processing each item  
   reconcileStartTS := time.Now()  
   defer func() {  
      c.updateMetrics(time.Since(reconcileStartTS))  
   }()  
  
   // Make sure that the object is a valid request.  
   req, ok := obj.(reconcile.Request)  
   if !ok {  
      // As the item in the workqueue is actually invalid, we call  
      // Forget here else we'd go into a loop of attempting to      // process a work item that is invalid.      c.Queue.Forget(obj)  
      c.LogConstructor(nil).Error(nil, "Queue item was not a Request", "type", fmt.Sprintf("%T", obj), "value", obj)  
      // Return true, don't take a break  
      return  
   }  
  
   log := c.LogConstructor(&req)  
  
   log = log.WithValues("reconcileID", uuid.NewUUID())  
   ctx = logf.IntoContext(ctx, log)  
  
   // RunInformersAndControllers the syncHandler, passing it the Namespace/Name string of the  
   // resource to be synced.   
   result, err := c.Reconcile(ctx, req)  
   switch {  
   case err != nil:  
      c.Queue.AddRateLimited(req)  
      ctrlmetrics.ReconcileErrors.WithLabelValues(c.Name).Inc()  
      ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelError).Inc()  
      log.Error(err, "Reconciler error")  
   case result.RequeueAfter > 0:  
      // The result.RequeueAfter request will be lost, if it is returned  
      // along with a non-nil error. But this is intended as      
      // We need to drive to stable reconcile loops before queuing due      
      // to result.RequestAfter      
      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:  
      // Finally, if no error occurs we Forget this item so it does not  
      // get queued again until another change happens.      c.Queue.Forget(obj)  
      ctrlmetrics.ReconcileTotal.WithLabelValues(c.Name, labelSuccess).Inc()  
   }  
}

根据 Reconcile() 方法返回的结果, 将对象重新入队列或从队列中删除。 重新 入队列的方法可以是带有一定延迟的 Queue.AddAfter(), 也可以是有限速的 Queue. AddRateLimited()。重新加入的次数无限制。


func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (_ reconcile.Result, err error) {  
   defer func() {  
      if r := recover(); r != nil {  
         if c.RecoverPanic {  
            for _, fn := range utilruntime.PanicHandlers {  
               fn(r)  
            }  
            err = fmt.Errorf("panic: %v [recovered]", r)  
            return  
         }  
  
         log := logf.FromContext(ctx)  
         log.Info(fmt.Sprintf("Observed a panic in reconciler: %v", r))  
         panic(r)  
      }  
   }()  
   // Reconcile 是个接口里面只有一个方法,也就是Reconcile(协调函数),提供给controller,需要开发者自己去实现的。
   return c.Do.Reconcile(ctx, req)  
}

5、Reconciler

Reconciler( 协调器)是提供给 Controller 的一个函数, 可以随时使用对象的 Name 和 Namespace 对其进行调用。当它被调用时,Reconciler 将确保集群中资源的状态和 预设的状态保持一致。例如:ReplicaSet 指定 3 个副本, 但系统中仅存在 2 个 Pod 时, Reconciler 将再创建 1 个 Pod,并向 Pod 的 OwnerReference 中添加该 ReplicaSet 的名称,同时设置“controller=true”属性。


// pkg/reconcile/reconcile.go
type Reconciler interface {  
   Reconcile(context.Context, Request) (Result, error)  
}

Request 包含了需要处理的对象 Name 和 Namespace,而 Result 是决定是否需要将对象重新入队及如何加入队列。


// pkg/reconcile/reconcile.go
type Request struct {  
   // NamespacedName is the name and namespace of the object to reconcile.  
   types.NamespacedName  
}
// pkg/types/namespacedname.go
type NamespacedName struct {  
   Namespace string  
   Name      string  
}

type Result struct {  
   // 通知 Controller 是否需要重新将对象加⼊队列,默认为 False  
   Requeue bool  

   // RequeueAfter 值大于 0 表示 Controller 需要在设置的时间间隔后将对象重新加⼊队列
   RequeueAfter time.Duration  
}

6、Predicate 过滤器

Predicate 主要有以下特性:

  • 接受一个事件,并将该事件是否通过过滤条件的结果返回。如果通过,该事件将 被加入待处理事件队列中。
  • Predicate 是可选项,可以不设置。如果不设置,默认事件都将被加入待处理事件 队列中。
  • 用户可以使用内置的 Predicate,但是可以设置自定义 Predicate。

7、EventHandler(事件句柄)

源码位置:pkg/handler/eventhandler.go

它是 Controller.Watch 的参数,当事件产生时,EventHandler 将返回对象的 Name 和 Namespace, 作为 Request 被添加到待处理事件队列中。


// pkg/predicate/predicate.go
type EventHandler interface {  
   // Create is called in response to an create event - e.g. Pod Creation.   
   Create(event.CreateEvent, workqueue.RateLimitingInterface)  
  
   // Update is called in response to an update event -  e.g. Pod Updated.   
   Update(event.UpdateEvent, workqueue.RateLimitingInterface)  
  
   // Delete is called in response to a delete event - e.g. Pod Deleted.   
   Delete(event.DeleteEvent, workqueue.RateLimitingInterface)  
  
   // Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or   
   // external trigger request - e.g. reconcile Autoscaling, or a Webhook.   
   Generic(event.GenericEvent, workqueue.RateLimitingInterface)  
}

8、resource.Source(事件源)

它提供了一个事件流,事件通常来自 Watch Kubernetes API(如 Pod Create、Update、Delete)。


// pkg/source/source.go
type Source interface {  
   // Start() 是 Controller-runtime 的内部⽅法,应该仅由 Controller 调⽤
   Start(context.Context, handler.EventHandler, workqueue.RateLimitingInterface, ...predicate.Predicate) error  
}

Source 的实现在 pkg/source/source.go 下,主要包括 3 种:

(1)Informer:用于提供来自集群内部的事件,例如,Pod Create 事件。


func (is *Informer) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,  
   prct ...predicate.Predicate) error {  
   // Informer should have been specified by the user.  
   if is.Informer == nil {  
      return fmt.Errorf("must specify Informer.Informer")  
   }  
  
   is.Informer.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})  
   return nil  
}

(2)Kind:与 Informer 类似 , 也用于提供来自集群内部的事件。 不同的是 Informer 结构直接继承 cache.Informer,需要开发者自己设置;而 Kind 会根据设置的资源对象类型, 自动生成 cache.Informer。

Builder 创 建 Controller 时, 会 根 据 Builder.For()、Builder.Owns()、Builder.Watches() 方法中设置的资源对象类型在 Builder.Build() 中创建相应的 Kind, 并调用 Controller. Watch() 方法将 Kind 传入 Controller。


func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,  
   prct ...predicate.Predicate) error {  
   // Type should have been specified by the user.  
   if ks.Type == nil {  
      return fmt.Errorf("must specify Kind.Type")  
   }  
  
   // cache should have been injected before Start was called  
   if ks.cache == nil {  
      return fmt.Errorf("must call CacheInto on Kind before calling Start")  
   }  
  
   ctx, ks.startCancel = context.WithCancel(ctx)  
   ks.started = make(chan error)  
   go func() {  
      var (  
         i       cache.Informer  
         lastErr error  
      )  
  
      // Tries to get an informer until it returns true,  
      // an error or the specified context is cancelled or expired.      
      if err := wait.PollImmediateUntilWithContext(ctx, 10*time.Second, func(ctx context.Context) (bool, error) {  
         // Lookup the Informer from the Cache and add an EventHandler which populates the Queue  
         i, lastErr = ks.cache.GetInformer(ctx, ks.Type)  
         if lastErr != nil {  
            kindMatchErr := &meta.NoKindMatchError{}  
            switch {  
            case errors.As(lastErr, &kindMatchErr):  
               log.Error(lastErr, "if kind is a CRD, it should be installed before calling Start",  
                  "kind", kindMatchErr.GroupKind)  
            case runtime.IsNotRegisteredError(lastErr):  
               log.Error(lastErr, "kind must be registered to the Scheme")  
            default:  
               log.Error(lastErr, "failed to get informer from cache")  
            }  
            return false, nil // Retry.  
         }  
         return true, nil  
      }); err != nil {  
         if lastErr != nil {  
            ks.started <- fmt.Errorf("failed to get informer from cache: %w", lastErr)  
            return  
         }  
         ks.started <- err  
         return  
      }  
  
      i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})  
      if !ks.cache.WaitForCacheSync(ctx) {  
         // Would be great to return something more informative here  
         ks.started <- errors.New("cache did not sync")  
      }  
      close(ks.started)  
   }()  
  
   return nil  
}

(3)Channel:用于提供集群外部的事件,例如,GitHub WebHook 回调。Channel 要 求用户连接外部的源(如 Http Handler), 以便将 GenericEvents 写入底层的 Channel 结 构中。


func (cs *Channel) Start(  
   ctx context.Context,  
   handler handler.EventHandler,  
   queue workqueue.RateLimitingInterface,  
   prct ...predicate.Predicate) error {  
   // Source should have been specified by the user.  
   if cs.Source == nil {  
      return fmt.Errorf("must specify Channel.Source")  
   }  
  
   // stop should have been injected before Start was called  
   if cs.stop == nil {  
      return fmt.Errorf("must call InjectStop on Channel before calling Start")  
   }  
  
   // use default value if DestBufferSize not specified  
   if cs.DestBufferSize == 0 {  
      cs.DestBufferSize = defaultBufferSize  
   }  
  
   dst := make(chan event.GenericEvent, cs.DestBufferSize)  
  
   cs.destLock.Lock()  
   cs.dest = append(cs.dest, dst)  
   cs.destLock.Unlock()  
  
   cs.once.Do(func() {  
      // Distribute GenericEvents to all EventHandler / Queue pairs Watching this source  
      go cs.syncLoop(ctx)  
   })  
  
   go func() {  
      for evt := range dst {  
         shouldHandle := true  
         for _, p := range prct {  
            if !p.Generic(evt) {  
               shouldHandle = false  
               break            }  
         }  
  
         if shouldHandle {  
            handler.Generic(evt, queue)  
         }  
      }  
   }()  
  
   return nil  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值