Creating a k8s controller that watches all kinds in a single api group

51 Views Asked by At

I need to write a k8s controller using kubebuilder that watches objects of all kinds in a api group. It should also support dynamic kinds in the api group. That is if a new CRD is installed that belongs to the same api group and an instance of it is created then the controller should pick it up.

Is this possible at all? What are some of the other alternatives? e.g. hard-code the kinds (or take it from a config) and use the watches function to monitor the resources?

1

There are 1 best solutions below

0
kosta On BEST ANSWER

Thanks to some of the ideas from comments, I was able to make it work like the below. The code is part of the SetupWithManager function. The idea is to listen for CRDs and then dynamically create controllers based on GVK

builder.Watches(crd, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {

        crd := obj.(*v1.CustomResourceDefinition)
        r.Queue.SetContext(ctx)
        customResource := &unstructured.Unstructured{}
        for _, version := range crd.Spec.Versions {
            gvk := schema.GroupVersionKind{
                Group:   crd.Spec.Group,
                Version: version.Name,
                Kind:    crd.Spec.Names.Kind,
            }
            // set GVK for unstructured object
            // this is needed to add a watch unstructured objects
            customResource.SetGroupVersionKind(gvk)
            // Create a new controller dynamically based on the CRD added within an API group
            err := ctrl.NewControllerManagedBy(mgr).
                For(customResource).
                Owns(crd).
                WithLogConstructor(func(request *reconcile.Request) logr.Logger {
                    return mgr.GetLogger()
                }).
                Complete(&myReconciler{client: mgr.GetClient(), gvk: gvk, Queue: r.Queue})
            if err != nil {
                mgr.GetLogger().Error(err, fmt.Sprintf("Unable to create a controller for %s", gvk))
            }
        }
        return []reconcile.Request{}
    }

I also filtered the CRDs using predicate function (not part of snippet above)

and then need a reconciler function like below

func (r *myReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// this is invoked when objects of the filtered CRDs are created, updated etc.
}

The key really is to set the GVK on unstructured types. Understand this might be not recommended, because Do one thing and do it good. but it works for my specific use case.