### Install with Go Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Install a release of the envtest binaries manager using Go. Note that different release branches may require specific Golang versions. ```shell go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.22 ``` ```shell go install sigs.k8s.io/controller-runtime/tools/setup-envtest@release-0.20 ``` -------------------------------- ### Set Up Integration Test Environment with envtest Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Initialize a real `kube-apiserver` and `etcd` process for integration testing. CRDs are automatically installed from specified directories. This setup is typically done in `BeforeSuite` and torn down in `AfterSuite`. ```go import ( "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "sigs.k8s.io/controller-runtime/pkg/envtest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) var ( testEnv *envtest.Environment cfg *rest.Config ) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Controller Suite") } var _ = BeforeSuite(func() { ctrl.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{"../../config/crd/bases"}, ErrorIfCRDPathMissing: true, } var err error cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) // Create a manager backed by the test environment mgr, err := ctrl.NewManager(cfg, ctrl.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) err = (&MyReconciler{Client: mgr.GetClient()}).SetupWithManager(mgr) Expect(err).NotTo(HaveOccurred()) go func() { Expect(mgr.Start(ctrl.SetupSignalHandler())).NotTo(HaveOccurred()) }() }) var _ = AfterSuite(func() { Expect(testEnv.Stop()).To(Succeed()) }) ``` -------------------------------- ### Switch to Installed Envtest Version Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Switch to the most recently installed envtest version for a specific minor version (e.g., 1.21.x) and set up the environment. This command requires sourcing the output. ```shell source <(setup-envtest use -i -p env 1.21.x) ``` -------------------------------- ### Create and Start a Manager Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Use `ctrl.NewManager` to create a Manager, which is the entry point for operator binaries. It owns shared dependencies like cache, client, and leader election, and starts all registered controllers and runnables. Configure metrics, health probes, and leader election ID as needed. ```go package main import ( "os" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) func main() { ctrl.SetLogger(zap.New()) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, // custom scheme with CRDs registered Metrics: metricsserver.Options{BindAddress: ":8080"}, HealthProbeBindAddress: ":8081", LeaderElection: true, LeaderElectionID: "my-operator-lock", }) if err != nil { ctrl.Log.Error(err, "unable to create manager") os.Exit(1) } mgr.AddHealthzCheck("healthz", healthz.Ping) mgr.AddReadyzCheck("readyz", healthz.Ping) if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { ctrl.Log.Error(err, "problem running manager") os.Exit(1) } } ``` -------------------------------- ### Starting Caches Concurrently Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/move-cluster-specific-code-out-of-manager.md Shows the mechanism for starting all identified caches concurrently using goroutines after they have been collected. ```go for idx := range cm.caches { go func(idx int) {cm.caches[idx].Start(cm.internalStop)} } ``` -------------------------------- ### List Available Local Versions Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md List all envtest versions currently installed locally for a specific operating system and architecture. ```shell setup-envtest list -i --os darwin --arch amd64 ``` -------------------------------- ### Sideload Envtest Tarball Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Install a pre-downloaded envtest tarball as a specific version (e.g., 1.16.2) into the local store. ```shell setup-envtest sideload 1.16.2 < downloaded-envtest.tar.gz ``` -------------------------------- ### Download Specific Envtest Version Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Download a specific version of envtest (e.g., 1.19.x) and print its installation path. ```shell setup-envtest use -p path 1.19.x! ``` -------------------------------- ### Reconciler Interface - DeploymentReconciler Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Example implementation of the Reconciler interface for managing Deployments. It fetches a Deployment, logs its details, and schedules a requeue after 5 minutes. ```APIDOC ## DeploymentReconciler.Reconcile ### Description This method reconciles a Deployment object. It fetches the Deployment, logs its name and replica count, and returns a result indicating to requeue after 5 minutes. ### Method `Reconcile` ### Parameters - `ctx` (context.Context) - The context for the request. - `req` (ctrl.Request) - The request object containing the name and namespace of the object to reconcile. ### Request Body N/A ### Response #### Success Response - `ctrl.Result` - An object indicating whether to requeue the request and when. - `error` - An error if the reconciliation fails. #### Response Example ```go return ctrl.Result{RequeueAfter: 5 * time.Minute}, nil ``` #### Error Handling - If the Deployment is not found, it returns `ctrl.Result{}` and `nil` error, indicating nothing to do. - If fetching the Deployment fails for other reasons, it returns an error wrapping the original error. ``` -------------------------------- ### Perform CRUD Operations with client.Client in Go Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Demonstrates various CRUD operations using the client.Client interface, including Get, List, Create, Update, Patch, Delete, and updating status subresources. It also shows how to create tokens using subresources and ignore NotFound errors. ```go import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) // Get pod := &corev1.Pod{} err := c.Get(ctx, client.ObjectKey{Namespace: "default", Name: "my-pod"}, pod) if apierrors.IsNotFound(err) { /* handle */ } // List with label selector and namespace filter podList := &corev1.PodList{} err = c.List(ctx, podList, client.InNamespace("default"), client.MatchingLabels{"app": "my-app"}, ) // Create newCM := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{Name: "my-cm", Namespace: "default"}, Data: map[string]string{"key": "value"}, } err = c.Create(ctx, newCM) // Update pod.Labels["updated"] = "true" err = c.Update(ctx, pod) // Patch (strategic merge) patch := client.MergeFrom(pod.DeepCopy()) pod.Labels["patched"] = "true" err = c.Patch(ctx, pod, patch) // Delete err = c.Delete(ctx, pod, client.GracePeriodSeconds(0)) // Update status subresource pod.Status.Phase = corev1.PodRunning err = c.Status().Update(ctx, pod) // SubResource token creation sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "my-sa"}} token := &authv1.TokenRequest{Spec: authv1.TokenRequestSpec{ExpirationSeconds: ptr.To[int64](3600)}} err = c.SubResource("token").Create(ctx, sa, token) // Helper: ignore NotFound errors err = client.IgnoreNotFound(c.Get(ctx, key, obj)) ``` -------------------------------- ### Cache Initialization Check Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/move-cluster-specific-code-out-of-manager.md Demonstrates how the manager will use type assertion to identify `HasCaches` interfaces on runnables, ensuring caches are started before other components. ```go type HasCaches interface { GetCache() } if getter, hasCaches := runnable.(HasCaches); hasCaches { m.caches = append(m.caches, getter()) } ``` -------------------------------- ### Use Latest Installed with KubeBuilder Assets Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Use the latest installed envtest version, respecting KUBEBUILDER_ASSETS if set. The '-i' flag ensures only installed versions are considered. ```shell setup-envtest use -i --use-env ``` -------------------------------- ### Custom Shell Function for setup-envtest Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md A custom shell function to integrate setup-envtest into your shell environment, automatically sourcing the output for 'use' commands that require environment variable setup. ```shell setup-envtest() { if (($@[(Ie)use])); then source <($GOPATH/bin/setup-envtest "$@" -p env) else $GOPATH/bin/setup-envtest "$@" fi } ``` -------------------------------- ### Manager - ctrl.NewManager / manager.New Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Creates a Manager that owns shared dependencies and starts all registered controllers and runnables. The Manager is the entry point for every operator binary. ```APIDOC ## Manager - `ctrl.NewManager` / `manager.New` ### Description Creates a Manager that owns shared dependencies (cache, client, scheme, webhook server, leader election) and starts all registered controllers and runnables. The Manager is the entry point for every operator binary. ### Method `ctrl.NewManager` ### Parameters #### Options - **Scheme** (custom scheme with CRDs registered) - **Metrics** (metricsserver.Options with BindAddress) - **HealthProbeBindAddress** (string) - **LeaderElection** (bool) - **LeaderElectionID** (string) ### Request Example ```go package main import ( "os" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" ) func main() { ctrl.SetLogger(zap.New()) mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, // custom scheme with CRDs registered Metrics: metricsserver.Options{BindAddress: ":8080"}, HealthProbeBindAddress: ":8081", LeaderElection: true, LeaderElectionID: "my-operator-lock", }) if err != nil { ctrl.Log.Error(err, "unable to create manager") os.Exit(1) } mgr.AddHealthzCheck("healthz", healthz.Ping) mgr.AddReadyzCheck("readyz", healthz.Ping) if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { ctrl.Log.Error(err, "problem running manager") os.Exit(1) } } ``` ### Response #### Success Response - **Manager** (manager.Manager) - **error** (error) #### Error Handling - Returns an error if the manager cannot be created. ``` -------------------------------- ### Secret Mirror Reconciler Example Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/move-cluster-specific-code-out-of-manager.md This reconciler creates a secret in a mirror cluster for each secret found in a reference cluster. It does not compare secret contents. ```go type secretMirrorReconciler struct { referenceClusterClient, mirrorClusterClient client.Client } func (r *secretMirrorReconciler) Reconcile(r reconcile.Request)(reconcile.Result, error){ s := &corev1.Secret{} if err := r.referenceClusterClient.Get(context.TODO(), r.NamespacedName, s); err != nil { if kerrors.IsNotFound{ return reconcile.Result{}, nil } return reconcile.Result, err } if err := r.mirrorClusterClient.Get(context.TODO(), r.NamespacedName, &corev1.Secret); err != nil { if !kerrors.IsNotFound(err) { return reconcile.Result{}, err } mirrorSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: s.Namespace, Name: s.Name}, Data: s.Data, } return reconcile.Result{}, r.mirrorClusterClient.Create(context.TODO(), mirrorSecret) } return nil } ``` ```go func NewSecretMirrorReconciler(mgr manager.Manager, mirrorCluster cluster.Cluster) error { return ctrl.NewControllerManagedBy(mgr). // Watch Secrets in the reference cluster For(&corev1.Secret{}). // Watch Secrets in the mirror cluster Watches( source.NewKindWithCache(&corev1.Secret{}, mirrorCluster.GetCache()), &handler.EnqueueRequestForObject{}, ). Complete(&secretMirrorReconciler{ referenceClusterClient: mgr.GetClient(), mirrorClusterClient: mirrorCluster.GetClient(), }) } ``` ```go func main(){ mgr, err := manager.New( cfg1, manager.Options{}) if err != nil { panic(err) } mirrorCluster, err := cluster.New(cfg2) if err != nil { panic(err) } if err := mgr.Add(mirrorCluster); err != nil { panic(err) } if err := NewSecretMirrorReconciler(mgr, mirrorCluster); err != nil { panic(err) } if err := mgr.Start(signals.SetupSignalHandler()); err != nil { panic(err) } } ``` -------------------------------- ### Configure Filtered Cache with Selectors Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/use-selectors-at-cache.md Example of overriding the default NewCache function to use a filtered cache. This configuration specifies selectors for corev1.Node and v1beta1.NodeNetworkState objects. ```go ctrl.Options.NewCache = cache.BuilderWithOptions(cache.Options{ SelectorsByObject: cache.SelectorsByObject{ &corev1.Node{}: { Field: fields.SelectorFromSet(fields.Set{"metadata.name": "node01"}), } &v1beta1.NodeNetworkState{}: { Field: fields.SelectorFromSet(fields.Set{"metadata.name": "node01"}), Label: labels.SelectorFromSet(labels.Set{"app": "kubernetes-nmstate})", } } } ) ``` -------------------------------- ### Manage Finalizers with controllerutil Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Add, remove, and check for finalizers to prevent object deletion until cleanup is complete. This example demonstrates adding a finalizer on creation and removing it after cleanup during deletion. ```go import "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" const myFinalizer = "myoperator.example.com/finalizer" func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { obj := &myv1.MyResource{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } if obj.DeletionTimestamp.IsZero() { // Object is not being deleted — register finalizer if controllerutil.AddFinalizer(obj, myFinalizer) { return ctrl.Result{}, r.Update(ctx, obj) } } else { // Object is being deleted — run cleanup if controllerutil.ContainsFinalizer(obj, myFinalizer) { if err := r.cleanupExternalResources(ctx, obj); err != nil { return ctrl.Result{}, err } controllerutil.RemoveFinalizer(obj, myFinalizer) return ctrl.Result{}, r.Update(ctx, obj) } } return ctrl.Result{}, nil } ``` -------------------------------- ### Use client.IgnoreNotFound and client.IgnoreAlreadyExists Helpers Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Provides convenience functions to suppress specific API error types commonly encountered in reconcile loops. client.IgnoreNotFound is useful for Get operations, while client.IgnoreAlreadyExists is beneficial for Create operations. ```go // Instead of: if err := r.Get(ctx, req.NamespacedName, obj); err != nil { if !apierrors.IsNotFound(err) { return ctrl.Result{}, err } return ctrl.Result{}, nil } // Use: if err := r.Get(ctx, req.NamespacedName, obj); client.IgnoreNotFound(err) != nil { return ctrl.Result{}, err } ``` ```go // IgnoreAlreadyExists — useful in Create paths err := r.Create(ctx, cm) if client.IgnoreAlreadyExists(err) != nil { return ctrl.Result{}, err } ``` -------------------------------- ### Register CRD Types with runtime.Scheme Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Register custom resource definition (CRD) types before creating the Manager so the client and cache understand them. This example shows how to add built-in types and custom CRD types to the scheme. ```go import ( "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" myv1 "github.com/example/myoperator/api/v1" ) var scheme = runtime.NewScheme() func init() { _ = clientgoscheme.AddToScheme(scheme) // built-in types (Pod, Deployment, etc.) _ = myv1.AddToScheme(scheme) // custom CRD types } func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, }) // ... } ``` -------------------------------- ### SetupWithManager Pattern for Controllers Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt This is the conventional pattern for controllers where SetupWithManager encapsulates the builder wiring and is called from main.go. It configures the controller to watch specific resources and apply predicates. ```go // In controller file func (r *MyReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&myv1.MyResource{}). Owns(&corev1.ConfigMap{}). Owns(&appsv1.Deployment{}). WithEventFilter(predicate.GenerationChangedPredicate{}). Complete(r) } ``` ```go // In main.go if err := (&controllers.MyReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "MyResource") os.Exit(1) } ``` -------------------------------- ### Basic Structured Logging Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Contrast standard library logging with controller-runtime's structured logging approach using key-value pairs. ```go log.Printf("starting reconciliation for pod %s/%s", podNamespace, podName) ``` ```go logger.Info("starting reconciliation", "pod", req.NamespacedName) ``` -------------------------------- ### Logging Kubernetes Objects Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Demonstrates logging Kubernetes objects directly, allowing for special encoding by Zap. ```go log.Info("this is a Kubernetes object", "pod", somePod) ``` -------------------------------- ### Handle Shutdown Signals with SetupSignalHandler Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Obtain a context that is cancelled upon receiving SIGTERM or SIGINT signals. This context should be passed to `mgr.Start()` to ensure graceful shutdown of the manager when the pod is terminated. ```go import ctrl "sigs.k8s.io/controller-runtime" func main() { ctx := ctrl.SetupSignalHandler() // cancelled on SIGTERM / SIGINT mgr, _ := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) // ... register controllers ... if err := mgr.Start(ctx); err != nil { ctrl.Log.Error(err, "manager exited with error") os.Exit(1) } // graceful shutdown complete } ``` -------------------------------- ### Cleanup Old Envtest Versions Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Remove all installed envtest versions older than a specified version (e.g., older than 1.16). ```shell setup-envtest cleanup <1.16 ``` -------------------------------- ### Use Custom Index URL Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Download envtest binaries from a custom index URL instead of the default GitHub repository. ```shell setup-envtest use --index https://custom.com/envtest-releases.yaml ``` -------------------------------- ### Named and Value-Tagged Loggers Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Shows how to create a named logger and then attach specific values to it for contextual logging. ```go logger := log.Log.WithName("controller").WithName("replicaset") // in reconcile... logger = logger.WithValues("replicaset", req.NamespacedName) // later on in reconcile... logger.Info("doing things with pods", "pod", newPod) ``` -------------------------------- ### Load Kubernetes Configuration with GetConfigOrDie Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Load a `*rest.Config` using standard Kubernetes precedence. `GetConfigOrDie` logs any errors and exits the process if configuration cannot be loaded. QPS and Burst can be adjusted for client-side rate limiting. ```go import ctrl "sigs.k8s.io/controller-runtime" // Returns error cfg, err := ctrl.GetConfig() if err != nil { return err } // Logs error and calls os.Exit(1) on failure cfg = ctrl.GetConfigOrDie() // Adjust QPS/burst if needed (client-side rate limiting disabled by default) cfg.QPS = 50 cfg.Burst = 100 ``` -------------------------------- ### Initialize Manager with Component Config Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/component-config.md Initialize a controller manager using a component configuration file and a scheme. The configuration is unmarshalled into a runtime.Object. ```golang mgr, err := ctrl.NewManagerFromComponentConfig(ctrl.GetConfigOrDie(), scheme, configname, &defaultv1alpha1.DefaultControllerManagerConfiguration{}) if err != nil { // ... } ``` -------------------------------- ### Set Up Structured Logging with Zap Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Configure Zap as the logging backend for controller-runtime. Call `ctrl.SetLogger` before creating a new manager. Loggers can be named at the package level or obtained from the context within a reconciler, pre-enriched with request details. ```go import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) // Set Zap as the logging backend (call before NewManager) ctrl.SetLogger(zap.New(zap.UseDevMode(true))) // Named logger at package/component level setupLog := ctrl.Log.WithName("setup") setupLog.Info("starting operator", "version", "v1.0.0") // In reconciler — logger pre-enriched with name/namespace from context func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) logger.Info("reconciling", "generation", obj.Generation) logger.Error(err, "failed to update", "resource", req.NamespacedName) // Enrich logger and store back in context ctx = log.IntoContext(ctx, logger.WithValues("phase", "cleanup")) return ctrl.Result{}, r.cleanup(ctx, obj) } ``` -------------------------------- ### Client - CRUD Operations Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt The `client.Client` interface provides methods for performing CRUD operations (Get, List, Create, Update, Patch, Delete) on Kubernetes resources. ```APIDOC ## client.Client Operations ### Description The `client.Client` interface offers typed methods to interact with the Kubernetes API server for common operations like retrieving, listing, creating, updating, patching, and deleting resources. ### Methods #### Get Retrieves a single object. - **Usage**: `c.Get(ctx, client.ObjectKey{Namespace: "default", Name: "my-pod"}, pod)` #### List Lists multiple objects, optionally filtered by namespace and labels. - **Usage**: `c.List(ctx, podList, client.InNamespace("default"), client.MatchingLabels({"app": "my-app"}))` #### Create Creates a new object. - **Usage**: `c.Create(ctx, newCM)` #### Update Updates an existing object. - **Usage**: `c.Update(ctx, pod)` #### Patch Applies a patch to an object (e.g., strategic merge patch). - **Usage**: `c.Patch(ctx, pod, patch)` #### Delete Deletes an object. - **Usage**: `c.Delete(ctx, pod, client.GracePeriodSeconds(0))` #### Update Status Subresource Updates the status subresource of an object. - **Usage**: `c.Status().Update(ctx, pod)` #### SubResource Token Creation Creates a subresource, such as a token for a ServiceAccount. - **Usage**: `c.SubResource("token").Create(ctx, sa, token)` #### IgnoreNotFound Helper A convenience function to ignore `NotFound` errors when performing operations like `Get`. - **Usage**: `client.IgnoreNotFound(c.Get(ctx, key, obj))` ``` -------------------------------- ### Initialize Manager with Flags and Component Config Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/component-config.md Initialize a controller manager with a component configuration, allowing for the use of flags to set values on the configuration type before initialization. ```golang leaderElect := true config := &defaultv1alpha1.DefaultControllerManagerConfiguration{ Spec: configv1alpha1.ControllerManagerConfiguration{ LeaderElection: configv1alpha1.LeaderElectionConfiguration{ LeaderElect: &leaderElect, }, }, } mgr, err := ctrl.NewManagerFromComponentConfig(ctrl.GetConfigOrDie(), scheme, configname, config) if err != nil { // ... } ``` -------------------------------- ### Download Latest Envtest Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Download the latest available envtest binary and display information about it. ```shell setup-envtest use ``` -------------------------------- ### Build a Controller with `ctrl.NewControllerManagedBy` Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Use the fluent builder `ctrl.NewControllerManagedBy` to define a controller's behavior. Specify the primary resource to reconcile with `For`, owned child resources to watch with `Owns`, and additional watches with `Watches`. Event filters can be applied using `WithEventFilter`, and concurrency options with `WithOptions`. ```go import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/predicate" ) err := ctrl.NewControllerManagedBy(mgr). For(&appsv1.Deployment{}). // reconcile Deployments Owns(&corev1.Pod{}). // re-reconcile owner when Pod changes WithEventFilter(predicate.GenerationChangedPredicate{}). // skip status-only updates WithOptions(controller.Options{MaxConcurrentReconciles: 3}). Named("deployment-controller"). Complete(&DeploymentReconciler{Client: mgr.GetClient()}) if err != nil { return err } ``` -------------------------------- ### Add Custom Runnables to Manager Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Add custom long-running components to the manager using the `mgr.Add` method. These components implement the `manager.Runnable` interface or can be defined using `manager.RunnableFunc` for simpler cases. They are started and stopped with the manager. ```go import "sigs.k8s.io/controller-runtime/pkg/manager" // Implement Runnable interface type myWorker struct{ client client.Client } func (w *myWorker) Start(ctx context.Context) error { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: log.Log.Info("worker tick") case <-ctx.Done(): return nil } } } if err := mgr.Add(&myWorker{client: mgr.GetClient()}); err != nil { return err } // Or use RunnableFunc for simple cases mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { <-ctx.Done() return nil })) ``` -------------------------------- ### Logging Errors Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Illustrates the recommended way to log errors using log.Error, which allows for special handling by logr implementations. ```go log.Error(err, "Reconciler error") ``` -------------------------------- ### Registering and Using a FieldIndexer in Go Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Set up a custom cache index during manager initialization to enable efficient filtered List queries on arbitrary object fields in reconcilers. ```go // Setup during manager initialization (before Start) if err := mgr.GetFieldIndexer().IndexField( context.Background(), &corev1.Pod{}, "spec.nodeName", func(obj client.Object) []string { pod := obj.(*corev1.Pod) return []string{pod.Spec.NodeName} }, ); err != nil { return err } // Use in reconciler func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { podsOnNode := &corev1.PodList{} if err := r.List(ctx, podsOnNode, client.MatchingFields{"spec.nodeName": req.Name}, ); err != nil { return ctrl.Result{}, err } log.FromContext(ctx).Info("pods on node", "count", len(podsOnNode.Items)) return ctrl.Result{}, nil } ``` -------------------------------- ### Enable Warmup for All Controllers Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/warmreplicas.md Configure the manager to enable the warmup phase for all controllers by default. This option is set within the manager's options. ```go mgr, err := ctrl.NewManager(cfg, ctrl.Options{ Controller: config.Controller{ EnableWarmup: new(true), // make every controller warm up }, }) if err != nil { panic(err) } ``` -------------------------------- ### Controller Builder - ctrl.NewControllerManagedBy Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Fluent builder for wiring a controller, specifying resources to reconcile, owned child resources to watch, and other configurations. ```APIDOC ## Controller Builder - `ctrl.NewControllerManagedBy` ### Description Fluent builder for wiring a controller: which resource to reconcile (`For`), which owned child resources to watch (`Owns`), additional watches with custom handlers (`Watches`), event filters (`WithEventFilter`), and concurrency options (`WithOptions`). ### Method `ctrl.NewControllerManagedBy(mgr)` ### Parameters - **mgr** (manager.Manager): The manager instance. #### Builder Methods - **For**(&resource{}): Specifies the primary resource to reconcile. - **Owns**(&resource{}): Watches owned child resources and re-reconciles the owner when they change. - **Watches** (custom watches with custom handlers) - **WithEventFilter** (predicate.Predicate): Filters events, e.g., `predicate.GenerationChangedPredicate{}` to skip status-only updates. - **WithOptions** (controller.Options): Configures concurrency options, e.g., `MaxConcurrentReconciles`. - **Named** (string): Assigns a name to the controller. - **Complete** (Reconciler): Completes the builder and registers the controller with the manager. ### Request Example ```go import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/predicate" ) err := ctrl.NewControllerManagedBy(mgr). For(&appsv1.Deployment{}). // reconcile Deployments Owns(&corev1.Pod{}). // re-reconcile owner when Pod changes WithEventFilter(predicate.GenerationChangedPredicate{}). // skip status-only updates WithOptions(controller.Options{MaxConcurrentReconciles: 3}). Named("deployment-controller"). Complete(&DeploymentReconciler{Client: mgr.GetClient()}) if err != nil { return err } ``` ### Response #### Success Response - **error** (error): Returns nil if the controller is successfully registered. ``` -------------------------------- ### Use KubeBuilder Assets Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/tools/setup-envtest/README.md Use envtest binaries, prioritizing the value from the KUBEBUILDER_ASSETS environment variable if set, otherwise following the default logic for 'use'. ```shell setup-envtest --use-env ``` -------------------------------- ### Logger with Values in Reconcile Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Demonstrates how to create a logger with specific values attached within a Reconcile function. ```go func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Response, error) { logger := logger.WithValues("pod", req.NamespacedName) // do some stuff logger.Info("starting reconciliation") } ``` -------------------------------- ### Registering Webhooks with ctrl.NewWebhookManagedBy in Go Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Define and register mutating (defaulter) and validating webhooks using the strongly-typed fluent builder provided by controller-runtime. ```go import ( "context" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // Defaulter — implements admission.Defaulter[T] type PodDefaulter struct{} func (d *PodDefaulter) Default(ctx context.Context, pod *corev1.Pod) error { if pod.Labels == nil { pod.Labels = map[string]string{} } pod.Labels["defaulted-by"] = "my-operator" return nil } // Validator — implements admission.Validator[T] type PodValidator struct{} func (v *PodValidator) ValidateCreate(ctx context.Context, pod *corev1.Pod) (admission.Warnings, error) { if pod.Spec.ServiceAccountName == "" { return nil, fmt.Errorf("pod must have a serviceAccountName") } return nil, nil } func (v *PodValidator) ValidateUpdate(ctx context.Context, old, new *corev1.Pod) (admission.Warnings, error) { return nil, nil } func (v *PodValidator) ValidateDelete(ctx context.Context, pod *corev1.Pod) (admission.Warnings, error) { return nil, nil } // Register webhooks in main if err := ctrl.NewWebhookManagedBy(mgr, &corev1.Pod{}). WithDefaulter(&PodDefaulter{}). WithValidator(&PodValidator{}). Complete(); err != nil { return err } ``` -------------------------------- ### Cache Configuration Structs Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/cache_options.md Defines the core data structures for configuring cache behavior, including options for specific objects, namespaces, and default settings. ```go const ( AllNamespaces = corev1.NamespaceAll ) type Config struct { // LabelSelector specifies a label selector. A nil value allows to // default this. LabelSelector labels.Selector // FieldSelector specifics a field selector. A nil value allows to // default this. FieldSelector fields.Selector // Transform specifies a transform func. A nil value allows to default // this. Transform toolscache.TransformFunc // UnsafeDisableDeepCopy specifies if List and Get requests against the // cache should not DeepCopy. A nil value allows to default this. UnsafeDisableDeepCopy *bool } type ByObject struct { // Namespaces maps a namespace name to cache setting. If set, only the // namespaces in this map will be cached. // // Settings in the map value that are unset because either the value as a // whole is nil or because the specific setting is nil will be defaulted. // Use an empty value for the specific setting to prevent that. // // It is possible to have specific Config for just some namespaces // but cache all namespaces by using the AllNamespaces const as the map key. // This wil then include all namespaces that do not have a more specific // setting. // // A nil map allows to default this to the cache's DefaultNamespaces setting. // An empty map prevents this. // // This must be unset for cluster-scoped objects. Namespaces map[string]Config // Config will be used for cluster-scoped objects and to default // Config in the Namespaces field. // // It gets defaulted from the cache'sDefaultLabelSelector, DefaultFieldSelector, // DefaultUnsafeDisableDeepCopy and DefaultTransform. Config *Config } type Options struct { // ByObject specifies per-object cache settings. If unset for a given // object, this will fall through to Default* settings. ByObject map[client.Object]ByObject // DefaultNamespaces maps namespace names to cache settings. If set, it // will be used for all objects that have a nil Namespaces setting. // // It is possible to have a specific Config for just some namespaces // but cache all namespaces by using the `AllNamespaces` const as the map // key. This wil then include all namespaces that do not have a more // specific setting. // // The options in the Config that are nil will be defaulted from // the respective Default* settings. DefaultNamespaces map[string]Config // DefaultLabelSelector is the label selector that will be used as // the default field label selector for everything that doesn't // have one configured. DefaultLabelSelector labels.Selector // DefaultFieldSelector is the field selector that will be used as // the default field selector for everything that doesn't have // one configured. DefaultFieldSelector fields.Selector // DefaultUnsafeDisableDeepCopy is the default for UnsafeDisableDeepCopy // for everything that doesn't specify this. DefaultUnsafeDisableDeepCopy *bool // DefaultTransform will be used as transform for all object types // unless they have a more specific transform set in ByObject. DefaultTransform toolscache.TransformFunc // HTTPClient is the http client to use for the REST client HTTPClient *http.Client // Scheme is the scheme to use for mapping objects to GroupVersionKinds Scheme *runtime.Scheme // Mapper is the RESTMapper to use for mapping GroupVersionKinds to Resources Mapper meta.RESTMapper // SyncPeriod determines the minimum frequency at which watched resources are // reconciled. A lower period will correct entropy more quickly, but reduce // responsiveness to change if there are many watched resources. Change this // value only if you know what you are doing. Defaults to 10 hours if unset. // there will a 10 percent jitter between the SyncPeriod of all controllers // so that all controllers will not send list requests simultaneously. // // This applies to all controllers. // // A period sync happens for two reasons: // 1. To insure against a bug in the controller that causes an object to not // be requeued, when it otherwise should be requeued. // 2. To insure against an unknown bug in controller-runtime, or its dependencies, // that causes an object to not be requeued, when it otherwise should be // requeued, or to be removed from the queue, when it otherwise should not // be removed. // // If you want // 1. to insure against missed watch events, or // 2. to poll services that cannot be watched, // then we recommend that, instead of changing the default period, the // controller requeue, with a constant duration `t`, whenever the controller // is "done" with an object, and would otherwise not requeue it, i.e., we // recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`, // instead of `reconcile.Result{}`. SyncPeriod *time.Duration } ``` -------------------------------- ### Registering Health and Readiness Probes with Manager in Go Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Register liveness and readiness checks with the Manager, exposing them as HTTP endpoints, including built-in `healthz.Ping` and custom checkers. ```go import "sigs.k8s.io/controller-runtime/pkg/healthz" mgr, _ := ctrl.NewManager(cfg, ctrl.Options{ HealthProbeBindAddress: ":8081", }) // Ping check (always passes — confirms the process is alive) if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { return err } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { return err } // Custom check — e.g., verify database connectivity dbCheck := healthz.CheckerFunc(func(req *http.Request) error { if !db.IsConnected() { return fmt.Errorf("database not connected") } return nil }) if err := mgr.AddReadyzCheck("database", dbCheck); err != nil { return err } // GET :8081/healthz → 200 ok // GET :8081/readyz → 200 ok (or 500 with details if any check fails) ``` -------------------------------- ### Wait for Cache Sync Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/move-cluster-specific-code-out-of-manager.md Iterates through all caches and waits for them to be synchronized before proceeding. This is typically used during manager startup. ```go for _, cache := range cm.caches { cache.WaitForCacheSync(cm.internalStop) } ``` -------------------------------- ### Cache Constructor with Options Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/use-selectors-at-cache.md Provides a builder function to create a cache constructor using provided cache.Options. This allows users to configure SelectorsByObject for cache filtering. ```go func BuilderWithOptions(options cache.Options) NewCacheFunc { ... ``` -------------------------------- ### Apply Selectors in Informer ListWatch Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/use-selectors-at-cache.md Demonstrates how the selector logic is applied to the informer's ListWatch function, filtering options based on configured selectors. ```go # At pkg/cache/internal/informers_map.go ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { ip.selectors[gvk].ApplyToList(&opts) ... ``` -------------------------------- ### Debug Logging with V(1) Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/TMP-LOGGING.md Marks a log line as a debug message using V(1), suitable for verbose information. ```go logger.V(1).Info("this is particularly verbose!", "state of the world", allKubernetesObjectsEverywhere) ``` -------------------------------- ### AddOpts Struct for PriorityQueue Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/priorityqueue.md Specifies options for adding items to the PriorityQueue, including a delay duration (After), whether to use rate limiting, and the priority level of the item. ```go type AddOpts struct { // After is a duration after which the object will be available for // reconciliation. If the object is already in the workqueue, the // lowest of existing and new After period will be used. After time.Duration // Ratelimited specifies if the ratelimiter should be used to // determine a wait period. If the object is already in the // workqueue, the lowest of existing and new wait period will be // used. RateLimited bool // Priority specifies the priority of the object. Objects with higher // priority are returned before objects with lower priority. If the // object is already in the workqueue, the priority will be updated // to the highest of existing and new priority. // // The default value is 0. Priority int } ``` -------------------------------- ### Implementing a Custom Admission.Handler for Webhooks in Go Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Implement the admission.Handler interface directly to gain full control over admission responses, including JSON patch operations, for custom webhook logic. ```go import ( "net/http" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) type podAnnotator struct { decoder admission.Decoder } func (a *podAnnotator) Handle(ctx context.Context, req admission.Request) admission.Response { pod := &corev1.Pod{} if err := a.decoder.Decode(req, pod); err != nil { return admission.Errored(http.StatusBadRequest, err) } if pod.Annotations == nil { pod.Annotations = map[string]string{} } pod.Annotations["example.com/handled-at"] = time.Now().Format(time.RFC3339) marshaledPod, err := json.Marshal(pod) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod) } // Register as a raw handler on the webhook server mgr.GetWebhookServer().Register("/mutate-v1-pod", &admission.Webhook{ Handler: &podAnnotator{decoder: admission.NewDecoder(mgr.GetScheme())}, }) ``` -------------------------------- ### Define Warmup Runnable Interface Source: https://github.com/kubernetes-sigs/controller-runtime/blob/main/designs/warmreplicas.md Defines the internal interface for runnables that support a warmup phase. This interface is used by the manager during its startup sequence. ```go type warmupRunnable interface { Warmup(context.Context) error } ``` -------------------------------- ### Finalizer Registry Management Source: https://context7.com/kubernetes-sigs/controller-runtime/llms.txt Utilize the Finalizer Registry for managing multiple finalizers. This abstraction handles adding/removing finalizers and executing cleanup logic automatically. ```go import "sigs.k8s.io/controller-runtime/pkg/finalizer" type myFinalizer struct{ client client.Client } func (f *myFinalizer) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) { // Cleanup external resources log.FromContext(ctx).Info("cleaning up", "object", obj.GetName()) return finalizer.Result{}, nil } // Register during controller setup finalizers := finalizer.NewFinalizers() if err := finalizers.Register("myoperator.example.com/cleanup", &myFinalizer{client: mgr.GetClient()}); err != nil { return err } // In reconcile loop func (r *MyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { obj := &myv1.MyResource{} if err := r.Get(ctx, req.NamespacedName, obj); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } result, err := r.finalizers.Finalize(ctx, obj) if err != nil { return ctrl.Result{}, err } if result.Updated { if err := r.Update(ctx, obj); err != nil { return ctrl.Result{}, err } } return ctrl.Result{}, nil } ```