Component Reconciler
Dependent objects are - by definition - the resources returned by the Generate()
method of the used resource generator.
Whenever a component resource (that is, an instance of the component’s custom resource type) is created, udpated, or deleted,
the set of dependent object potentially changes, and the cluster state has to be synchronized with that new declared state.
This synchronization is the job of the reconciler provided by this framework.
Creating the reconciler instance
Typically, a component operator runs one reconciler which is instantiated by calling the following constructor:
package component
func NewReconciler[T Component](
name string,
resourceGenerator manifests.Generator
options ReconcilerOptions
) *Reconciler[T]
The passed type parameter T Component
is the concrete runtime type of the component’s custom resource type. Furthermore,
name
is supposed to be a unique name (typically a DNS name) identifying this component operator in the cluster; ìt will be used in annotations, labels, for leader election, …resourceGenerator
is an implementation of theGenerator
interface, describing how the dependent objects are rendered from the component’s spec.options
can be used to tune the behavior of the reconciler:package component // ReconcilerOptions are creation options for a Reconciler. type ReconcilerOptions struct { // Whether namespaces are auto-created if missing. // If unspecified, true is assumed. CreateMissingNamespaces *bool // How to react if a dependent object exists but has no or a different owner. // If unspecified, AdoptionPolicyIfUnowned is assumed. // Can be overridden by annotation on object level. AdoptionPolicy *AdoptionPolicy // How to perform updates to dependent objects. // If unspecified, UpdatePolicyReplace is assumed. // Can be overridden by annotation on object level. UpdatePolicy *UpdatePolicy // Schemebuilder allows to define additional schemes to be made available in the // target client. SchemeBuilder types.SchemeBuilder }
The object returned by NewReconciler
implements controller-runtime’s Reconciler
interface, and can therefore be used as a drop-in
in kubebuilder managed projects. After creation, the reconciler has to be registered with the responsible controller-runtime manager instance by calling
package component
func (r *Reconciler[T]) SetupWithManager(mgr ctrl.Manager) error
The used manager mgr
has to fulfill a few requirements:
- its client must bypass informer caches for the following types:
- the type
T
itself - the type
CustomResourceDefinition
from theapiextensions.k8s.io/v1
group - the type
APIService
from theapiregistration.k8s.io/v1
group
- the type
- its scheme must recognize at least the following types:
- the types in the API group defined in this repository
- the core group (
v1
) - group
apiextensions.k8s.io/v1
- group
apiregistration.k8s.io/v1
.
Reconciler hooks
Component operators may register hooks to enhance the reconciler logic at certain points, by passing functions of type
package component
// HookFunc is the function signature that can be used to
// establish callbacks at certain points in the reconciliation logic.
// Hooks will be passed the current (potentially unsaved) state of the component.
// Post-hooks will only be called if the according operation (read, reconcile, delete)
// has been successful.
type HookFunc[T Component] func(ctx context.Context, client client.Client, component T) error
to the desired registration functions:
package component
// Register post-read hook with reconciler.
// This hook will be called after the reconciled component object has been retrieved from the Kubernetes API.
func (r *Reconciler[T]) WithPostReadHook(hook HookFunc[T]) *Reconciler[T]
// Register pre-reconcile hook with reconciler.
// This hook will be called if the reconciled component is not in deletion (has no deletionTimestamp set),
// right before the reconcilation of the dependent objects starts.
func (r *Reconciler[T]) WithPreReconcileHook(hook HookFunc[T]) *Reconciler[T]
// Register post-reconcile hook with reconciler.
// This hook will be called if the reconciled component is not in deletion (has no deletionTimestamp set),
// right after the reconcilation of the dependent objects happened, and was successful.
func (r *Reconciler[T]) WithPostReconcileHook(hook HookFunc[T]) *Reconciler[T]
// Register pre-delete hook with reconciler.
// This hook will be called if the reconciled component is in deletion (has a deletionTimestamp set),
// right before the deletion of the dependent objects starts.
func (r *Reconciler[T]) WithPreDeleteHook(hook HookFunc[T]) *Reconciler[T]
// Register post-delete hook with reconciler.
// This hook will be called if the reconciled component is in deletion (has a deletionTimestamp set),
// right after the deletion of the dependent objects happened, and was successful.
func (r *Reconciler[T]) WithPostDeleteHook(hook HookFunc[T]) *Reconciler[T]
Note that the client passed to the hook functions is the client of the manager that was used when calling SetupWithManager()
(that is, the return value of that manager’s GetClient()
method). In addition, reconcile and delete hooks (that is all except the
post-read hook) can retrieve a client for the deployment target by calling utils.ClientFromContext()
.
Tuning the retry behavior
By default, errors returned by the component’s generator or by a registered hook will make the reconciler go
into a backoff managed by controller-runtime (which usually is an exponential backoff, capped at 10 minutes).
However, if the error is or unwraps to a types.RetriableError
, then the retry delay specified at the error
will be used instead of the backoff. Implementations should use
pacakge types
func NewRetriableError(err error, retryAfter *time.Duration) RetriableError {
return RetriableError{err: err, retryAfter: retryAfter}
}
to wrap an error into a RetriableError
. It is allowed to pass retryAfter
as nil; in that case the retry delay
will be determined by calling the component’s GetRetryInterval()
method (if the component or its spec implements
the
package component
// The RetryConfiguration interface is meant to be implemented by components (or their spec) which offer
// tweaking the retry interval (by default, it would be the value of the requeue interval).
type RetryConfiguration interface {
// Get retry interval. Should be greater than 1 minute.
GetRetryInterval() time.Duration
}
interface), or otherwise will be set to the effective requeue interval (see below).
Tuning the requeue behavior
If a component was successfully reconciled, another reconciliation will be scheduled after 10 minutes, by default. This default requeue interval may be overridden by the component by implementing the
package component
// The RequeueConfiguration interface is meant to be implemented by components (or their spec) which offer
// tweaking the requeue interval (by default, it would be 10 minutes).
type RequeueConfiguration interface {
// Get requeue interval. Should be greater than 1 minute.
GetRequeueInterval() time.Duration
}
interface.
Tuning the timeout behavior
If the dependent objects of a component do not reach a ready state after a certain time, the component state will switch from Processing
to Error
.
This timeout restarts counting whenever something changed in the component, or in the manifests of the dependent objects, and by default has the value
of the effective requeue interval, which in turn defaults to 10 minutes.
The timeout may be overridden by the component by implementing the
package component
// The TimeoutConfiguration interface is meant to be implemented by components (or their spec) which offer
// tweaking the processing timeout (by default, it would be the value of the requeue interval).
type TimeoutConfiguration interface {
// Get timeout. Should be greater than 1 minute.
GetTimeout() time.Duration
}
interface.