Ansible Operator Overview
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
- Part1: Install Opeshift
- Part2: How to Enable Auto Approval of CSR in Openshift v3.11
- Part3: Add new workers to Openshift cluster
- Part4: Chane the certificates of the Openshift cluster
- Part5: LDAP authentication for Openshift
- Part6: Keycloak SSO authentication for Openshift
- Part7: Gitlab SSO authentication for Openshift
- Part8a: Ceph persistent storage for Openshift
- Part8b: vSphere persistent storage for Openshift
- Part9: Helm on Openshift
- Part10: Tillerless Helm on Openshift
- Part11: Use external docker registry on Openshift
- Part12: Secondary router on Openshift
- Part13a: Use Letsencrypt on Openshift
- Part13b: Install cert-managger on Openshift
- Part14: Create Openshift operators
- Part15: Convert docker-compose file to Opeshift
- Part16a: Opeshift elasticsearch search-guard error
- Part16b: Openshift: Log4Shell - Remote Code Execution (CVE-2021-44228) (CVE-2021-4104)
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