Ansible Operator Tutorial

    NOTE: If your project was created with an version prior to v1.0.0 please migrate, or consult the .

    • Go through the installation guide.
    • User authorized with cluster-admin permissions.
    • An accessible image registry for various operator images (ex. , quay.io) and be logged in in your command line environment.
      • example.com is used as the registry Docker Hub namespace in these examples. Replace it with another value if using a different registry or namespace.
      • if the registry is private or uses a custom CA.

    Overview

    We will create a sample project to let you know how it works and this sample will:

    • Create a Memcached Deployment if it doesn’t exist
    • Ensure that the Deployment size is the same as specified by the Memcached CR spec
    • Update the Memcached CR status using the status writer with the names of the CR’s pods

    Use the CLI to create a new memcached-operator project:

    Among the files generated by this command is a Kubebuilder PROJECT file. Subsequent operator-sdk commands (and help text) run from the project root read this file and are aware that the project type is Ansible.

    Next, we will create a Memcached API.

    1. operator-sdk create api --group cache --version v1alpha1 --kind Memcached --generate-role

    The scaffolded operator has the following structure:

    • Memcached Custom Resource Definition, and a sample Memcached resource.
    • A “Manager” that reconciles the state of the cluster to the desired state
      • A reconciler, which is an Ansible Role or Playbook.
      • A watches.yaml file, which connects the Memcached resource to the memcached Ansible Role.

    See and watches reference for more detailed information

    Now we need to provide the reconcile logic, in the form of an Ansible Role, which will run every time a Memcached resource is created, updated, or deleted.

    Update roles/memcached/tasks/main.yml:

    1. ---
    2. - name: start memcached
    3. kubernetes.core.k8s:
    4. definition:
    5. kind: Deployment
    6. apiVersion: apps/v1
    7. metadata:
    8. name: '{{ ansible_operator_meta.name }}-memcached'
    9. namespace: '{{ ansible_operator_meta.namespace }}'
    10. spec:
    11. replicas: "{{size}}"
    12. selector:
    13. matchLabels:
    14. app: memcached
    15. template:
    16. metadata:
    17. labels:
    18. app: memcached
    19. spec:
    20. - name: memcached
    21. command:
    22. - memcached
    23. - -m=64
    24. - -o
    25. - modern
    26. - -v
    27. image: "docker.io/memcached:1.4.36-alpine"
    28. ports:
    29. - containerPort: 11211

    This memcached role will:

    • Ensure a memcached Deployment exists
    • Set the Deployment size

    Note that the tasks in this Ansible role file are what actually defines the behavior of the spec and status of the memcached custom resource. As Kubernetes allows entry of arbitrary fields when creating resources, we don’t need to actually create specific fields in the CRD. While we won’t be doing this in this tutorial, it is recommended to also define these fields in the CRD, so that Kubernetes users can see the fields that will be used when using the custom resource. It is also good practice to set default values for variables used in Ansible Roles, so edit roles/memcached/defaults/main.yml:

    1. ---
    2. # defaults file for Memcached
    3. size: 1

    Finally, update the sample, config/samples/cache_v1alpha1_memcached.yaml:

    1. apiVersion: cache.example.com/v1alpha1
    2. kind: Memcached
    3. metadata:
    4. name: memcached-sample
    5. spec:
    6. size: 3

    Note: The names of all variables in the spec field are converted to snake_case by the operator before running ansible. For example, serviceAccount in the spec becomes service_account in ansible. You can disable this case conversion by setting the snakeCaseParameters option to false in your watches.yaml. It is recommended that you perform some type validation in Ansible on the variables to ensure that your application is receiving expected input.

    Configure the operator’s image registry

    All that remains is to build and push the operator image to the desired image registry. Your Makefile composes image tags either from values written at project initialization or from the CLI. In particular, IMAGE_TAG_BASE lets you define a common image registry, namespace, and partial name for all your image tags. Update this to another registry and/or namespace if the current value is incorrect. Afterwards you can update the IMG variable definition like so:

    1. -IMG ?= controller:latest
    2. +IMG ?= $(IMAGE_TAG_BASE):$(VERSION)

    Once done, you do not have to set IMG or any other image variable in the CLI. The following command will build and push an operator image tagged as example.com/memcached-operator:v0.0.1 to Docker Hub:

    1. make docker-build docker-push

    There are three ways to run the operator:

    1. Run locally outside the cluster

    Execute the following command, which install your CRDs and run the manager locally:

    By default, a new namespace is created with name <project-name>-system, ex. memcached-operator-system, and will be used for the deployment.

    The scaffolded Makefile uses to apply custom configurations and generate manifests from the config/ directory, which are piped to kubectl.

    1. kustomize build config/default | kubectl apply -f -

    Commonly, Operator authors may need to modify config/rbac in order to give their Operator the necessary permissions to reconcile.

    Run the following to customize the manifests and deploy the operator.

    1. make deploy

    Verify that the memcached-operator is up and running:

    1. $ kubectl get deployment -n memcached-operator-system
    2. NAME READY UP-TO-DATE AVAILABLE AGE
    3. memcached-operator-controller-manager 1/1 1 1 8m

    3. Deploy your Operator with OLM

    First, install :

    1. operator-sdk olm install

    Bundle your operator, then build and push the bundle image. The bundle target generates a bundle in the bundle directory containing manifests and metadata defining your operator. bundle-build and bundle-push build and push a bundle image defined by bundle.Dockerfile.

    1. make bundle bundle-build bundle-push
    1. operator-sdk run bundle example.com/memcached-operator-bundle:v0.0.1

    Check out the for a deep dive into operator-sdk‘s OLM integration.

    Create a Memcached CR

    Update the sample Memcached CR manifest at config/samples/cache_v1alpha1_memcached.yaml and define the spec as the following:

    Create the CR:

    1. kubectl apply -f config/samples/cache_v1alpha1_memcached.yaml

    Ensure that the memcached operator creates the deployment for the sample CR with the correct size:

    1. $ kubectl get deployment
    2. NAME READY UP-TO-DATE AVAILABLE AGE
    3. memcached-sample 3/3 3 3 1m

    Check the pods and CR status to confirm the status is updated with the memcached pod names:

    1. $ kubectl get pods
    2. NAME READY STATUS RESTARTS AGE
    3. memcached-sample-6fd7c98d8-7dqdr 1/1 Running 0 1m
    4. memcached-sample-6fd7c98d8-g5k7v 1/1 Running 0 1m
    5. memcached-sample-6fd7c98d8-m7vn7 1/1 Running 0 1m
    1. kind: Memcached
    2. metadata:
    3. creationTimestamp: "2021-03-17T19:54:42Z"
    4. generation: 1
    5. managedFields:
    6. - apiVersion: cache.example.com/v1alpha1
    7. fieldsType: FieldsV1
    8. fieldsV1:
    9. f:status:
    10. .: {}
    11. f:conditions: {}
    12. manager: ansible-operator
    13. operation: Update
    14. time: "2021-03-17T19:54:42Z"
    15. - apiVersion: cache.example.com/v1alpha1
    16. fieldsType: FieldsV1
    17. fieldsV1:
    18. f:spec:
    19. .: {}
    20. f:size: {}
    21. manager: kubectl
    22. operation: Update
    23. time: "2021-03-17T19:54:42Z"
    24. name: memcached-sample
    25. namespace: default
    26. resourceVersion: "1008"
    27. uid: 4b023125-132a-44e3-80de-20801c7a9268
    28. spec:
    29. size: 3
    30. status:
    31. conditions:
    32. - ansibleResult:
    33. changed: 0
    34. completion: 2021-03-17T19:54:54.890394
    35. failures: 0
    36. ok: 1
    37. skipped: 0
    38. lastTransitionTime: "2021-03-17T19:54:42Z"
    39. message: Awaiting next reconciliation
    40. reason: Successful
    41. status: "True"
    42. type: Running

    Update config/samples/cache_v1alpha1_memcached.yaml to change the spec.size field in the Memcached CR from 3 to 5:

    1. kubectl patch memcached memcached-sample -p '{"spec":{"size": 5}}' --type=merge

    Confirm that the operator changes the deployment size:

    1. $ kubectl get deployment
    2. NAME READY UP-TO-DATE AVAILABLE AGE

    Cleanup

    Run the following to delete all deployed resources:

    We recommend reading through the our Ansible development section for tips and tricks, including how to run the operator locally.

    In this tutorial, the scaffolded watches.yaml could be used as-is, but has additional optional features. See .

    For brevity, some of the scaffolded files were left out of this guide. See Scaffolding Reference

    This example built a namespaced scope operator, but Ansible operators can also be used with cluster-wide scope.