导读
今天我在将原生的controller-runtime重构成kubebuilder架构时遇到了一个这样的问题。导致我浪费了好多时间,接下来让我们看看是什么问题。
首先,前辈们在控制器的run中写了一个wait.Until事件,作用是每半小时运行一次aa() (方法名我乱写的)方法。然后aa的参数还需要控制器的queue。
这本身没什么问题,但是在将其重构时,发现该方法无法将其加入到架构中,首先kubebuilder将控制器的创建等操作进行了封装,使得我们无法进行二次重构(这时候我突然想如果他是spring的那种模板模式就好了)。
那既然这样可以写在Reconciler方法里面可以嘛?当然不行,因为Reconciler是通过事件调用而不是主动去运行,所以这个方案pass了。
Reconciler不行的话那就只能写在SetupWithManager中。
这时候又又又出来一个问题就是,我们还需要利用控制器里面的事件队列怎么办(SetupWithManager里面本身无法利用队列)
经过我的查找,我发现SetupWithManager里的Watches方法能够解决我的问题
Watches
看过我Controller-runtime 的 informer分析 文章的同学都知道。Watches其实就是将当前资源添加到 blder.watchesInput 而 blder.watchesInput 会在doWatch中被blder.ctrl.Watch调用。
// 这里我只粘贴了一块,因为下面逻辑基本一样,只是监听的资源不一样
func (blder *Builder) doWatch() error {
// 返回 Reconcile 类型
typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)
if err != nil {
return err
}
// 这里注意 因为src是Kind类型,所以在调用src.Start()时是调用的Kind中的方法
src := &source.Kind{Type: typeForSrc}
hdler := &handler.EnqueueRequestForObject{}
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
//开始监听
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
return err
}
***
}
func (c *Controller) Watch(src source.Source, evthdler handler.EventHandler, prct ...predicate.Predicate)
{
***
// 在doWatch这个环节,!c.Started会变成true,那么会将src进行封装与c.startWatches进行append。资源监控会在manager.start 中进行start
if !c.Started {
c.startWatches = append(c.startWatches, watchDescription{src: src, handler: evthdler, predicates: prct})
return nil
}
// kind类型
return src.Start(c.ctx, evthdler, c.Queue, prct...)
}
而Watch 里的 src 最终会调用Start方法。
首先我们先根据 src = &source.Kind{Type: typeForSrc}对kind中的start进行追踪
func (ks *Kind) Start(ctx context.Context, handler handler.EventHandler, queue workqueue.RateLimitingInterface,
prct ...predicate.Predicate) error {
ks.started = make(chan error)
go func() {
// 获取资源的informer(也是在这里创建的)
i, err := ks.cache.GetInformer(ctx, ks.Type)
// 向每个资源的informer中添加监听事件
i.AddEventHandler(internal.EventHandler{Queue: queue, EventHandler: handler, Predicates: prct})
// 等待cache同步完成
if !ks.cache.WaitForCacheSync(ctx) {
//
ks.started <- errors.New("cache did not sync")
}
close(ks.started)
}()
return nil
}
看到这里我们应该就可以知道了。在kubebuilder中监听资源的操作其实是在src.start中完成的。并且我们还知道src.start只运行一次,并且拥有控制器的queue。 是不是到这里大家已经猜到了我的解决办法了!
解决办法
没错,我自定义了一个src。并实现source.Source 接口中的start
SetupWithManager
// SetupWithManager sets up the controller with the Manager.
func (l *aaa) SetupWithManager(mgr ctrl.Manager, controllerConfig controller.Options, informer v1listers.informer) error {
return ctrl.NewControllerManagedBy(mgr).Watches(&TimeSource.TimeSource{informer}, nil).
Complete(l)
}
type TimeSource struct {
informer v1listers.informer
}
// Start implements Source.
func (l TimeSource) Start(ctx context.Context, evt handler.EventHandler, queue workqueue.RateLimitingInterface,
pr ...predicate.Predicate) error {
wait.Until(func() {
if err := l.sync(queue); err != nil {
klog.Errorf("Error periodically sync user status, %v", err)
}
}, time.Hour, ctx.Done())
return nil
}
func (l *TimeSource) sync(queue workqueue.RateLimitingInterface) error {
ins, err := l.informer.List(labels.Everything())
if err != nil {
return err
}
for _, item := range ins {
key, err := cache.MetaNamespaceKeyFunc(item)
if err != nil {
return err
}
queue.AddRateLimited(key)
}
return nil
}