12 March 2020

Ansible en OpenShift; een krachtige combinatie voor deployment

Wander Boessenkool
Ansible Automation Containerization Openshift

Ansible en OpenShift zijn uitermate goed samen te gebruiken. In dit artikel zullen we kijken hoe we Ansible kunnen inzetten om een OpenShift cluster te beheren en applicaties uit te rollen op een OpenShift cluster. Traditioneel worden applicaties uitgerold vanuit OpenShift templates, Helm charts, pipelines en soortgelijke tools. In dit artikel zullen we de basis leggen voor het uitbreiden of vervangen van deze deployment methodes met Ansible.

Dit artikel gaat uit van basiskennis van zowel OpenShift / Kubernetes als Ansible.

Alles is YAML, YAML is alles

Alles in Ansible is YAML, alles in OpenShift (Kubernetes) is ook YAML. Twee voorbeelden:


1) Play.yml

– name: Voorbeeld playbook
hosts:
– localhost
become: false
gather_facts: false
tasks:
– name: Zeg hallo
debug:
msg: hallo


2) Secret.yml

apiVersion: v1
kind: Secret
metadata:
name: helloworld
namespace: example
type: Opague
stringData:
geheim: Hallo wereld


Het eerste voorbeeld is een Ansible playbook, het tweede voorbeeld is een Kubernetes (k8s) resource definitie voor een Secret. Het feit dat allebei de technologieën gebaseerd zijn op de dezelfde markup taal maakt het makkelijk om de twee te integreren.

Kubernetes API

K8S wordt aangestuurd met een REST API. Wanneer we het netwerkverkeer zouden bekijken van een oc apply -f secret.yml voor het tweede voorbeeld, of wanneer we -v=8 of hoger meegeven aan het oc commando, dan zien we de volgende informatie (geformatteerd voor leesbaarheid):


Request Body: {
“apiVersion”: “v1”,
“kind”:”Secret”,
“metadata”: {
“name”: “helloworld”,
“namespace”: “example”
},
“stringData”: {
“geheim”: “Hello World”
},
“type”:”Opague”}
Request URI: https://api.crc.testing:6443/api/v1/namespaces/example/secrets
Request Type: POST


Dit is een request voor het aanmaken van een nieuwe resource. Het aanpassen of verwijderen van bestaande resources gaat net iets anders (andere HTTP methodes, ander body formaat, uri voor specifieke resource), maar de theorie blijft hetzelfde. Een k8s resource wordt via een CRUD API aangemaakt, opgevraagd, aangepast of verwijderd.

Dit betekent dat we in theorie via de Ansible uri module alles zouden moeten kunnen doen, maar er zijn betere opties…

Ansible k8s module

In een basisinstallatie van Ansible wordt de k8s module beschikbaar gemaakt. Deze module kan gebruikt worden om Kubernetes resources aan te maken, aan te passen en te verwijderen. Zoals het een goede Ansible module betaamt wordt dit alles idem-potent gedaan. Dat wil zeggen, of een play(book) nou één of meerdere malen wordt uitgevoerd, het resultaat is hetzelfde.

Het onderstaande playbook maakt een nieuwe namespace aan, genaamd ‘voorbeeld’. Vanwege de idem-potentie kunnen we dit playbook ook draaien als de namespace al bestaat en zal er niks aan de namespace veranderen:


– name: Create voorbeeld namespace
hosts:
– localhost
gather_facts: false
become: false
tasks:
– name: Create namespace
k8s:
definition:
apiVersion: v1
kind: Namespace
metadata:
name: voorbeeld


Als we dit playbook analyseren dan zien we de volgende dingen:

  1. We voeren het playbook uit tegen localhost. Dit kan ook een ander systeem zijn, maar het is (meestal) niet het OpenShift cluster, maar de machine vanaf waar de Kubernetes API calls uitgevoerd worden. Als er geen expliciete Kubernetes authenticate opties aan de k8s module worden meegegeven wordt de `kubeconfig` van de `ansible_user` op de doelmachine gebruikt.
  2. Omwille van de snelheid verzamelen we geen Ansible facts.
  3. Er zijn geen root privileges nodig, dus become staat op false.
  4. Van alle opties die met k8s module kunnen worden gebruikt gebruiken we er nu slechts één: definition. Voor andere bewerkingen zullen we meer opties gebruiken.

Ansible rollen gebruiken

In het bovenstaande voorbeeld hebben we één object aangemaakt, en nog met een vaste naam ook. Om schaalbaar te zijn willen we eigenlijk gebruik maken van Ansible variabelen, templates en rollen.

Het onderstaande voorbeeld transformeert het vorige voorbeeld naar een rol en voegt ook een secret toe aan de namespace:


create-ns-from-role.yml
– name: Create voorbeeld namespace

hosts:

– localhost

gather_facts: false

become: false

roles:

– k8s_project


roles/k8s_project/templates/namespace.yml.j2

apiVersion: v1

kind: Namespace

metadata:

name:


 roles/k8s_project/templates/secret.yml.j2

apiVersion: v1

kind: Secret

metadata:

name:

namespace:

type: Opague

data:

geheim:


roles/k8s_project/tasks/main.yml

– name: Apply templates

k8s:

definition: “”

loop:

– namespace.yml.j2

– secret.yml.j2


roles/k8s_project/defaults/main.yml

k8s_project_namespace: voorbeeld

k8s_project_secret_name: geheim

k8s_project_secret_value: Hallo wereld


Het playbook (create-ns-from-role.yml) heeft nu geen directe taken meer, maar roept wel een rol op: k8s_project.

In de rol k8s_project hebben we drie hoofdelementen: tasks, templates en defaults.

Defaults

In roles/k8s_project/defaults/main.yml definiëren we wat variabelen. Omdat we ervan uitgaan dat een gebruiker van onze rol andere waarden zal willen invullen, hebben we deze op het niveau van defaults gezet. Alle variabelen zijn geprefixed met onze rol-naam zodat de rol netjes naast andere rollen kan leven, zonder conflicten in variabele namen.

Templates

In de templates directory van de rol hebben we twee Jinja2 templates gemaakt. In het template voor het secret gebruiken we nu in plaats van “stringData” “data”. Een “data” veld bevat dezelfde inhoud als “stringData”, maar dan in base64 encoding. In dit geval doen we de base64 encoding zelf met Ansible. Beide oplossingen zijn functioneel gelijk, de Kubernetes API transformeert stringData waardes zelf automatisch naar base64 encoded waardes onder “data”.

Tasks

In de taken van onze rol loopen we over beide templates, om ze aan de k8s module te geven. De constructie hier met een template lookup, gefilterd door het from_yaml filter, is er eentje die we vaker tegen zullen gaan komen. De template lookup zoekt, net zoals de module met dezelfde naam, altijd eerst in de templates directory van de rol van waaruit hij wordt opgeroepen.

Het from_yaml filter zorgt ervoor dat de YAML formattering van de templates goed bewaard blijft.

Conclusie

Met weinig werk kan Ansible gebruikt worden om bestaande deployment oplossingen zoals OpenShift templates of Helm charts te vervangen, met de mogelijkheid om méér logica en flexibiliteit in te bouwen dan andere oplossingen bieden, zoals het automatisch aanpassen van deployment variabelen, het updaten van database schema’s of zelfs integreren met externe systemen zoals monitoring oplossingen. In het volgende deel van deze serie zullen we ons playbook gaan uitbreiden om een traditionele build-test-deploy pipeline te vervangen.