Ansible en OpenShift - een krachtige combinatie voor deployment

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

    1- name: Voorbeeld playbook
    2  hosts:
    3  - localhost
    4  become: false
    5  gather_facts: false
    6  tasks:
    7  - name: Zeg hallo
    8    debug:
    9      msg: hallo
    
  2. secret.yml

    1apiVersion: v1
    2kind: Secret
    3metadata:
    4  name: helloworld
    5  namespace: example
    6type: Opague
    7stringData:
    8  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):

 1Request Body: {
 2  "apiVersion": "v1",
 3  "kind":"Secret",
 4  "metadata": {
 5    "name": "helloworld",
 6    "namespace": "example"
 7  },
 8  "stringData": {
 9    "geheim": "Hello World"
10  },
11  "type":"Opague"}
12Request URI: https://api.crc.testing:6443/api/v1/namespaces/example/secrets
13Request 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: 

 1- name: Create voorbeeld namespace
 2  hosts:
 3  - localhost
 4  gather\_facts: false
 5  become: false
 6  tasks:
 7  - name: Create namespace
 8    k8s:
 9      definition:
10        apiVersion: v1
11        kind: Namespace
12        metadata:
13          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

    1- name: Create voorbeeld namespace
    2  hosts:
    3  - localhost
    4  gather_facts: false
    5  become: false
    6  roles:
    7  - k8s\_project
    
  • roles/k8s_project/templates/namespace.yml.j2

    1apiVersion: v1
    2kind: Namespace
    3metadata:
    4  name: {{ k8s_project_namespace }}
    
  • roles/k8s_project/templates/secret.yml.j2

    1apiVersion: v1
    2kind: Secret
    3metadata:
    4  name: {{ k8s_project_secret_name }}
    5  namespace: {{ k8s_project_namespace }}
    6type: Opague
    7data:
    8  geheim: {{ k8s_project_secret_value | b64encode }}
    
  • roles/k8s_project/tasks/main.yml

    1- name: Apply templates
    2  k8s:
    3    definition: "{{ lookup('template', item) | from\_yaml }}"
    4  loop:
    5  - namespace.yml.j2
    6  - secret.yml.j2
    
  • roles/k8s_project/defaults/main.yml

    1k8s_project_namespace: voorbeeld
    2k8s_project_secret_name: geheim
    3k8s_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.

Gerelateerde posts