现象描述
我们给CRD的控制器通过Owns方法指定消费被crd管理的资源的增删改查事件,例如:
func (r *MyDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1beta1.MyDeployment{}).
Owns(&v1.Pod{}, builder.WithPredicates(predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return true
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return true
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return true
},
})).
Complete(r)
}
其中MyDeployment是我们自定义的资源,.For
指定此控制器会监听MyDeployment资源的事件。MyDeployment资源控制器会创建Pod资源,并配置Pod的ownerReferences为MyDeployment资源对象。我们通过.Owns
配置监听Pod的创建、更新和删除事件,让MyDeployment资源控制器能够消费Pod的创建、更新和删除事件。
然而,我们遇到了个问题。我们在CreateFunc、DeleteFunc、UpdateFunc方法中添加日记,发现这些方法被调用了,但却没有触发MyDeployment资源控制器的Reconcile方法执行。
我们在创建Pod资源的时候,会将MyDeployment资源对象转换为metav1.OwnerReference,并赋值给Pod的ownerReferences字段。这是toOwner方法:
func toOwner(deployment *v1beta1.MyDeployment) metav1.OwnerReference {
gvk := deployment.GroupVersionKind()
return metav1.OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: deployment.GetName(),
UID: deployment.GetUID(),
BlockOwnerDeletion: pointer.Bool(true),
}
}
原因分析
通过debug我们发现,原来事件并没有进入MyDeployment资源控制器的事件队列。
以delete事件为例,在此案例中,当接收到v1.Pod的更新事件时,如果Predicates都返回true,就会尝试调用EventHandler的Delete方法往MyDeployment资源控制器的事件队列放入一个事件。
func (e EventHandler) OnDelete(obj interface{}) {
d := event.DeleteEvent{}
......
// UpdateFunc返回true
for _, p := range e.Predicates {
if !p.Delete(d) {
return
}
}
// 将事件放入队列
e.EventHandler.Delete(d, e.Queue)
}
EventHandler的实现类是EnqueueRequestForOwner,其Delete方法如下。
func (e *EnqueueRequestForOwner) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) {
reqs := map[reconcile.Request]empty{}
e.getOwnerReconcileRequest(evt.Object, reqs)
for req := range reqs {
q.Add(req)
}
}
我们发现getOwnerReconcileRequest之后,reqs这个map还是空的。
继续追踪方法调用,发现getOwnersReferences返回的metav1.OwnerReference数组为空数组。
func (e *EnqueueRequestForOwner) getOwnersReferences(object metav1.Object) []metav1.OwnerReference {
if object == nil {
return nil
}
// If not filtered as Controller only, then use all the OwnerReferences
if !e.IsController {
return object.GetOwnerReferences()
}
// If filtered to a Controller, only take the Controller OwnerReference
if ownerRef := metav1.GetControllerOf(object); ownerRef != nil {
return []metav1.OwnerReference{*ownerRef}
}
// No Controller OwnerReference found
return nil
}
最后追踪到GetControllerOfNoCopy方法,发现会判断对象的OwnerReferences的Controller是否有配置,且配置为true。如果未配置,或配置为false,那么就会返回nil。
func GetControllerOfNoCopy(controllee Object) *OwnerReference {
refs := controllee.GetOwnerReferences()
for i := range refs {
if refs[i].Controller != nil && *refs[i].Controller {
return &refs[i]
}
}
return nil
}
解决办法
修改我们的toOwner方法,配置OwnerReference对象的Controller为true即可。
func toOwner(deployment *v1beta1.MyDeployment) metav1.OwnerReference {
gvk := deployment.GroupVersionKind()
controller := true
return metav1.OwnerReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: deployment.GetName(),
UID: deployment.GetUID(),
Controller: &controller,
BlockOwnerDeletion: pointer.Bool(true),
}
}