A set of go libraries for building Kubernetes controllers with structured abstractions for managers, reconcilers, clients, caches, webhooks, and testing
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)
}Install with Tessl CLI
npx tessl i tessl/golang-sigs-k8s-io--controller-runtime