我们先利用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,
...
}