informer和controller-runtime源码
问题
最近在写operator时,碰到一个场景需要让controller每隔1分钟做一次reconcile,而不需要借助外部事件触发。
虽然client-go
和controller-runtime
都提供了很方便的接口进行设置。但是知其然还要知其所以然,借着解决这个问题的契机,仔细阅读了一下infomer
和controller-runtime
的代码,搞清楚了底层实现原理。
方法一
创建informerFactory
对象时,设置defaultResync
参数
原理
reflector除了会watch apiserver,还会每隔 defaultResync 从indexer中重新获取Object,并将其入队fifo,这样就会重新触发一次informer的Add事件并入队工作队列。
client-go源码
reflector在进行ListAndWatch的同时也会周期性的做resync:
这里的store就是fifo,fifo的Resync实现如下:
这里调用knownObjects.ListKeys()来获取所有的Object key然后再入队fifo,这个knownObjects其实就是indexer cache(一个带锁的map)
controller-runtime源码
对于controller-runtime库,reflector、informer、indexer等组件被封装在cache对象中,cache对象是Manager对象的属性,它们之间的关系如下图所示:
我们可以通过在创建Manager对象时,传入SyncPeriod参数来达到这一目的,当然SyncPeriod应该是可配置的:
方法二
使用controller-runtime
库时,还可以通过在Reconcile
中,设置返回的Result.RequeueAfter
为1分钟来实现:
原理
先找到Reconcile的调用点:
-
ControllerManagedBy通过Builder模式将Controller添加进Manager中
-
Manager启动时会启动所有controller,对于controller,“启动”的含义就是启动多个goroutine循环的从workqueue中取key,然后执行Reconcile,顺着Manager.Start一层层的找到Controller的Start入口,最终可以看到熟悉的
processNextWorkItem
:processNextWorkItem
的逻辑当然就是从workqueue里取key,然后执行Reconcile的业务逻辑: -
可以看到当
result.RequeueAfter > 0
时,执行了c.Queue.Forget(obj)
和c.Queue.AddAfter(req, result.RequeueAfter)
,分别是什么意思呢?要搞清楚这一点,首先我们要弄清楚workqueue的实现。
workqueue
-
workqueue的创建方法定义在controller中::
-
这里创建的是一个限速队列,限速队列由延迟队列和限速器两部分组成:
AddAfter
AddAfter
是延迟队列提供的方法,它向waitingForAddCh
这个channel中传入了一个构造的waitFor
对象
而这个channel的接收方,则是在创建延迟队列时启动的一个goroutine:
在这个goroutine中,收到waitFor
对象后,如果还没到执行时间,则会插入优先级队列中(可以看到,高性能定时器一般用堆实现)
随后会判断优先级队列中堆顶元素的时间是否到达,如果时间到了,就取出堆顶元素,并入队workqueue,时间没到就计算需要等多长时间,然后启动一个timer进行等待
Forget
在看Forget方法前,先看限速队列中我们最常用的AddRateLimited
方法,一般这个方法会在我们Reconcile失败的时候进行调用,目的就是以某种限定的速率重新入队workqueue,从而达到限制重试速度的目的:
可以看到其实就是调用延迟队列的AddAfter
方法,只是AddAfter
的方法的参数不是固定的时间,而是由ratelimiter计算得到
workqueue包中提供的默认限速器是指数退避限速器 + 令牌桶限速器:
Forget
是ratelimiter提供的方法,其实就是把失败的对象从ratelimiter中移除,这样ratelimiter就不会再根据该对象的失败次数对其进行限速计算了
因此,在Reconcile执行成功后,需要调用Forget将对象(也就是字符串namespace/name)从限速器中移除,否则会重复入队workqueue一次并且会影响后续限速器对于相同key的限速计算。
回到最开始的问题,当设置result.RequeueAfter
为1min时,会调用c.Queue.Forget(obj)
和c.Queue.AddAfter(req, result.RequeueAfter)
,也就是说1分钟之后会再次入队,触发reconciliation。