mirror of
https://github.com/imjasonh/client-go2
synced 2026-07-07 00:33:49 +00:00
* Fix controller example to implement correct interface method - Change ReconcileKind to Reconcile to match the Reconciler interface - Add nil check for secretLister to handle initialization timing - Fix OnError handler in e2e test to not fail immediately on expected errors This allows the controller-owned-lister example to compile and run correctly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Update examples/controller-owned-lister/main.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
165 lines
5 KiB
Markdown
165 lines
5 KiB
Markdown
# 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: `Reconcile` or 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
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
// 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:
|
|
|
|
```go
|
|
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:
|
|
|
|
```go
|
|
// 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](../examples/controller/main.go) for a complete example that validates ConfigMaps.
|
|
|
|
## Best Practices
|
|
|
|
1. **Don't modify spec** - Reconcilers should only update metadata and status
|
|
2. **Use finalizers carefully** - Always check DeletionTimestamp before adding
|
|
3. **Handle conflicts** - The framework handles most conflicts, but design for idempotency
|
|
4. **Log with context** - Use clog for structured, context-aware logging
|
|
5. **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.
|