Controller
Controllers (pkg/controller) use events (pkg/event) to eventually trigger reconcile requests. They may be constructed manually, but are often constructed with a Builder (pkg/builder), which eases the wiring of event sources (pkg/source), like Kubernetes API object changes, to event handlers (pkg/handler), like "enqueue a reconcile request for the object owner". Predicates (pkg/predicate) can be used to filter which events actually trigger reconciles. There are pre-written utilities for the common cases, and interfaces and helpers for advanced cases.
Controller interface
Controller embeds reconcile.Reconciler.
type Controller interface {
// Reconciler is called to reconcile an object by Namespace/Name
reconcile.Reconciler
// Watch takes events provided by a Source and uses the EventHandler to
// enqueue reconcile.Requests in response to the events.
//
// Watch may be provided one or more Predicates to filter events before
// they are given to the EventHandler. Events will be passed to the
// EventHandler if all provided Predicates evaluate to true.
Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error
// Start starts the controller. Start blocks until the context is closed or a
// controller has an error starting.
Start(ctx context.Context) error
// GetLogger returns this controller logger prefilled with basic information.
GetLogger() logr.Logger
}
Controller type
type Controller struct {
Name string
MaxConcurrentReconciles int
Do reconcile.Reconciler
MakeQueue func() workqueue.RateLimitingInterface
Queue workqueue.RateLimitingInterface
SetFields func(i interface{}) error
mu sync.Mutex
Started bool
ctx context.Context
CacheSyncTimeout time.Duration
startWatches []watchDescription
LogConstructor func(request *reconcile.Request) logr.Logger
RecoverPanic bool
}
Do: reconcile.Reconciler
How Controller is used
Interestingly, New requires Manager and when a controller is created, the controller is added to the manager. A Controller must be run by a Manager. A controller is added to controllerManager via builder, more specifically NewControllerManagedBy. For more details, you can also check manager.
// New returns a new Controller registered with the Manager. The Manager will ensure that shared Caches have
// been synced before the Controller is Started.
func New(name string, mgr manager.Manager, options Options) (Controller, error) {
c, err := NewUnmanaged(name, mgr, options)
if err != nil {
return nil, err
}
// Add the controller as a Manager components
return c, mgr.Add(c)
}
ManagerandReconcilerneed to be prepared.- Call
NewControllerManagedBy(mgr)andComplete(r)with the manager and reconciler. Builer.buildcallsbldr.doControllerto create a controller with Controller.New.- Inject dependencies to reconciler with
Manager.SetFields(reconciler)(ref) - Inititialize Controller
&controller.Controller{ Do: options.Reconciler, MakeQueue: func() workqueue.RateLimitingInterface { return workqueue.NewNamedRateLimitingQueue(options.RateLimiter, name) }, MaxConcurrentReconciles: options.MaxConcurrentReconciles, CacheSyncTimeout: options.CacheSyncTimeout, SetFields: mgr.SetFields, Name: name, LogConstructor: options.LogConstructor, RecoverPanic: options.RecoverPanic, }Manager.SetFieldsis passed toctrl.SetFieldsworkqueue.NewNamedRateLimitingQueueis set toctrl.MakeQueueReconcileris set toctrl.Do
- Register the controller to manager with
mgr.Add(controller)(ref) - The created controller is set to
bldr.ctrl(ref)
- Inject dependencies to reconciler with
Builder.buildalso callsbldr.doWatchKindis created forForandOwns.EventHandleris created.- Call
bldr.ctrl.Watch(src, hdlr, allPredicates...)
controller.Watch- Inject the cache to the
srcwithSetFields(given by the Manager). - Inject (sth) to the
eventHandlerandPredicateswithSetFields(not checked what's injected). - If the controller hasn't started yet, store the watches locally (
startWatches) and return. - If the controller has started, calls
src.Start
- Inject the cache to the
controller.Startis called by theManagerwhenManager.Startis called.- Controller can be started only once.
- Create a
QueuewithMakeQueuefunc which is specified inNew. - For the stored watches (
startWatches), callwatch.src.Startto start src to monitor API server and enqueue modified objects. - Convert
KindtosyncingSourceand callsyncingSource.WaitForSync. This waits until the cache is synced insrc.Startby checking ks.started channel. - Clean up the
startWatchesafter the caches of all the watches are in-sync. - Call
processNextWorkItemwithMaxConcurrentReconcilesgo routines.
Watch func
- Where is
Watchcalled?Watchis called in bldr.doWatch in builder forFor,Owns, andWatchesconfigured with a controller builder.
WatchcallsSetFieldsforSource,EventHandler, andPredicates.if err := c.SetFields(src); err != nil { return err } if err := c.SetFields(evthdler); err != nil { return err } for _, pr := range prct { if err := c.SetFields(pr); err != nil { return err } }SetFieldsis one of the Controller's fieldSetFields func(i interface{}) error, which is set when initializing in NewUnmanaged frommgr.SetFields.- For more details, you can check inject and manager