Overview
controller-runtime 是 Kubernetes 社区提供可供快速搭建一套 实现了controller 功能的工具,无需自行实现Controller的功能了;在 Kubebuilder
与 Operator SDK
也是使用 controller-runtime
。本文将对 controller-runtime
的工作原理以及在不同场景下的使用方式进行简要的总结和介绍。
controller-runtime structure
controller-runtime
主要组成是需要用户创建的 Manager
和 Reconciler
以及 Controller Runtime
自己启动的 Cache
和 Controller
。
- Manager:是用户在初始化时创建的,用于启动
Controller Runtime
组件 - Reconciler:是用户需要提供来处理自己的业务逻辑的组件(即在通过
code-generator
生成的api-like而实现的controller中的业务处理部分)。 - Cache:一个缓存,用来建立
Informer
到ApiServer
的连接来监听资源并将被监听的对象推送到queue中。 - Controller: 一方面向 Informer 注册
eventHandler
,另一方面从队列中获取数据。controller 将从队列中获取数据并执行用户自定义的Reconciler
功能。
由图可知,Controller会向 Informer 注册一些列eventHandler;然后Cache启动Informer(informer属于cache包中),与ApiServer建立监听;当Informer检测到资源变化时,将对象加入queue,Controller 将元素取出并在用户端执行 Reconciler。
Controller引入
我们从 controller-rumtime项目的 example 进行引入看下,整个架构都是如何实现的。
可以看到 example 下的实际上实现了一个 reconciler
的结构体,实现了 Reconciler
抽象和 Client
结构体
|
|
那么来看下 抽象的 Reconciler 是什么,可以看到就是抽象了 Reconcile
方法,这个是具体处理的逻辑过程
|
|
下面在看下谁来实现了这个 Reconciler 抽象
|
|
controller structure
在 controller-runtime\pkg\internal\controller\controller.go 中实现了这个 Controller
|
|
看完了controller的structure,接下来看看controller是如何使用的
injection
Controller.Watch 实现了注入的动作,可以看到 watch()
通过参数将 对应的事件函数传入到内部
|
|
启动操作实际上为informer注入事件函数
|
|
我们知道对于 eventHandler,实际上应该是一个 onAdd
,onUpdate
这种类型的函数,queue则是workqueue,那么 Predicates
是什么呢?
通过追踪可以看到定义了 Predicate 抽象,可以看出Predicate 是Watch到的事件时什么类型的,当对于每个类型的事件,对应的函数就为 true,在 eventHandler 中,这些被用作,事件的过滤。
|
|
在对应的动作中,可以看到这里作为过滤操作
|
|
上面就看到了,对应是 EventHandler.Create
进行添加的,那么这些动作具体是在做什么呢?
在代码 pkg/handler ,可以看到这些操作,类似于create,这里将ns/name放入到队列中。
|
|
unqueue
上面看到了,入队的动作实际上都是将 ns/name
加入到队列中,那么出队列时又做了些什么呢?
通过 controller.Start()
可以看到controller在启动后都做了些什么动作
|
|
通过上面的分析,可以看到,每个消费的worker线程,实际上调用的是 processNextWorkItem 下面就来看看他究竟做了些什么?
|
|
那么下面看看 reconcileHandler 做了些什么
|
|
通过对example中的 Reconcile 查找其使用,可以看到,调用他的就是上面我们说道的 reconcileHandler
,到这里我们就知道了,controller 的运行流为 Controller.Start()
> Controller.processNextWorkItem
> Controller.reconcileHandler
> Controller.Reconcile
最终到达了我们自定义的业务逻辑处理 Reconcile
Manager
在上面学习 controller-runtime
时了解到,有一个 Manager
的组件,这个组件是做什么呢?我们来分析下。
Manager
是用来创建与启动 controller
的(允许多个 controller
与 一个 manager
关联),Manager会启动分配给他的所有controller,以及其他可启动的对象。
在 example 看到,会初始化一个 ctrl.NewManager
|
|
这个 manager
就是 controller-runtime\pkg\manager\manager.go 下的 Manager
, Manager 通过初始化 Caches 和 Clients 等共享依赖,并将它们提供给 Runnables。
|
|
controller-manager
controllerManager 则实现了这个manager的抽象
|
|
workflow
了解完ControllerManager之后,我们通过 example 来看看 ControllerManager 的workflow
|
|
- 通过
manager.New()
初始化一个manager,这里面会初始化一些列的manager的参数 - 通过
ctrl.NewControllerManagedBy
注册 controller 到manager中ctrl.NewControllerManagedBy
是 builder的一个别名,构建出一个builder类型的controllerbuilder
中的ctrl
就是 controller
- 启动manager
builder
下面看来看下builder在构建时做了什么
|
|
我们看到 example 中是调用了 For()
动作,那么这个 For()
是什么呢?
通过注释,我们可以看到 For() 提供了 调解对象类型,ControllerManagedBy 通过 reconciling object 来相应对应create/delete/update
事件。调用 For()
相当于调用了 Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
。
|
|
接下来是调用的 Owns() ,Owns()
看起来和 For()
功能是类似的。只是说属于不同,是通过Owns方法设置的
|
|
最后到了 Complete(),Complete
是完成这个controller的构建
|
|
这里面可以看到,会完成 doController 和 doWatch
doController会初始化好这个controller并返回
|
|
start Manager
接下来是manager的启动,也就是对应的 start()
与 doWatch()
通过下述代码我们可以看出来,对于 doWatch()
就是把 compete()
前的一些资源的事件函数都注入到controller 中
|
|
由于前两部 builder
的操作将 mgr 指针传入到 builder中,并且操作了 complete()
,也就是操作了 build()
,这代表了对 controller
完成了初始化,和事件注入(watch
)的操作,所以 Start(),就是将controller启动
|
|
可以看到上面启动了4种类型的runnable,实际上就是对这runnable进行启动,例如 controller,cache等。
回顾一下,我们之前在使用code-generator
生成,并自定义controller时,我们也是通过启动 informer.Start()
,否则会报错。
最后可以通过一张关系图来表示,client-go与controller-manager之间的关系