controller-runtime源码解析(一)

8 阅读7分钟

我们先利用kubebuilder初始化一个简单的config自动备份的项目:

kubebuilder init --domain=syncer.io --repo=configmap-syncer

kubebuilder 脚手架会在文件夹下搭建好基本的项目雏形,然后利用以下命令创建好对应的api:

kubebuilder create api --group=core --version=v1alpha1 --kind=ConfigMapSyncer

然后我们利用生成的代码,分析一下controller-runtime的源码结构。

典型Controller的启动

main.go

// 首先先初始化manager实例
	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
		Scheme:                 scheme,
		...
		WebhookServer:          webhookServer,
		...
	})

以上2个初始化变量,一个是webhookServer。controller-runtime的webhookServer是提供准入控制(Admission)和CRD 默认值填充等功能的。scheme是一个 k8s的*runtime.Scheme 类型,它是一个中央注册表,用来告诉 Kubernetes 客户端如何将 YAML/JSON 格式的对象(GVK)反序列化成对应的 Go 结构体,或将结构体序列化为对象。

然后在初始化了manager实例后,需要把自定义实现的Reconciler注册进manager:

configmapsyncer_controller.go

	if err = (&controller.ConfigMapSyncerReconciler{
		Client: mgr.GetClient(),
		Scheme: mgr.GetScheme(),
	}).SetupWithManager(mgr); 
...
// SetupWithManager sets up the controller with the Manager.
func (r *ConfigMapSyncerReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
    ...
    		Complete(r)
}
// 分为2步
// 第一步:NewControllerManagedBy利用Manager初始化controllerBuilder(TypedBuilder)
	NewControllerManagedBy = builder.ControllerManagedBy
    ...
func ControllerManagedBy(m manager.Manager) *Builder {
	return TypedControllerManagedBy[reconcile.Request](m)
}
    // TypedControllerManagedBy returns a new typed controller builder that will be started by the provided Manager.
func TypedControllerManagedBy[request comparable](m manager.Manager) *TypedBuilder[request] {
	return &TypedBuilder[request]{mgr: m}
}
// 第二步,Manager添加对应controller
// Complete builds the Application Controller.
func (blder *TypedBuilder[request]) Complete(r reconcile.TypedReconciler[request]) error {
	_, err := blder.Build(r)
    ...
}
// Build builds the Application Controller and returns the Controller it created.
func (blder *TypedBuilder[request]) Build(r reconcile.TypedReconciler[request]) (controller.TypedController[request], error) {
	...
	// Set the ControllerManagedBy
	if err := blder.doController(r); err != nil {
		return nil, err
	}
}
func (blder *TypedBuilder[request]) doController(r reconcile.TypedReconciler[request]) error {
    ...
    // 把我们自定义实现的Reconciler复制给Controller的初始哈opt,后续会用到
	if ctrlOptions.Reconciler == nil {
		ctrlOptions.Reconciler = r
	}
    ...
    	if blder.newController == nil {
		blder.newController = controller.NewTyped[request]
	}

	// Build the controller and return.
	blder.ctrl, err = blder.newController(controllerName, blder.mgr, ctrlOptions)
}
func NewTyped[request comparable](name string, mgr manager.Manager, options TypedOptions[request]) (TypedController[request], error) {
    // 下面NewTypedUnmanaged会把我们自定义实现的Reconciler复制给Controller,我们后面回头再看,这边先关注Manager
    	c, err := NewTypedUnmanaged(name, mgr, options)
...
	// Add the controller as a Manager components
    // 把生成的controller添加到Manager的组件
	return c, mgr.Add(c)
}

Manager的Add方式,最终调用的执行代码是以下:

func (r *runnables) Add(fn Runnable) error {
	switch runnable := fn.(type) {
	case *Server:
		if runnable.NeedLeaderElection() {
			return r.LeaderElection.Add(fn, nil)
		}
		return r.HTTPServers.Add(fn, nil)
	case hasCache:
		return r.Caches.Add(fn, func(ctx context.Context) bool {
			return runnable.GetCache().WaitForCacheSync(ctx)
		})
	case webhook.Server:
		return r.Webhooks.Add(fn, nil)
	case LeaderElectionRunnable:
		if !runnable.NeedLeaderElection() { jjjjjjjjkkkwix
                                           jjjmionionio;
			return r.Others.Add(fn, nil)
		}
        // 默认的controller.Controller实现了LeaderElectionRunnable,所以会走到这里
		return r.LeaderElection.Add(fn, nil)
	default:
		return r.LeaderElection.Add(fn, nil)
	}
}
// 会走到ruannableGroup的Add
func (r *runnableGroup) Add(rn Runnable, ready runnableCheck) error {
...
    	///还没启动的话,会加入到队列中
    	if !r.started {
			// Store the runnable in the internal if not.
			r.startQueue = append(r.startQueue, readyRunnable)
			r.start.Unlock()
			return nil
		}
    ...
    //已经开始的话,会<-到runnable的chan中
    	// Enqueue the runnable.
	r.ch <- readyRunnable
	return nil
}

以上流程,便可以把对应的Controller新建后添加到Manager中,那么添加完成后,Manager是怎么启动呢,我们可以回到main这个主函数看看启动函数:

	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        
///Manager最终由controllerManager实现
func (cm *controllerManager) Start(ctx context.Context) (err error) {
    ...
				if err := cm.startLeaderElectionRunnables(); err != nil {
					cm.errChan <- err

    ...
}
// LeaderElection不是重点,这边就不详细讲解了  
func (cm *controllerManager) startLeaderElectionRunnables() error {
	return cm.runnables.LeaderElection.Start(cm.internalCtx)
}

首先我们可以关注一下runnableGroup这个结构体:

// runnableGroup这个结构体主要是管理一系列runnables的结构体的(执行Start),并且即使在runnableGroup已经启动后,仍然能够动态添加对应于的runnables
type runnableGroup struct {
	ctx    context.Context
	cancel context.CancelFunc
	// 下面几个都是管理runnableGroup启动(Start)与否状态
	start        sync.Mutex
	startOnce    sync.Once
	started      bool
    // 如果还没Start,新的readyRunnable会添加到这个队列里面
	startQueue   []*readyRunnable
    // 一旦readyRunnable顺利启动了,会在这里返回通知
	startReadyCh chan *readyRunnable
    // 如果已经Start,则会持续从这里动态消费
    ch chan *readyRunnable

    ...}

func (r *runnableGroup) Start(ctx context.Context) error {
	var retErr error
// 利用sync.Once,这个函数只会调用一次,主要目的是把前面add在r.startQueue的送到r.ch中,供reconcile函数消费执行
	r.startOnce.Do(func() {
		defer close(r.startReadyCh)

		// Start the internal reconciler.
		go r.reconcile()

		// Start the group and queue up all
		// the runnables that were added prior.
		r.start.Lock()
		r.started = true
		for _, rn := range r.startQueue {
			rn.signalReady = true
            // 供reconcile消费
			r.ch <- rn
		}
		r.start.Unlock()

		// If we don't have any queue, return.
		if len(r.startQueue) == 0 {
			return
		}

		// Wait for all runnables to signal.
		for {
			select {
			case <-ctx.Done():
				if err := ctx.Err(); !errors.Is(err, context.Canceled) {
					retErr = err
				}
            // 被开始执行的会返回准备就绪的信号
			case rn := <-r.startReadyCh:
				for i, existing := range r.startQueue {
					if existing == rn {
						// 消费就绪的会从队列中剔除
						r.startQueue = append(r.startQueue[:i], r.startQueue[i+1:]...)
						break
					}
				}
				// 剔完了就说明全部readyRunnable顺利执行了
				if len(r.startQueue) == 0 {
					return
				}
			}
		}
	})
    
func (r *runnableGroup) reconcile() {
    // 会从r.ch持续消费执行
	for runnable := range r.ch {
		...
        go func(rn *readyRunnable) {
			go func() {
				if rn.Check(r.ctx) {
					if rn.signalReady {
                        // 开始启动要返回信号
						r.startReadyCh <- rn
					}
				}
			}()

			...
			// 启动对应的runnable,这里是
			if err := rn.Start(r.ctx); err != nil {
				r.errChan <- err
			}
		}(runnable)
        	}
}
// 由前面build出来的controller实现
type Runnable interface {
	Start(context.Context) error
}
// pkg/internal/controller/controller.go
// Controller implements controller.Controller.
type Controller[request comparable] struct {
    ...
    // 我们自定义的Controller就会实现TypedReconciler接口(Reconcile)
	Do reconcile.TypedReconciler[request]
    // 需要监控的一系列资源,会在Build生成controller时候的doWatch中赋值这个切片
	startWatches []source.TypedSource[request]
}
// 我们回到Build生成Controller的时候,会调用doWatch
func (blder *TypedBuilder[request]) doWatch() error {
	// Reconcile type
    // forInput是ctrl.NewControllerManagedBy(mgr).For(XXX)赋值生成
	if blder.forInput.object != nil {
		obj, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)
		if err != nil {
			return err
		}
		// 断言该泛型实际类型是否是reconcile.Request{},以防后续反射赋值报错
		if reflect.TypeFor[request]() != reflect.TypeOf(reconcile.Request{}) {
			return fmt.Errorf("For() can only be used with reconcile.Request, got %T", *new(request))
		}
		// 下面的操作,主要是接口赋值具体类型。正常来说接口赋值具体类型直接用等于就行了,但这接口TypedEventHandler和EnqueueRequestForObject
        // 的泛型不同(虽然底层相同可以赋值),所以用反射绕过该限制
		var hdler handler.TypedEventHandler[client.Object, request]
		reflect.ValueOf(&hdler).Elem().Set(reflect.ValueOf(&handler.EnqueueRequestForObject{}))
		allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
		allPredicates = append(allPredicates, blder.forInput.predicates...)
		src := source.TypedKind(blder.mgr.GetCache(), obj, hdler, allPredicates...)
        // Controller添加TypedSyncingSource(由internal.Kind)实现
        // Watch方法实际调用就是c.startWatches = append(c.startWatches, src),把TypedSyncingSource添加到关注资源列表
		if err := blder.ctrl.Watch(src); err != nil {
			return err
		}
	}


// 被前面runnableGroup调用的Start方法
func (c *Controller[request]) Start(ctx context.Context) error {
	...
    // 启动一个对关注api信息先进先出的queue
    queue := c.NewQueue(c.Name, c.RateLimiter)
	if priorityQueue, isPriorityQueue := queue.(priorityqueue.PriorityQueue[request]); isPriorityQueue {
		c.Queue = priorityQueue
	} else {
		c.Queue = &priorityQueueWrapper[request]{TypedRateLimitingInterface: queue}
	}
    ...
    // 在cache同步之前开始Start所有关注的资源
    	if err := c.startEventSources(ctx); err != nil {
			return err
		}
    ...
}
func (c *Controller[request]) startEventSources(ctx context.Context) error {
	errGroup := &errgroup.Group{}
	for _, watch := range c.startWatches {
        ...
		errGroup.Go(func() error {
            ...
            // 对对应资源启动Start,这里对应资源是internal.Kind
			if err := watch.Start(ctx, c.Queue); err != nil {
					sourceStartErrChan <- err
					return
				}
        }
}
// duiy
// Kind is used to provide a source of events originating inside the cluster from Watches (e.g. Pod Create).
type Kind[object client.Object, request comparable] struct {
	// Type是我们关注的类型,比如 &v1.Pod{}
	Type object
	
	// Cache used to watch APIs
	Cache cache.Cache
	// 处理方法
	Handler handler.TypedEventHandler[object, request]
...
}
// kind的Start方法主要目的是为了注册一个EventHandler到Informer中,而EventHandler功能是把reconcile.Requests加入到队列中
func (ks *Kind[object, request]) Start(ctx context.Context, queue workqueue.TypedRateLimitingInterface[request]) error {

    ...
    			// 获取对应的Informer(包裹一层的k8sinformer)
    			i, lastErr = ks.Cache.GetInformer(ctx, ks.Type)
    ...
    	// 为对应的Informer添加EventHandler
    	_, err := i.AddEventHandler(NewEventHandler(ctx, queue, ks.Handler, ks.Predicates).HandlerFuncs())
	
...
}
                    

添加的EventHandler是一个专门的结构体,同时也有专门的时间处理函数:

// EventHandler adapts a handler.EventHandler interface to a cache.ResourceEventHandler interface.
type EventHandler[object client.Object, request comparable] struct {
	// ctx stores the context that created the event handler
	// that is used to propagate cancellation signals to each handler function.
	ctx context.Context

	handler    handler.TypedEventHandler[object, request]
	queue      workqueue.TypedRateLimitingInterface[request]
	predicates []predicate.TypedPredicate[object]
}

func (e *EventHandler[object, request]) HandlerFuncs() cache.ResourceEventHandlerFuncs {
	return cache.ResourceEventHandlerFuncs{
		AddFunc:    e.OnAdd,
		UpdateFunc: e.OnUpdate,
		DeleteFunc: e.OnDelete,
	}
}
// OnAdd creates CreateEvent and calls Create on EventHandler.
func (e *EventHandler[object, request]) OnAdd(obj interface{}) {
	c := event.TypedCreateEvent[object]{}
...
    // 最终调用的是handler的Create
	e.handler.Create(ctx, c, e.queue)
}

那么对应的ks.Handler,其实是调用&handler.EnqueueRequestForObject{}的Create方:

func (e *TypedEnqueueRequestForObject[T]) Create(ctx context.Context, evt event.TypedCreateEvent[T], q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
...

	item := reconcile.Request{NamespacedName: types.NamespacedName{
		Name:      evt.Object.GetName(),
		Namespace: evt.Object.GetNamespace(),
	}}

	addToQueueCreate(q, evt, item)
}
// 简单来说,就是塞进queue中
func addToQueueCreate[T client.Object, request comparable](q workqueue.TypedRateLimitingInterface[request], evt event.TypedCreateEvent[T], item request) {
	priorityQueue, isPriorityQueue := q.(priorityqueue.PriorityQueue[request])
	if !isPriorityQueue {
		q.Add(item)
		return
	}

	var priority int
	if isObjectUnchanged(evt) {
		priority = LowPriority
	}
	priorityQueue.AddWithOpts(priorityqueue.AddOpts{Priority: priority}, item)
}

所以本质上,Controller启动就是为各个Informer添加关注类型的事件,简单来说各个时间都是把对应的key(gvk)投入到queue去,那么对应的queue就需要有个消费的地方,其实也在之前Controller的Start函数里面:

func (c *Controller[request]) Start(ctx context.Context) error {
    ...
    		for c.processNextWorkItem(ctx) {
				}
func (c *Controller[request]) processNextWorkItem(ctx context.Context) bool {
    // 在这里从queue从获取刚刚加进去的目标1
	obj, priority, shutdown := c.Queue.GetWithPriority()
	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, priority)
	return true
}
func (c *Controller[request]) reconcileHandler(ctx context.Context, req request, priority int) {
    	...
		result, err := c.Reconcile(ctx, req)
}
func (c *Controller[request]) Reconcile(ctx context.Context, req request) (_ reconcile.Result, err error) {
    ...//  最终在这里执行我们自定义的Reconcile逻辑
    	return c.Do.Reconcile(ctx, req)
}

那么这么c.do在哪里赋值呢,就在前面前面提及过的初始化函数NewTyped里面提及,会调用NewTypedUnmanaged函数进行初始化操作:

func NewTypedUnmanaged[request comparable](name string, mgr manager.Manager, options TypedOptions[request]) (TypedController[request], error) {
	...
    	return &controller.Controller[request]{
				...
            	//这里赋值
            	Do:                      options.Reconciler,
            ...
        }