Developers guide

In this developers guide we will cover the basics you need to develop on the Yaook operators. We generally assume you already had a look at the concepts section above.

Operator State Graph

Operators are build based on a state graph. This graph is a directed-acyclic-graph (DAG) and describes all resources and their relations to fulfill the request to the operator.

Take a look at the follwing simplified graph for the galera custom resource. The goal of this operator is to create a galera statefulset with certificates and a specific configuration. In front of the statefulset there should be an HAProxy for loadbalancing.

../_images/galera_graph_simplified.drawio.svg

During each run the operator tries to traverse all nodes of the graph. Some nodes (haproxy, public service and database statefulset) have requirements to other nodes. These dependencies define the order in which the nodes are traversed.

This means that the database statefulset will only be created once the required service, the certificates and the configuration have been generated.

Resources

Now lets take a look at an individual resource such as the statefulset of the database. Each resource is represented as a subclass of yaook.statemachine.Resource.

Each resource exposes two methods to the operator:

yaook.statemachine.Resource.reconcile()

the goal of reconcile is to make reality match the intent of the resource. If you e.g. want to change the number of replicas in a statefulset, then this method is responsible to write the change to kubernetes. Note that applying the changes to kubernetes does not imply that the resource is immediately provisioned. For instance, a Pod often takes a while to start and become ready.

yaook.statemachine.Resource.is_ready()

the goal of is_ready is to determine if the operator can assume this resource has been fully reconciled. A resource that returns false here will block all of its dependents. This can for example be used to block until a certificate has been issued. Resources that might return false here MUST implement yaook.statemachine.Resource.get_listeners(). They need to install a listener for something which allows them to re-trigger the reconcile. Otherwise, the custom resource will never be touched again.

Each resource also has a component set. This is used to distinguish between different resources of the same kubernetes kind. The component is normally set automatically in the custom resource class (see below).

Kubernetes resources

For most plain kubernetes resources, classes to represent them have already been implemented. They are subclasses of yaook.statemachine.SingleObjectState which provides a few more convenience methods.

These resources generally need to implement the following methods:

yaook.statemachine.SingleObjectState._make_body()

this method is responsible to create the body of the kubernetes resource. it matches the json of the resource in kubernetes.

yaook.statemachine.SingleObjectState._needs_update()

this method checks if the given existing resource matches the return value of _make_body. While this might seem trivial it is not always easy to differenciate between fields managed by the operator and ones generate by k8s.

Check yaook.statemachine for a list of all generally supported resources.

The kubernetes resource stores labels to identify the matching resource in the operator and its parent custom resource. The following labels are used for that:

state.yaook.cloud/component

the component of the resource.

state.yaook.cloud/parent-group

the kubernetes group (first part of apiVersion) of the custom resource that created this resource.

state.yaook.cloud/parent-name

the kubernetes name of the custom resource that created this resource.

state.yaook.cloud/parent-plural

the kubernetes plural of the custom resource that created this resource.

state.yaook.cloud/parent-version

the kubernetes version (second part of apiVersion) of the custom resource that created this resource.

Using templates

Many resources are featured in the plain version and in a “Templated…” version. The plain version requires of you to overwrite yaook.statemachine.SingleObjectState._make_body() (see above).

When using e.g. a TemplatedDeploymentState point the resources to the filename of the jinja template that should be used as a body. The templates are searched for in a templates subdirectory next to the python file in which the CustomResource subclass is defined. Generally, this will be yaook/op/$youroperatorname/templates.

Combining resources to fulfill a CustomResource request

Each custom resource definition in kubernetes is represented by a subclass of yaook.statemachine.CustomResource. The subclass defines the the reference to the custom resource definition. It also contains all resources required to fulfill the request of the custom resource.

The resources are represented as attributes on the class. The name of the attribute for each resource will be used as the component of it as described above.

The context

The classes of all operators and their resources MUST NOT hold any information about a specific resource. This allows us to reuse the same operator for multiple resources and prevents potential leaking of data between resources.

To know which resource is currently being processed a context object yaook.context.Context is passed around. It contains all data about the current resource as well as possibilities to interact with the kubernetes api.

Triggering the operator

The operator relies on external triggers to know when to take action. There are the following kind of triggers:

Operator Startup

When the operator starts for the first time. It will reconcile all resources learns about from a listing via the Kubernetes API.

Changes to a resource

Whenever a resource spec changes it will trigger a reconcile.

Notifications to listeners

As described above Resources can implement yaook.statemachine.Resource.get_listeners() to define more reasons for a reconcile run.

The operator generally deduplicates reconcile requests. So if multiple sources request a reconcile of a resource the process will still only run once. If currently already a reconcile is running then the reconcile is started again after the current one has finished.

The reason of the reconcile is never passed to the actual operator or the individual states.

If you changed something in the operator (be it a piece of code or a jinja template) just restart the operator for the changes to take effect.

Further reading

The devlopers guide is continued in the following subpages: