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 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
}
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
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)
}
Manager
andReconciler
need to be prepared.- Call
NewControllerManagedBy(mgr)
andComplete(r)
with the manager and reconciler. Builer.build
callsbldr.doController
to 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.SetFields
is passed toctrl.SetFields
workqueue.NewNamedRateLimitingQueue
is set toctrl.MakeQueue
Reconciler
is 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.build
also callsbldr.doWatch
Kind
is created forFor
andOwns
.EventHandler
is created.- Call
bldr.ctrl.Watch(src, hdlr, allPredicates...)
controller.Watch
- Inject the cache to the
src
withSetFields
(given by the Manager). - Inject (sth) to the
eventHandler
andPredicates
withSetFields
(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.Start
is called by theManager
whenManager.Start
is called.- Controller can be started only once.
- Create a
Queue
withMakeQueue
func which is specified inNew
. - For the stored watches (
startWatches
), callwatch.src.Start
to start src to monitor API server and enqueue modified objects. - Convert
Kind
tosyncingSource
and callsyncingSource.WaitForSync
. This waits until the cache is synced insrc.Start
by checking ks.started channel. - Clean up the
startWatches
after the caches of all the watches are in-sync. - Call
processNextWorkItem
withMaxConcurrentReconciles
go routines.
- Where is
Watch
called?Watch
is called in bldr.doWatch in builder forFor
,Owns
, andWatches
configured with a controller builder.
Watch
callsSetFields
forSource
,EventHandler
, andPredicate
s.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 } }
SetFields
is 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