Ansible Operator makes it easy for you to write Ansible to package operational knowledge into a Kubernetes application. Ansible Playbook Bundles (APBs) are lightweight application definitions designed to make cloud services available to the Kubernetes Service Catalog through the Automation Broker. This post will demonstrate a method for taking an existing investment in Ansible, in the form of an APB, and using it to develop a functional operator using the operator-sdk.
First, if you are reading this and are not aware of the k8s Ansible
Module you
should take a look. This module, introduced in Ansible 2.6, changes the game
with respect to managing Kubernetes objects in Ansible. This post on the
Ansible
blog
introduces the k8s module and the dynamic
client. Simply put,
in my opinion, if you are interacting with Kubernetes objects in Ansible and are
not using the k8s module, you are doing it wrong.
The Automation Broker, often referred to as the Ansible Service Broker, and the APBs it relies on came from a desire to make exposing an application to the Service Catalog easier for application developers. Instead of a developer having to make your own Open Service Broker API compatible broker, you wrote some Ansible, added some metadata to your container image and the Automation Broker took care of the hard parts. In many of the same ways the Ansible Operator allows you to write some Ansible, tell the Ansible Operator what Custom Resources to watch, and allow the Ansible Operator to worry about reconciliation and garbage collection.
Developing a functional operator from an existing APB project is straightforward and this post will show you how. While the method shown here will not work out of the box for every possible APB project layout, it will put on display what Ansible Operator requires to run properly in a way that could be adapted to any APB when making an operator. This post I will:
- Clone a simple APB project,
hello-world-apb
and use
operator-sdk new --type=ansibleto get the essential pieces for an Ansible Operator. - Take the essential generated Ansible Operator components and incorporate them into the hello-world-apb project.
Here, I will start with an APB project and use the operator-sdk to generate
the necessary parts for a functional Ansible Operator. If you haven't noticed
by now, this post is really just a clever attempt to get you to try
operator-sdk; maybe not all that clever.
Pre-Requisites
- Operator SDK
- You can grab the binary on the project's releases page or follow the quick start to install the latest from source.
- Ansible
- See their installation guide if you do not already have Ansible isntalled.
Clone the hello-world-apb:
$ git clone https://github.com/ansibleplaybookbundle/hello-world-apb.gitThen run operator-sdk new:
# From inside the hello-world-apb project is best
$ operator-sdk new --type=ansible hello-world-operator \
--api-version=examples.djzager.io/v1alpha1 \
--kind=HelloWorld \
--skip-git-init
INFO[0000] Creating new Ansible operator 'hello-world-operator'.
INFO[0000] Create build/Dockerfile
INFO[0000] Create watches.yaml
INFO[0000] Create /tmp/osdk583910079/galaxy_init.sh
INFO[0000] Create deploy/service_account.yaml
INFO[0000] Create deploy/role.yaml
INFO[0000] Create deploy/role_binding.yaml
INFO[0000] Create deploy/operator.yaml
INFO[0000] Create deploy/crds/examples_v1alpha1_helloworld_crd.yaml
INFO[0000] Create deploy/crds/examples_v1alpha1_helloworld_cr.yaml
INFO[0000] Running galaxy-init.
Initializing role skeleton...
- HelloWorld was created successfully
- INFO[0000] Project creation complete.Now that I have all the essential pieces for Ansible Operator, I will
incorporate them into my project. Before I jump in, I want to take a moment to
call out the important files generated by the operator-sdk new --type=ansible
command that I ran:
- build/Dockerfile - how to build the operator container image
- deploy/operator.yaml - operator deployment, the operator itself run as a deployment in Kubernetes
- deploy/(role|service_account|role_binding).yaml - RBAC for the operator deployment
- deploy/crds/. - the custom resource definition the operator will be watching and an example custom resource
- watches.yaml - tell the operator what to watch and what Ansible to run
This is my project after running operator-sdk new:
$ tree -L 2
.
βββ apb.yml
βββ defaults
βΒ Β βββ main.yml
βββ Dockerfile
βββ hello-world-operator
βΒ Β βββ build
βΒ Β βββ deploy
βΒ Β βββ roles
βΒ Β βββ watches.yaml
βββ Makefile
βββ meta
βΒ Β βββ main.yml
βββ playbooks
βΒ Β βββ deprovision.yml
βΒ Β βββ provision.yml
βΒ Β βββ test.yml
βββ README.md
βββ tasks
βΒ Β βββ main.yml
βββ templates
βΒ Β βββ deployment.yml.j2
βΒ Β βββ route.yml.j2
βΒ Β βββ service.yml.j2
βββ vars
βββ main.ymlMove the operator files into the project, throwing away the rest:
# First the Dockerfile
$ mv hello-world-operator/build .
# Then the deploy directory
$ mv hello-world-operator/deploy .
# Then the watches file
$ mv hello-world-operator/watches.yaml .
# Throw away the rest
$ rm -r hello-world-operatorThe project now looks like:
$ tree -L 2
.
βββ apb.yml
βββ build
βΒ Β βββ Dockerfile
βββ defaults
βΒ Β βββ main.yml
βββ deploy
βΒ Β βββ crds
βΒ Β βββ operator.yaml
βΒ Β βββ role_binding.yaml
βΒ Β βββ role.yaml
βΒ Β βββ service_account.yaml
βββ Dockerfile
βββ Makefile
βββ meta
βΒ Β βββ main.yml
βββ playbooks
βΒ Β βββ deprovision.yml
βΒ Β βββ provision.yml
βΒ Β βββ test.yml
βββ README.md
βββ tasks
βΒ Β βββ main.yml
βββ templates
βΒ Β βββ deployment.yml.j2
βΒ Β βββ route.yml.j2
βΒ Β βββ service.yml.j2
βββ vars
βΒ Β βββ main.yml
βββ watches.yamlWith the generated files from operator-sdk in place, it is time to make
a functional operator. This will have the ancillary benefit providing a deep
dive of sorts into the important pieces of Ansible Operator.
From the Ansible Operator User Guide:
The Watches file contains a list of mappings from custom resources, identified by it's Group, Version, and Kind, to an Ansible Role or Playbook.
When I initially ran operator-sdk new, I said nothing about the arguments
provided.
api-versionis a combination of the Group and Version (examples.djzager.io/v1alpha1)kindis just that,HelloWorldin my case
Since I am working with an Ansible Playbook Bundle, I will map the custom resource to an Ansible Playbook in my watches file:
---
- version: v1alpha1
group: examples.djzager.io
kind: HelloWorld
playbook: /opt/ansible/operator.yml
Here is my operator playbook:
---
- name: hello-world-apb operator
hosts: localhost
gather_facts: false
connection: local
vars:
apb_action: provision
app_name: "{{ meta.name }}"
namespace: "{{ meta.namespace }}"
roles:
- role: ansibleplaybookbundle.asb-modules
- role: hello-world-apbWhere do meta.name and meta.namespace come from? Extravars given to the
ansible-playbook during reconciliation come from the Custom Resource.
For exmaple a HelloWorld resource like:
apiVersion: examples.djzager.io/v1alpha1
kind: HelloWorld
metadata:
name: example-helloworld
namespace: default
spec:
size: 3The structure of extravars would be:
{
"meta": {
"name": "example-helloworld",
"namespace": "default"
},
"size": 3,
"_examples_djzager_io_helloworld": {
<Full CR>
}
}I need to update my Ansible to make use of size:
# Add a default size
$ echo 'size: 1' >> defaults/main.yml
# Update the deployment(config) replica count
$ sed 's|replicas: 1|replicas: "{{ size }}"|' templates/deployment.yml.j2Awesome work. Now all I need to do is configure my operator container image.
Here is my Dockerfile:
FROM quay.io/water-hole/ansible-operator
# This APB has a dependency
RUN ansible-galaxy install ansibleplaybookbundle.asb-modules
# The operator playbook is expecting a hello-world-apb role
COPY . ${HOME}/roles/hello-world-apb
# Put the operator playbook where it belongs in the container image
COPY playbooks ${HOME}/
# The watches file goes in the home directory
COPY watches.yaml ${HOME}/watches.yaml
That's it. I have made a functional Ansible Operator using the operator-sdk
with an existing APB project. To recap:
- With
operator-sdk new --type=ansibleI was able to generate the necessary pieces for an Ansible Operator. - I moved those pieces into my APB project in such a way that
operator-sdkcan be used when building the operator (more on this next) or adding more Custom Resources. - I configured
watches.yaml, added anoperator.ymlplaybook, and made the number of replicas configurable viasize. - I updated the operator Dockerfile based on the structure of my project.
- Use the
operator-sdk buildcommand to build an operator image. - Ansible Playbook Bundles have parameter validation via
apb.ymland the Service Catalog. There is nothing preventing a user from trying to create a HelloWorld resource withsize: lol. It would be a good idea to add validation in our Ansible. - In this incredibly simple example there was no need for multiple custom
resources. In your project, you may want to use
operator-sdk new crdand the watches file to allow your operator to manage more Custom Resources. - Check out the Operator Lifecycle Manager.
I hope that you will give operator-sdk and the Ansible Operator a try. To
learn more, head over to the Operator SDK documentation and work through the
Getting Started Guide
for Ansible-based Operators. Then join us on the Operator Framework mailing
list and let us
know what you think.
Thanks for this.
I'm still not clear on the difference between an
apband an ansible-operator.Also, why do you put the apb into the operator, and no the other way around, with the operator in the apb?