Kubebuilder控制器配置Owns然而监听到事件却不触发Reconcile方法

原创 吴就业 143 0 2024-04-16

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/7a6e5c05b8cd44bba4d36c227a15513e

作者:吴就业
链接:https://wujiuye.com/article/7a6e5c05b8cd44bba4d36c227a15513e
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

现象描述

我们给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),
	}
}
#云原生

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

如何自己部署autoscaler实现节点的自动扩缩容(一):本地debug运行autoscaler

在google cloud上使用gke集群,gke已经集成autoscaler,通过在控制台创建节点池点击开启自动扩缩容就可以,那么为什么还要自己部署呢?怎么本地让autoscaler成功跑起来呢?

Helm chart在values文件中声明为字符串的变量,部署替换占位符后却变成了数字类型

helm在获取values.yaml文件中配置的值时,由于没有对应一个结构体来反序列化yaml,只能使用map来接收,例如map[string]interface{}。可能是将interface{}尝试转成数字,能够转换成功helm就误以为我们需要的是数字了。

kubebuilder如何Watch由Pod产生的Event的创建,触发控制器Reconcile方法的执行

自定义资源的Controller创建出来的子资源,子资源创建的子资源(子子资源),如何Watch子子资源的事件?我们以MyDeployment->创建Pod->创建Event,想要watch Pod创建的Event的Create事件为例。

满足Autoscaler触发自定扩容Node的条件是什么?

我们使用自定义的调度器来调度pod,有自定义的Filter插件。Autoscaler在执行扩容之前,会调用Filter插件,尝试是不是真的没有node满足调度这个pod再去扩容。而默认情况下,Autoscaler拿的是默认的Filter插件,拿不到我们自定义的Filter插件,所以没有走我们的Filter逻辑,所以不会扩容。

如何实现一个简单的K8s apiserver

apiserver是k8s的另一种扩展机制,相比CRD,它更为灵活。本篇以实战为主,介绍如何实现一个简单的apiserver。

Autopilot: workload autoscaling at Google论文描述的requests预测算法

本篇简单描述(Autopilot: workload autoscaling at Google)论文中描述的资源request预测算法,不需要理解论文中那复杂的数学公式。