I need to create a k8s resource which take some time until it will be available, for this I use the following
op, err := controllerutil.CreateOrUpdate(context.TODO(), c, deploy, func() error {
})
func2()
now I need to call to func2 right after the creation of the object was done (it may take 2-3 min until finish),
How should I do it right?
I found this but not sure how to combine them ...
https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg#hdr-Watching_and_EventHandling
im using kubebuilder
The above approach is more for cli usage.
When you are using kubebuilder or the operator sdk then you need to deal with it in your reconcile function.
Usually you have a custom resource that triggers your controllers reconcile function. When the custom resource is being created you then create the deployment and instead of returning an empty reconcile.Result (which marks it as done) you can return the reconcile.Result with the Requeue attribute.
reconcile.Result{Requeue: true}
So during the next run you check if the deployment is ready. If not then you requeue again. Once it is ready you return an empty reconcile.Result struct.
Also keep in mind that the reconcile function always needs to be idempotent as it will be run again for every custom resource during a restart of the controller and also every 10 hours by default.
Alternatively you could also use an owner reference on the created deployment and then setup the controller to reconcile the owner resource (your custom resource) whenever an update happens on the owned resource (the deployment). With operator sdk this can be configured in the SetupWithManager function, which by default only uses the For option function. Here you need to add the Owns option function.
// SetupWithManager sets up the controller with the Manager.
func (r *YourReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&yourapigroup.YourCustomResource{}).
Owns(&appv1.Deployment{}).
Complete(r)
}
I never used that approach though therefore it might be required to add more code for this to work.
Using the owner reference can also come in handy if you do not require any finalizer code, because kubernetes will delete your owned resource (the deployment) automatically when the custom resource is being deleted.
Here is an example how to create a deployment and check if it has at least 1 ready replica. Maybe it would be even better to check the conditions in the status and look for the condition of type Available and status of "True".
package main
import (
"context"
"fmt"
v1 "k8s.io/api/apps/v1"
podv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"time"
)
const (
namespace = "default"
deploymentName = "nginx"
)
func main() {
cfg, err := config.GetConfig()
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
panic(err)
}
client, err := client.New(cfg, client.Options{})
if err != nil {
panic(err)
}
d := &v1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deploymentName,
Namespace: namespace,
},
Spec: v1.DeploymentSpec{
Replicas: toInt32Ptr(2),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "nginx",
},
},
Template: podv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "nginx",
},
},
Spec: podv1.PodSpec{
Containers: []podv1.Container{
{
Name: "nginx",
Image: "nginx",
},
},
},
},
},
}
fmt.Println("Deploying")
_, err = controllerutil.CreateOrUpdate(context.TODO(), client, d, func() error {
return nil
})
if err != nil {
panic(err)
}
stop := make(chan struct{})
watchList := cache.NewListWatchFromClient(clientset.AppsV1().RESTClient(), "deployments", namespace, fields.Everything())
_, ctrl := cache.NewInformer(watchList, &v1.Deployment{}, time.Second, cache.ResourceEventHandlerFuncs{
UpdateFunc: func(o, n interface{}) {
newDeployment := n.(*v1.Deployment)
if newDeployment.Name != deploymentName {
return
}
if newDeployment.Status.ReadyReplicas > 0 {
close(stop)
return
}
return
},
})
ctrl.Run(stop)
fmt.Println("Deployment has at least 1 ready replica")
}
func toInt32Ptr(i int32) *int32 {
return &i
}
This example, from operator-sdk documentation, might help: https://sdk.operatorframework.io/docs/building-operators/golang/references/client/#example-usage. It is based on Create() and Update() functions, which might lead to a simpler algorithm.
This one, from kubebuilder documentation, also provides an interesting track, although it is deprecated: https://book-v1.book.kubebuilder.io/basics/simple_controller.html