Controller Example

    This example is for the Controller for the ContainerSet API shown in the .It uses the controller-runtime librariesto implement the Controller and Manager.

    Unlike the Hello World example, here we use the underlying Controller libraries directly insteadof the higher-level pattern libraries. This gives greater control overthe Controller is configured.

    ContainerSetController

    ContainerSetController has a single annotation:

    • // +kubebuilder:rbac creates RBAC rules in the config/rbac/rbac_role.yaml file when make is run.This will ensure the Kubernetes ServiceAccount running the controller can read / write to the Deployment API.ContainerSetController has 2 variables:

    • scheme *runtime.Scheme is a runtime.Scheme used by the library to set OwnerReferences.

    Adding a Controller to the Manager

    Add creates a new Controller that will be started by the Manager. When adding a Controller it is important to setupWatch functions to trigger Reconciles.

    Watch is a function that takes an event source.Source and a handler.EventHandler. The Source provides eventsfor some type, and the EventHandler responds to events by enqueuing reconcile.Requests for objects.Watch optionally takes a list of Predicates that may be used to filter events.

    Sources

    • To watch for create / update / delete events for an object use a source.KindSource e.g.source.KindSource{Type: &v1.Pod}Handlers

    • To enqueue a Reconcile for the object in the event use a handler.EnqueueRequestForObject

    • To enqueue a Reconcile for the owner object that created the object in the event use a handler.EnqueueRequestForOwnerwith the type of the owner e.g. &handler.EnqueueRequestForOwner{OwnerType: &appsv1.Deployment{}, IsController: true}
    • To enqueue Reconcile requests for an arbitrary collection of objects in response to the event, use ahandler.EnqueueRequestsFromMapFunc.Example:

      • Invoke Reconcile with the Name and Namespace of a ContainerSet for ContainerSet create / update / delete events
      • Invoke Reconcile with the Name and Namespace of a ContainerSet for Deployment create / update / delete events

    Reference

    • See the controller librariesgodocs for reference documentation on the controller libraries.
    • See the to learn moreabout how use annotations in kubebuilder.

    Adding Annotations For Watches And CRUD Operations

    It is important// +kubebuilder:rbac annotations when adding Watches or CRUD operationsso that when the Controller is deployed it will have the correct permissions.

    make must be run anytime annotations are changed to regenerated code and configs.

    Implementing Controller Reconcile

    Level vs Edge

    The Reconcile function does not differentiate between create, update or deletion events.Instead it simply reads the state of the cluster at the time it is called.

    Reconcile uses a client.Client to read and write objects. The Client is able toread or write any type of runtime.Object (e.g. Kubernetes object), so users don't needto generate separate clients for each collection of APIs.

    The function shown here creates or updates a Deployment using the replicas and image specified inContainerSet.Spec. Note that it sets an OwnerReference for the Deployment to enable garbage collectionon the Deployment once the ContainerSet is deleted.

    • Read the ContainerSet using the NamespacedName
    • If there is an error or it has been deleted, return
    • Create the new desired DeploymentSpec from the ContainerSetSpec
    • Read the Deployment and compare the Deployment.Spec to the ContainerSet.Spec
    • If the observed Deployment.Spec does not match the desired spec
      • Deployment was not found: create a new Deployment
      • Deployment was found and changes are needed: update the Deployment
    1. var _ reconcile.Reconciler = &ContainerSetController{}
    2. func (r *ReconcileContainerSet) Reconcile(request reconcile.Request) (reconcile.Result, error) {
    3. instance := &workloadsv1beta1.ContainerSet{}
    4. err := r.Get(context.TODO(), request.NamespacedName, instance)
    5. if err != nil {
    6. // Object not found, return. Created objects are automatically garbage collected.
    7. // For additional cleanup logic use finalizers.
    8. return reconcile.Result{}, nil
    9. // Error reading the object - requeue the request.
    10. return reconcile.Result{}, err
    11. }
    12. // TODO(user): Change this to be the object type created by your controller
    13. // Define the desired Deployment object
    14. deploy := &appsv1.Deployment{
    15. ObjectMeta: metav1.ObjectMeta{
    16. Name: instance.Name + "-deployment",
    17. Namespace: instance.Namespace,
    18. },
    19. Spec: appsv1.DeploymentSpec{
    20. Selector: &metav1.LabelSelector{
    21. MatchLabels: map[string]string{"deployment": instance.Name + "-deployment"},
    22. },
    23. Replicas: &instance.Spec.Replicas,
    24. Template: corev1.PodTemplateSpec{
    25. ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"deployment": instance.Name + "-deployment"}},
    26. Spec: corev1.PodSpec{
    27. Containers: []corev1.Container{
    28. {
    29. Name: instance.Name,
    30. Image: instance.Spec.Image,
    31. },
    32. },
    33. },
    34. },
    35. }
    36. return reconcile.Result{}, err
    37. }
    38. // TODO(user): Change this for the object type created by your controller
    39. // Check if the Deployment already exists
    40. found := &appsv1.Deployment{}
    41. err = r.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, found)
    42. if err != nil && errors.IsNotFound(err) {
    43. log.Printf("Creating Deployment %s/%s\n", deploy.Namespace, deploy.Name)
    44. err = r.Create(context.TODO(), deploy)
    45. if err != nil {
    46. return reconcile.Result{}, err
    47. }
    48. } else if err != nil {
    49. return reconcile.Result{}, err
    50. }
    51. // TODO(user): Change this for the object type created by your controller
    52. // Update the found object and write the result back if there are any changes
    53. if !reflect.DeepEqual(deploy.Spec, found.Spec) {
    54. found.Spec = deploy.Spec
    55. log.Printf("Updating Deployment %s/%s\n", deploy.Namespace, deploy.Name)
    56. err = r.Update(context.TODO(), found)
    57. if err != nil {
    58. return reconcile.Result{}, err
    59. }
    60. }
    61. }