Ansible Operator Overview

Page content

Operators make it easy to manage complex stateful applications on top of Kubernetes. In this pos I will show you how to read an ansible based Openshift operator.

Parts of the Openshift series

Why an Operator?

Operators make it easy to manage complex stateful applications on top of Kubernetes. However writing an operator today can be difficult because of challenges such as using low level APIs, writing boilerplate, and a lack of modularity which leads to duplication.

What is an Ansible Operator?

Ansible Operator is one of the available types of Operators that Operator SDK is able to generate. Operator SDK can create an operator using with Golang, Helm, or Ansible. It is a collection of building blocks from Operator SDK that enables Ansible to handle the reconciliation logic for an Operator.

How Ansible Operator works?

We want to trigger this Ansible logic when a Custom Resource changes. The Ansible Operator uses a Watches file, written in YAML, which holds the mapping between Custom Resources and Ansible Roles/Playbooks. The Operator expects this mapping file in a predefined location: /opt/ansible/watches.yaml

Each mapping within the Watches file has 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.

Initialize new operator template

# istall sdk client
brew install operator-sdk

# generate base temlate
operator-sdk new memcached-operator --type=ansible --api-version=cache.example.com/v1alpha1 --kind=Memcached --skip-git-init
cd memcached-operator

The sdk cli generated the base structure for all the componets. The main parts are the watches.yaml the Dockerfile in build/Dockerfile and the ansible role in roles/ folder. Fore this tutorial I will use this role https://github.com/dymurray/memcached-operator-role. In the role we must use k8s ansible module to deploy kubernetes compnets to the cluster.

nano roles/memcached/tasks/main.yml
---
- name: start memcached
  k8s:
    definition:
      kind: Deployment
      apiVersion: apps/v1
      metadata:
        name: '{{ meta.name }}-memcached'
        namespace: '{{ meta.namespace }}'
      spec:
        replicas: "{{size}}"
        selector:
          matchLabels:
            app: memcached
        template:
          metadata:
            labels:
              app: memcached
          spec:
            containers:
            - name: memcached
              command:
              - memcached
              - -m=64
              - -o
              - modern
              - -v
              image: "memcached:1.4.36-alpine"
              ports:
                - containerPort: 11211

You can use variables in ansible Jinja temlate from the CR spec or from kubernetes enviroment like namespace: {{ meta.namespace }}. Add default value to the {{size}} variable:

nano roles/memcached/defaults/main.yml
---
size: 1

Variable sharing Example

apiVersion: "foo.example.com/v1alpha1"
kind: "Foo"
metadata:
  name: "example"
annotations:
  ansible.operator-sdk/reconcile-period: "30s"
  name: "example"
spec:
  message: "Hello world 2"
  newParameter: "newParam"
# associates GVK with Role
role: /opt/ansible/roles/Foo
- debug:
    msg: "message value from CR spec: {{ message }}"

- debug:
    msg: "newParameter value from CR spec: {{ new_parameter }}"

- debug:
    msg: "name: {{ meta.name }}, namespace: {{ meta.namespace }}"

The Openshidt SDK created a simple Dockerfile in build/Dockerfile to run the newly created ansible role. We nead to build a docker image from this Dockerfile and use this image in our memcached-operator deployment.

# buid image
operator-sdk build memcached-operator:v0.0.1

# Edit deploy/operator.yaml to use the newly created memcached-operator:v0.0.1 docker image
sed -i 's|{{ REPLACE_IMAGE }}|memcached-operator:v0.0.1|g' deploy/operator.yaml

# If we did not want to download the image (besause we build it on the worker or it is representid on all of my workes) we can disable image pulling.
sed -i "s|{{ pull_policy\|default('Always') }}|Never|g" deploy/operator.yaml

Creating the Operator from deploy manifests

oc create -f deploy/crds/cache_v1alpha1_memcached_crd.yaml

oc new-project tutorial
oc create -f deploy/service_account.yaml
oc create -f deploy/role.yaml
oc create -f deploy/role_binding.yaml
oc create -f deploy/operator.yaml

oc get deployment

Now that we have deployed our Operator, let’s create a CR and deploy an instance of memcached. There is a sample CR in the scaffolding created as part of the Operator SDK. Inspect deploy/crds/cache_v1alpha1_memcached_cr.yaml, and then use it to create a Memcached custom resource.

oc create -f deploy/crds/cache_v1alpha1_memcached_cr.yaml



$ oc get deployment
NAME                 DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
memcached-operator   1       1       1          1         2m
example-memcached    3       3       3          3         1m

#Check the pods to confirm 3 replicas were created:

$ oc get pods
NAME                                READY STATUS   RESTARTS AGE
example-memcached-6cc844747c-2hbln  1/1   Running  0        1m
example-memcached-6cc844747c-54q26  1/1   Running  0        1m
example-memcached-6cc844747c-7jfhc  1/1   Running  0        1m
memcached-operator-68b5b558c5-dxjwh 1/1   Running  0        2m

Removing Memcached from the cluster

# First, delete the 'memcached' CR, which will remove the 4 Memcached pods and the associated deployment.
oc delete -f deploy/crds/cache_v1alpha1_memcached_cr.yaml

# Then, delete the memcached-operator deployment.
oc delete -f deploy/operator.yaml