Skip to content

Related Objects#

A Controller needs to specify related resources if changes to them is meant to trigger the reconciler.

These relations are generally set up with Controller::owns, but we will go through the different variants below.

Owned Relation#

The Controller::owns relation is the most straight-forward and most ubiquitous one. One object controls the lifecycle of a child object, and cleanup happens automatically via ownerReferences.

let cmgs = Api::<ConfigMapGenerator>::all(client.clone());
let cms = Api::<ConfigMap>::all(client.clone());

Controller::new(cmgs, watcher::Config::default())
    .owns(cms, watcher::Config::default())

This configmapgen example uses one custom resource ConfigMapGenerator whose controller is in charge of the lifecycle of the child ConfigMap.

  • What happens if we delete a ConfigMapGenerator instance here? Well, there will be a ConfigMap with ownerReferences matching the ConfigMapGenerator so Kubernetes will automatically cleanup the associated ConfigMap.
  • What happens if we modify the managed ConfigMap? The Controller sees a change and associates the change with the owning ConfigMapGenerator, ultimately triggering a reconciliation of the root ConfigMapGenerator.

This relation relies on ownerReferences being created on the managed/owned objects for Kubernetes automatic cleanup, and the Controller relies on it for association with its owner.

Watched Relations#

The Controller::watches relation is for related Kubernetes objects without ownerReferences, i.e. without a standard way for the controller to map the object to the root object. Thus, you need to define this mapper yourself:

let main = Api::<MainObj>::all(client);
let related = Api::<RelatedObject>::all(client);

let mapper = |obj: RelatedObject| {
    obj.spec.object_ref.map(|oref| {
        ReconcileRequest::from(oref)
    })
};

Controller::new(main, watcher::Config::default())
    .watches(related, watcher::Config::default(), mapper)

In this case we are extracing an object reference from the spec of our object. Regardless of how you get the information, your mapper must return an iterator of ObjectRef for the root object(s) that must be reconciled as a result of the change.

As a theoretical example; every HPA object bundles a scale ref to the workload, so you could use this to build a Controller for Deployment using HPA as a watched object.

External Relations#

It is possible to be dependent on some external api that you have semantically linked to your cluster, either as a managed resource or a source of information.

  • If you want to populate an external API from a custom resource, you will want to use finalizers to ensure the api gets cleaned up on CRD deletion.
  • If you want changes to the external API to trigger reconciliations, then you need to write some custom logic.

The current best way to do this is to inject reconciliation requests to the Controller using Controller::reconcile_all_on.

Subsets#

With owned and watched relations, it is not always necessary to watch the full space. Use watcher::Config to filter on the categories you want to reduce IO utilization:

let myobjects = Api::<MyObject>::all(client.clone());
let pods = Api::<Pod>::all(client.clone())

Controller::new(myobjects, watcher::Config::default())
    .owns(pods, watcher::Config::default().labels("managed-by=my-controller"))

Summary#

Depending on what type of child object and its relation with the main object, you will need the following setup and cleanup:

Child Controller relation Setup Cleanup
Kubernetes object Owned Controller::owns ownerReferences
Kubernetes object Related Controller::watches n/a
External API Managed custom finalizers
External API Related custom n/a