Ansible Roles and Playbooks manage Custom Resources

Why an Operator?

Operators manage complex stateful applications on Kubernetes. Writing an operator is often a full-fledged software project, using low level APIs and writing boilerplate code. The Operator SDK uses the Kubernetes controller-runtime library to make writing operators easier by providing:

  • High level APIs and abstractions to write the operational logic more intuitively
  • Tools for scaffolding and code generation to bootstrap a new project fast
  • Extensions to cover common operator use cases

What is an Ansible Operator?

A collection of building blocks from Operator SDK that enables Ansible to handle the reconciliation logic for an Operator.

Included in Operator Framework

Ansible Operator is one of the available types of Operators that Operator SDK is able to generate. Operator SDK can create an operator using Golang, Helm, or Ansible.

How do I use it?

Build your Ansible code on top of a provided base image along with some metadata to map Kubernetes events to Ansible Playbooks or Roles.

Image
Ansible Operator development workflow

The workflow for a new Ansible Operator looks like this:

  1. Create a new Operator project using the Operator SDK Command Line Interface (CLI)
  2. Write the reconciling logic for your object using Ansible Playbooks and Roles
  3. Use the SDK CLI to build and generate the operator deployment manifests
  4. Optionally add additional CRD's using the SDK CLI and repeat steps 2 and 3

Now that we have demonstrated the Ansible k8s modules, we want to trigger Ansible logic when a Custom Resource changes. The Ansible Operator uses a Watches file, written in YAML, that maps Custom Resources to Ansible Roles/Playbooks.

NOTE: It is very important that the Ansible Roles and Playbooks in an Ansible Operator are idempotent, as these tasks will be repeated frequently to ensure the application is in its proper state.

Structure of a Watches file

The Watches file maps Custom Resources identified by Group, Version, and Kind ("GVK") to Ansible Roles and Playbooks. The Operator expects to find the Watches file at the path /opt/ansible/watches.yaml. Each mapping in the Watches file must specify at least the mandatory fields:

  • group: Group of the Custom Resource that you will be watching.
  • version: Version of the Custom Resource that you will be watching.
  • kind: Kind of the Custom Resource that you will be watching.
  • role (_default_): Path to the Role that should be run by the Operator for a particular Group-Version-Kind (GVK). This field is mutually exclusive with the "playbook" field.
  • playbook (_optional_): Path to the Playbook that should be run by the Operator for a particular Group-Version-Kind (GVK). A Playbook can be used to invoke multiple Roles. This field is mutually exclusive with the "role" field.

Examine a sample watches.yaml:

---
- version: v1alpha1
  group: foo.example.com
  kind: Foo
  # associates GVK with Role
  role: /opt/ansible/roles/Foo

By default, operator-sdk new --type ansible creates watches.yaml configured to execute an Ansible role in response to a Custom Resource state change. This works well for smaller projects, but for more complex Ansible logic, we might not want it all in a single role.

Triggering a Playbook instead of a Role

Configuring the *Watches file* to run a Playbook allows the developer more flexibility in consuming other Ansible roles and enabling more customized application deployments.

To use a Playbook in your operator, you would modify `watches.yaml` as shown below:

---
- version: v1alpha1
  group: foo.example.com
  kind: Foo
  # associating a Custom Resource GVK with a Playbook
  playbook: /opt/ansible/playbook.yaml

For this to work, we would need to copy playbook.yaml into the container image.

You would accomplish this by modifying the Operator Dockerfile to COPY playbook.yaml into the container as shown below:

# Dockerfile at <project-name>/build/Dockerfile

FROM quay.io/operator-framework/ansible-operator

COPY roles/ ${HOME}/roles
COPY watches.yaml ${HOME}/watches.yaml

# New 'COPY' build step for playbook.yaml
COPY playbook.yaml ${HOME}/playbook.yaml

Preconfiguration for a Playbook

If you know from the start that you want your Operator to use a Playbook instead of a Role, you can generate your project scaffolding with the --generate-playbook option:

operator-sdk init --plugins=ansible --domain example.com
operator-sdk create api --group cache --version v1alpha1 --kind Memcached --generate-playbook