|
|
||
|---|---|---|
| .. | ||
| controller.go | ||
| doc.go | ||
| e2e_test.go | ||
| errors.go | ||
| errors_test.go | ||
| example_test.go | ||
| owner_references.go | ||
| owner_references_test.go | ||
| README.md | ||
| reconciler.go | ||
| reconciler_test.go | ||
Controller Package
The controller package provides a generic Kubernetes controller framework inspired by Knative's controller implementation. It simplifies building Kubernetes controllers with automatic update detection, conflict resolution, and owner reference support.
Features
- Type-safe generic controllers - Build controllers for any Kubernetes resource type
- Simple reconciler interface - Just implement one method:
Reconcileor pass a function. - Automatic update detection - The framework detects and persists changes to your objects
- Built-in conflict resolution - Automatic retry with exponential backoff
- Owner reference support - Automatically reconcile owners when owned resources change
- Error handling patterns - Control reconciliation behavior with special error types
- Context-aware logging - Integrated with clog for structured logging
Quick Start
import (
"context"
"github.com/imjasonh/client-go2/controller"
"github.com/imjasonh/client-go2/generic"
corev1 "k8s.io/api/core/v1"
)
// Create a reconciler function
reconciler := controller.ReconcilerFunc[*corev1.ConfigMap](
func(ctx context.Context, cm *corev1.ConfigMap) error {
// Your reconciliation logic here
cm.Annotations["processed"] = "true"
return nil
})
// Create and run the controller
client, _ := generic.NewClient[*corev1.ConfigMap](config)
ctrl := controller.New(client, reconciler, nil)
ctrl.Run(ctx)
Reconciler Interface
The core interface has just one method:
type Reconciler[T runtime.Object] interface {
Reconcile(ctx context.Context, obj T) error
}
You can implement this interface directly or use ReconcilerFunc for simple cases.
Automatic Updates
The controller automatically detects and persists changes made during reconciliation:
- Metadata changes - Updates to annotations, labels, and finalizers
- Status updates - Changes to the status subresource
- Spec changes - Logged as warnings (specs should not be modified in reconcilers)
Example:
func (r *MyReconciler) Reconcile(ctx context.Context, pod *corev1.Pod) error {
// These changes will be automatically persisted
pod.Labels["processed"] = "true"
pod.Status.Phase = corev1.PodRunning
// Add a finalizer
pod.Finalizers = append(pod.Finalizers, "example.com/finalizer")
return nil
}
Error Handling
Control reconciliation behavior by returning special errors:
// Requeue after a specific duration
return controller.RequeueAfter(5 * time.Minute)
// Requeue immediately with rate limiting
return controller.RequeueImmediately()
// Don't retry this error
return controller.PermanentError(fmt.Errorf("unrecoverable error"))
// Normal errors are retried with exponential backoff
return fmt.Errorf("temporary error")
Controller Options
Configure the controller behavior:
opts := &controller.Options[*corev1.Pod]{
// Watch only a specific namespace
Namespace: "my-namespace",
// Number of concurrent workers
Concurrency: 5,
// Custom deep copy function (default uses JSON)
DeepCopyFunc: func(pod *corev1.Pod) *corev1.Pod {
return pod.DeepCopy()
},
// Watch owned resources
OwnedTypes: []controller.OwnedType{
{
Client: secretClient,
OwnerGVK: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
IsController: true,
},
},
}
ctrl := controller.New(client, reconciler, opts)
Owner References
The controller can watch owned resources and reconcile owners when owned resources change:
// In your reconciler, set owner references
func (r *MyReconciler) Reconcile(ctx context.Context, pod *corev1.Pod) error {
// Create a owned secret
secret := &corev1.Secret{...}
// Set owner reference
controller.SetOwnerReference(secret, pod, scheme.Scheme, true)
// Create the secret...
return nil
}
// Configure the controller to watch secrets
opts := &controller.Options[*corev1.Pod]{
OwnedTypes: []controller.OwnedType{
{
Client: secretClient,
OwnerGVK: schema.GroupVersionKind{Version: "v1", Kind: "Pod"},
IsController: true, // Only watch controller references
},
},
}
Complete Example
See examples/controller/main.go for a complete example that validates ConfigMaps.
Best Practices
- Don't modify spec - Reconcilers should only update metadata and status
- Use finalizers carefully - Always check DeletionTimestamp before adding
- Handle conflicts - The framework handles most conflicts, but design for idempotency
- Log with context - Use clog for structured, context-aware logging
- Return appropriate errors - Use RequeueAfter for known delays, PermanentError for unrecoverable failures
Testing
The package includes comprehensive unit and e2e tests. See controller_test.go and e2e_test.go for examples of testing your controllers.