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-runtime structure
图:controller-runtime flowchart
由图可知,Controller会向 Informer 注册一些列eventHandler;然后Cache启动Informer(informer属于cache包中),与ApiServer建立监听;当Informer检测到资源变化时,将对象加入queue,Controller 将元素取出并在用户端执行 Reconciler。
Controller引入
我们从 controller-rumtime项目的 example 进行引入看下,整个架构都是如何实现的。
可以看到 example 下的实际上实现了一个 reconciler
的结构体,实现了 Reconciler
抽象和 Client
结构体
type reconciler struct { client.Client scheme *runtime.Scheme }
那么来看下 抽象的 Reconciler 是什么,可以看到就是抽象了 Reconcile
方法,这个是具体处理的逻辑过程
type Reconciler interface { Reconcile(context.Context, Request) (Result, error) }
下面在看下谁来实现了这个 Reconciler 抽象
type Controller interface { reconcile.Reconciler // 协调的具体步骤,通过ns/name\ // 通过predicates来评估来源数据,并加入queue中(放入队列的是reconcile.Requests) Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error // 启动controller,类似于自定义的Run() Start(ctx context.Context) error GetLogger() logr.Logger }
controller structure
在 controller-runtime\pkg\internal\controller\controller.go 中实现了这个 Controller
type Controller struct { Name string // controller的标识 MaxConcurrentReconciles int // 并发运行Reconciler的数量,默认1 // 实现了reconcile.Reconciler的调节器, 默认DefaultReconcileFunc Do reconcile.Reconciler // makeQueue会构建一个对应的队列,就是返回一个限速队列 MakeQueue func() workqueue.RateLimitingInterface // MakeQueue创造出来的,在出入队列就是操作的这个 Queue workqueue.RateLimitingInterface // 用于注入其他内容 // 已弃用 SetFields func(i interface{}) error mu sync.Mutex // 标识开始的状态 Started bool // 在启动时传递的上下文,用于停止控制器 ctx context.Context // 等待缓存同步的时间 默认2分钟 CacheSyncTimeout time.Duration // 维护了eventHandler predicates,在控制器启动时启动 startWatches []watchDescription // 日志构建器,输出入日志 LogConstructor func(request *reconcile.Request) logr.Logger // RecoverPanic为是否对reconcile引起的panic恢复 RecoverPanic bool }
看完了controller的structure,接下来看看controller是如何使用的
injection
Controller.Watch 实现了注入的动作,可以看到 watch()
通过参数将 对应的事件函数传入到内部
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate) error { c.mu.Lock() defer c.mu.Unlock() // 使用SetFields来完成注入操作 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还未启动,那么将这些动作缓存到本地 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) return src.Start(c.ctx, evthdler, c.Queue, prct...) }
启动操作实际上为informer注入事件函数
type Source interface { // start 是Controller 调用,用以向 Informer 注册 EventHandler, 将 reconcile.Requests(一个入队列的动作) 排入队列。 Start(context.Context, handler.EventHandler, workqueue.RateLimitingInterface, ...predicate.Predicate) error } 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 }
我们知道对于 eventHandler,实际上应该是一个 onAdd
, onUpdate
这种类型的函数,queue则是workqueue,那么 Predicates
是什么呢?
通过追踪可以看到定义了 Predicate 抽象,可以看出Predicate 是Watch到的事件时什么类型的,当对于每个类型的事件,对应的函数就为 true