Utility packages for common operations including finalizer management, scheme building, configuration, certificate watching for webhook servers, and controller utilities.
Controller-runtime provides several utility packages:
Import Path: sigs.k8s.io/controller-runtime/pkg/finalizer
The finalizer package provides structured finalizer management.
package finalizer
import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// Finalizer manages adding and removing finalizers
type Finalizer interface {
// Finalize adds or removes a finalizer based on deletion timestamp
Finalize(context.Context, client.Object) (Result, error)
}
// Registerer registers finalizers
type Registerer interface {
// Register registers a finalizer by key
Register(key string, f Finalizer) error
}
// Finalizers combines Finalizer and Registerer
type Finalizers interface {
Registerer
Finalizer
}
// NewFinalizers creates a new Finalizers instance
func NewFinalizers() Finalizers// Result holds information about what was updated by finalizers
type Result struct {
// Updated indicates if the object was updated
Updated bool
// StatusUpdated indicates if the status was updated
StatusUpdated bool
}package controllers
import (
"context"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/finalizer"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type MyReconciler struct {
client.Client
Scheme *runtime.Scheme
Finalizers finalizer.Finalizers
}
type myFinalizer struct {
client.Client
}
func (f *myFinalizer) Finalize(ctx context.Context, obj client.Object) (finalizer.Result, error) {
pod := obj.(*corev1.Pod)
// Perform cleanup logic
log.Info("cleaning up resources for pod", "name", pod.Name)
// Clean up external resources
if err := cleanupExternalResources(ctx, pod); err != nil {
return finalizer.Result{}, err
}
return finalizer.Result{Updated: true}, nil
}
func (r *MyReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
var pod corev1.Pod
if err := r.Get(ctx, req.NamespacedName, &pod); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err)
}
// Run finalizers
result, err := r.Finalizers.Finalize(ctx, &pod)
if err != nil {
return reconcile.Result{}, err
}
if result.Updated {
// Update the object if finalizers modified it
if err := r.Update(ctx, &pod); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
func (r *MyReconciler) SetupWithManager(mgr ctrl.Manager) error {
// Create and register finalizers
r.Finalizers = finalizer.NewFinalizers()
if err := r.Finalizers.Register("my-finalizer", &myFinalizer{Client: r.Client}); err != nil {
return err
}
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Pod{}).
Complete(r)
}Import Path: sigs.k8s.io/controller-runtime/pkg/controller/controllerutil
Utilities for common controller operations.
package controllerutil
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// AddFinalizer adds a finalizer to the object
func AddFinalizer(o client.Object, finalizer string) (finalizersUpdated bool)
// RemoveFinalizer removes a finalizer from the object
func RemoveFinalizer(o client.Object, finalizer string) (finalizersUpdated bool)
// ContainsFinalizer checks if the object has a specific finalizer
func ContainsFinalizer(o client.Object, finalizer string) bool// SetControllerReference sets the controller owner reference
func SetControllerReference(owner, controlled metav1.Object, scheme *runtime.Scheme, opts ...OwnerReferenceOption) error
// SetOwnerReference sets an owner reference (not necessarily controller)
func SetOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme, opts ...OwnerReferenceOption) error
// HasControllerReference checks if an object has a controller reference
func HasControllerReference(object metav1.Object) bool
// HasOwnerReference checks if an object has a specific owner reference
func HasOwnerReference(ownerRefs []metav1.OwnerReference, obj client.Object, scheme *runtime.Scheme) (bool, error)
// RemoveControllerReference removes the controller owner reference
func RemoveControllerReference(owner, object metav1.Object, scheme *runtime.Scheme) error
// RemoveOwnerReference removes an owner reference
func RemoveOwnerReference(owner, object metav1.Object, scheme *runtime.Scheme) error// OwnerReferenceOption modifies OwnerReference behavior
type OwnerReferenceOption func(*metav1.OwnerReference)
// WithBlockOwnerDeletion sets the BlockOwnerDeletion field
func WithBlockOwnerDeletion(blockOwnerDeletion bool) OwnerReferenceOption// CreateOrUpdate creates or updates the given object in the Kubernetes cluster
func CreateOrUpdate(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error)
// CreateOrPatch creates or patches the given object in the Kubernetes cluster
func CreateOrPatch(ctx context.Context, c client.Client, obj client.Object, f MutateFn) (OperationResult, error)
// MutateFn is a function that modifies an object
type MutateFn func() error// OperationResult is the result of a CreateOrUpdate or CreateOrPatch operation
type OperationResult string
const (
OperationResultNone OperationResult = "unchanged"
OperationResultCreated OperationResult = "created"
OperationResultUpdated OperationResult = "updated"
OperationResultUpdatedStatus OperationResult = "updatedStatus"
OperationResultUpdatedStatusOnly OperationResult = "updatedStatusOnly"
)// AlreadyOwnedError is returned when an object is already owned by another controller
type AlreadyOwnedError struct {
Object metav1.Object
Owner metav1.OwnerReference
}
func (e *AlreadyOwnedError) Error() stringpackage controllers
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type DeploymentReconciler struct {
client.Client
Scheme *runtime.Scheme
}
func (r *DeploymentReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
var deployment appsv1.Deployment
if err := r.Get(ctx, req.NamespacedName, &deployment); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err)
}
// Add finalizer
if !controllerutil.ContainsFinalizer(&deployment, "my-finalizer") {
controllerutil.AddFinalizer(&deployment, "my-finalizer")
if err := r.Update(ctx, &deployment); err != nil {
return reconcile.Result{}, err
}
}
// Check if being deleted
if !deployment.DeletionTimestamp.IsZero() {
// Perform cleanup
if controllerutil.ContainsFinalizer(&deployment, "my-finalizer") {
// Do cleanup work
if err := r.cleanupResources(ctx, &deployment); err != nil {
return reconcile.Result{}, err
}
// Remove finalizer
controllerutil.RemoveFinalizer(&deployment, "my-finalizer")
if err := r.Update(ctx, &deployment); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
// Create or update a ConfigMap owned by this Deployment
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: deployment.Name + "-config",
Namespace: deployment.Namespace,
},
}
result, err := controllerutil.CreateOrUpdate(ctx, r.Client, configMap, func() error {
// Set desired state
configMap.Data = map[string]string{
"key": "value",
}
// Set owner reference
return controllerutil.SetControllerReference(&deployment, configMap, r.Scheme)
})
if err != nil {
return reconcile.Result{}, err
}
log.Info("ConfigMap operation", "result", result)
return reconcile.Result{}, nil
}
func (r *DeploymentReconciler) cleanupResources(ctx context.Context, deployment *appsv1.Deployment) error {
// Cleanup logic
return nil
}Import Path: sigs.k8s.io/controller-runtime/pkg/scheme
Utilities for building runtime schemes.
package scheme
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// Builder builds a Scheme for mapping Go types to Kubernetes GroupVersionKinds
type Builder struct {
GroupVersion schema.GroupVersion
runtime.SchemeBuilder
}
// Register registers objects with the scheme builder
func (bld *Builder) Register(object ...runtime.Object) *Builder
// RegisterAll registers all objects from another builder
func (bld *Builder) RegisterAll(b *Builder) *Builder
// AddToScheme adds all registered types to the scheme
func (bld *Builder) AddToScheme(s *runtime.Scheme) error
// Build creates a new scheme with all registered types
func (bld *Builder) Build() (*runtime.Scheme, error)package api
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "myapp.example.com", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme
AddToScheme = SchemeBuilder.AddToScheme
)
func init() {
SchemeBuilder.Register(&MyResource{}, &MyResourceList{})
}
// Usage in main.go
func main() {
scheme := runtime.NewScheme()
// Add built-in types
_ = clientgoscheme.AddToScheme(scheme)
// Add custom types
_ = api.AddToScheme(scheme)
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
})
// ...
}Import Path: sigs.k8s.io/controller-runtime/pkg/config
Configuration options for controllers.
package config
import (
"time"
"github.com/go-logr/logr"
)
// Controller contains configuration options for controllers
type Controller struct {
// SkipNameValidation allows skipping controller name validation
SkipNameValidation *bool
// GroupKindConcurrency maps GroupKind to the number of concurrent reconciles
GroupKindConcurrency map[string]int
// MaxConcurrentReconciles is the maximum number of concurrent reconciles
MaxConcurrentReconciles int
// CacheSyncTimeout is the timeout for waiting for caches to sync
CacheSyncTimeout time.Duration
// RecoverPanic indicates whether panics should be recovered
RecoverPanic *bool
// NeedLeaderElection indicates whether this controller needs leader election
NeedLeaderElection *bool
// EnableWarmup enables controller warmup on startup
EnableWarmup *bool
// UsePriorityQueue indicates whether to use a priority queue
UsePriorityQueue *bool
// Logger is the logger for this controller
Logger logr.Logger
// ReconciliationTimeout is the maximum duration for a single reconcile
ReconciliationTimeout time.Duration
}Import Path: sigs.k8s.io/controller-runtime/pkg/certwatcher
Certificate watcher for automatically reloading TLS certificates.
package certwatcher
import (
"context"
"crypto/tls"
"sync"
"time"
)
// CertWatcher watches certificate and key files for changes
type CertWatcher struct {
sync.RWMutex
// Has unexported fields
}
// New creates a new CertWatcher
func New(certPath, keyPath string) (*CertWatcher, error)// Start starts the certificate watcher
func (cw *CertWatcher) Start(ctx context.Context) error
// GetCertificate returns the current certificate
func (cw *CertWatcher) GetCertificate(_ *tls.ClientHelloInfo) (*tls.Certificate, error)
// ReadCertificate reads and parses the certificate from disk
func (cw *CertWatcher) ReadCertificate() error
// Watch watches for certificate changes and reloads
func (cw *CertWatcher) Watch()
// RegisterCallback registers a callback to be called when the certificate changes
func (cw *CertWatcher) RegisterCallback(callback func(tls.Certificate))
// WithWatchInterval sets the watch interval
func (cw *CertWatcher) WithWatchInterval(interval time.Duration) *CertWatcher
// NeedLeaderElection returns false (cert watcher doesn't need leader election)
func (cw *CertWatcher) NeedLeaderElection() boolImport Path: sigs.k8s.io/controller-runtime/pkg/certwatcher/metrics
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
// ReadCertificateTotal is the total number of certificate reads
ReadCertificateTotal = prometheus.NewCounter(prometheus.CounterOpts{
Name: "certwatcher_read_certificate_total",
Help: "Total number of certificate reads",
})
// ReadCertificateErrors is the total number of certificate read errors
ReadCertificateErrors = prometheus.NewCounter(prometheus.CounterOpts{
Name: "certwatcher_read_certificate_errors_total",
Help: "Total number of certificate read errors",
})
)package main
import (
"context"
"crypto/tls"
"net/http"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/certwatcher"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
func main() {
// Create certificate watcher
certWatcher, err := certwatcher.New(
"/tmp/certs/tls.crt",
"/tmp/certs/tls.key",
)
if err != nil {
panic(err)
}
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
WebhookServer: webhook.NewServer(webhook.Options{
Port: 9443,
TLSOpts: []func(*tls.Config){
func(config *tls.Config) {
config.GetCertificate = certWatcher.GetCertificate
},
},
}),
})
if err != nil {
panic(err)
}
// Add cert watcher to manager
if err := mgr.Add(certWatcher); err != nil {
panic(err)
}
// Register callback for certificate changes
certWatcher.RegisterCallback(func(cert tls.Certificate) {
log.Info("certificate reloaded")
})
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
panic(err)
}
}Import Path: sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue
Priority-based work queues for controllers.
package priorityqueue
import (
"time"
"github.com/go-logr/logr"
"k8s.io/client-go/util/workqueue"
)
// PriorityQueue is a priority queue interface
type PriorityQueue[T comparable] interface {
workqueue.TypedRateLimitingInterface[T]
// AddWithOpts adds items with specific options
AddWithOpts(o AddOpts, Items ...T)
// GetWithPriority retrieves an item with its priority
GetWithPriority() (item T, priority int, shutdown bool)
}
// New creates a new priority queue
func New[T comparable](name string, o ...Opt[T]) PriorityQueue[T]// AddOpts contains options for adding items
type AddOpts struct {
// After is the delay before adding the item
After time.Duration
// RateLimited indicates if the item should be rate limited
RateLimited bool
// Priority is the priority of the item (higher = more important)
Priority *int
}// Opt is an option for configuring a PriorityQueue
type Opt[T comparable] func(*Opts[T])
type Opts[T comparable] struct {
// RateLimiter is the rate limiter to use
RateLimiter workqueue.TypedRateLimiter[T]
// MetricProvider provides metrics for the queue
MetricProvider workqueue.MetricsProvider
// Log is the logger to use
Log logr.Logger
}package controllers
import (
"context"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/priorityqueue"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func SetupControllerWithPriorityQueue(mgr ctrl.Manager) error {
// Create custom queue factory
newQueue := func(name string, rateLimiter workqueue.RateLimiter) workqueue.RateLimitingInterface {
return priorityqueue.New[reconcile.Request](name)
}
return ctrl.NewControllerManagedBy(mgr).
Named("priority-controller").
For(&MyResource{}).
WithOptions(controller.Options{
UsePriorityQueue: ptr(true),
NewQueue: newQueue,
}).
Complete(reconciler)
}
func ptr[T any](v T) *T {
return &v
}Import Path: sigs.k8s.io/controller-runtime/pkg/conversion
Interfaces for API version conversion.
package conversion
import (
"k8s.io/apimachinery/pkg/runtime"
)
// Hub marks that a type is the hub type for conversion
type Hub interface {
runtime.Object
Hub()
}
// Convertible defines capability of a type to be convertible
type Convertible interface {
runtime.Object
// ConvertTo converts this version to the Hub version
ConvertTo(dst Hub) error
// ConvertFrom converts from the Hub version to this version
ConvertFrom(src Hub) error
}See the conversion example in the Webhooks documentation.
package controllers
import (
"context"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type ResourceReconciler struct {
client.Client
Scheme *runtime.Scheme
}
const finalizerName = "myapp.example.com/finalizer"
func (r *ResourceReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
log := ctrl.LoggerFrom(ctx)
var resource corev1.ConfigMap
if err := r.Get(ctx, req.NamespacedName, &resource); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err)
}
// Handle deletion
if !resource.DeletionTimestamp.IsZero() {
if controllerutil.ContainsFinalizer(&resource, finalizerName) {
log.Info("performing cleanup")
// Cleanup external resources
if err := r.cleanupExternalResources(ctx, &resource); err != nil {
return reconcile.Result{}, err
}
// Remove finalizer
controllerutil.RemoveFinalizer(&resource, finalizerName)
if err := r.Update(ctx, &resource); err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
// Add finalizer if not present
if !controllerutil.ContainsFinalizer(&resource, finalizerName) {
controllerutil.AddFinalizer(&resource, finalizerName)
if err := r.Update(ctx, &resource); err != nil {
return reconcile.Result{}, err
}
}
// Create or update a child resource
childResource := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: resource.Name + "-secret",
Namespace: resource.Namespace,
},
}
result, err := controllerutil.CreateOrUpdate(ctx, r.Client, childResource, func() error {
// Set desired state
childResource.StringData = map[string]string{
"key": "value",
}
// Set owner reference so child is deleted when parent is deleted
return controllerutil.SetControllerReference(&resource, childResource, r.Scheme)
})
if err != nil {
return reconcile.Result{}, err
}
log.Info("child resource operation completed", "result", result)
// Requeue after 5 minutes
return reconcile.Result{RequeueAfter: 5 * time.Minute}, nil
}
func (r *ResourceReconciler) cleanupExternalResources(ctx context.Context, resource *corev1.ConfigMap) error {
// Implement cleanup logic for external resources
return nil
}
func (r *ResourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.ConfigMap{}).
Owns(&corev1.Secret{}).
Complete(r)
}