Skip to main content

WORKSHOP 5: Conditionals, Handlers and Loops

Nu we de basis wat vast hebben kunnen we onze Ansible Playbooks wat intelligenter en flexibelder maken.

Doelstelling

In deze workshop leer je:

  • Conditionals gebruiken ⇒ voorwaarden inbouwen in je playbook
  • Handlers toepasssen ⇒ Taken uitvoeren, enkel indien nodig.
  • Loops gebruiken ⇒ iteratieve opdrachten uitvoeren.

Workshop

Stap 1 - Conditionals

Ansible kan gebruik maken van conditionals om taken uit te voeren enkel wanneer aan bepaalde voorwaarden voldaan werd.

Om een conditional te implementeren gebruiken we het keyword when bij een task in de playbook, gevolgd door een voorwaarde. De conditie maakt steeds gebruik van de gekende operators: ==, !=, >, >=, < en <=

Meer informatie kan je hierover vinden in de documentatie.

Als voorbeeld om dit duidelijk te maken zullen we via Ansible een FTP-server installeren enkel als de host in de inventory groep ftpserver zit.

Om dit te doen passen we eerst onze inventory wat aan. We voegen er een nieuwe groep ftpserver aan toe en plaatsen node2 daarbij.

  • Pas je hosts-file aan met de nieuwe groep. Zorg er ook voor dat de variabelen onder web op het hoogste niveau komen. De specifieke variabelen voor de groep control nemen toch de overhand voor de hosts in die groep.

    hosts.yml
    ---
    all:
    hosts:
    children:
    web:
    hosts:
    node1:
    ansible_host: X.X.X.X
    node2:
    ansible_host: Y.Y.Y.Y
    node3:
    ansible_host: Z.Z.Z.Z
    ftpserver:
    hosts:
    node2:
    ansible_host: A.A.A.A
    control:
    hosts:
    ansible-1:
    ansible_host: B.B.B.B
    vars:
    ansible_user: student
    ansible_password: student
    ansible_connection: local
    ansible_become_pass: student
    vars:
    ansible_port: 22
    ansible_user: ubuntu
    ansible_password: Azerty123
    ansible_connection: ssh
    ansible_become_pass: Azerty123
  • Maak nu ook een nieuwe playbook aan met de naam ftpserver.yml in de basismap ./ansible-files/ van je project:

    ftpserver.yml
    ---
    - name: Install vsftpd on ftpservers
    hosts: web, ftpserver
    become: true
    tasks:
    - name: Install FTP server when host in ftpserver group
    ansible.builtin.apt:
    name: vsftpd
    state: latest
    when: inventory_hostname in groups["ftpserver"]

note

inventory_hostname is de naam van de host in de inventory (in ons geval hosts.yml). Dit is één van de speciale variabelen in Ansible (zie documentatie)

  • Voer deze playbook uit en bekijk de output:

    TASK [Install FTP server when host in ftpserver group] *************************************************************
    skipping: [node1]
    skipping: [node3]
    // highlight-next-line
    [ok]: [node2]

    Als het goed is zou de ftp-service enkel op node2 mogen geïnstalleerd zijn, desondanks alle nodes uit zowel web als ftpserver opgegeven werden.

Stap 2 - Handlers

Soms gebeurt het dat, wanneer een taak een aanpassing doorvoert, er een extra taak moet uitgevoerd worden. Een typisch voorbeeld is een wijziging aan een bepaalde service (vb. web- of mysql server) en na die wijziging moet de service herstart worden zodat de wijziging actief wordt.

In bovenstaande geval kunnen we handlers gebruiken. Handlers kan je bekijken als inactieve taken die enkel getriggerd worden indien ze expliciet door een notify worden aangeroepen. Meer info daarover in de documentatie.

Om dit duidelijk te maken creëren we een nieuwe playbook die:

  • de configuratie van Apache aanpast in de het bestand /etc/apache2/ports.conf

  • de Apache-service herstart, enkel als er een "wijziging" was in het configuratiebestand ports.conf

  • Haal eerst een Apache configuratiebestand voor de tcp port-configuratie binnen op je Control Host zodat we deze later kunnen aanpassen en via Ansible naar de host kopiëren. We gebruiken hier de Ansible module fetch om zo'n bestand (ports.conf) van een reeds bestaande webserver binnen te halen naar onze Ansible Control host:

    [student@ControlHost ansible-files]$ ansible node1 -m fetch -a "src=/etc/apache2/ports.conf dest=./files/ flat=yes"

    Controleer of het ports.conf bestand zich nu ook effectief bevindt in de map ./ansible-files/files/.

  • Maak een nieuwe playbook aan met de naam apache2_port_conf.yml in je projectfolder ./ansible-files/:

    apache2_port_conf.yml
    ---
    - name: Manage port.conf
    hosts: web
    become: true
    tasks:
    - name: Copy Apache port configuration file
    ansible.builtin.copy:
    src: ports.conf
    dest: /etc/apache2
    notify:
    - Restart_apache
    handlers:
    - name: Restart_apache
    ansible.builtin.service:
    name: apache2
    state: restarted

  • Wat is er nieuw in bovenstaande playbook:

    • De notify-sectie roept de handler aan wanneer de taak effectief een aangepast bestand kopieert. Op deze manier zal de service apache2 enkel herstart worden indien nodig en niet elke keer dat de playbook uitgevoerd zal worden.
    • Een handlers-sectie die aangeeft wat er moet gebeuren indien deze aangeroepen wordt door de notify.
  • Voer de playbook uit. We hebben op dit moment eigenlijk nog geen veranderingen aangebracht. Dus in het resultaat zouden enkel ok lijnen mogen voorkomen en geen changed. De handler zal dus ook nog niet uitgevoerd worden.

    TASK [Copy Apache port configuration file] *******************************************
    ok: [node2]
    ok: [node3]
    ok: [node1]

    PLAY RECAP **********************************************************************
    node1 : ok=2 changed=0 unreachable=0
    node2 : ok=2 changed=0 unreachable=0
    node3 : ok=2 changed=0 unreachable=0
  • Pas nu in de ports.conf de webserver-poort aan:

    Listen 8080
  • Laat je playbook nog eens lopen. De output van je playbook zou nu wat interessanter moeten zijn:

    • ports.conf zou moeten gekopieerd zijn

    • de handler zou Apache moeten herstart hebben.

Apache zou nu moeten "luisteren" op poort 8080

  • controleer even via het eerder gebruikte ad-hoc command:

    [student@ControlHost ansible-files]$ ansible web -m command -a "curl http://localhost"
    ...
    [student@ControlHost ansible-files]$ ansible web -m command -a "curl http://localhost:8080"
  • Pas gerust het bestand ports.conf nog eens aan, of zet het terug naar de default-port 80

Stap 3 - Eenvoudige Loops

Loops worden gebruikt om dezelfde taak meerdere keren te herhalen. Een typisch voorbeeld is het aanmaken van gebruikers (met hun wachtwoord, mailadres, rechten,etc...). Dit kan nu makkelijk uitgevoerd worden in een Ansible playbook met 1 tasks. Meer info over loops kan je vinden in de documentatie.

Om het principe van loops aan te tonen willen we drie nieuwe users aanmaken op node1.

  • Maak opnieuw een playbook aan met de naam loop_users.yml in de projectmap ./ansible-files. Je gebruikt hiervoor de module user (zie documentatie).

    loop_users.yml
    ---
    - name: Ensure users are present
    hosts: node1
    become: true
    tasks:
    - name: Ensure three users are present
    ansible.builtin.user:
    name: "{{ item }}"
    state: present
    loop:
    - dev_user
    - qa_user
    - prod_user

  • Probeer bovenstaande playbook te begrijpen:

    • de namen van de users werden niet direct aan de module gegeven. Er werd daarvoor een variabele {{ item }} gebruikt als parameter.

    • Het keyword loop bevat de eigenlijke gebruikersnamen.

    • Tijdens de uitvoering van de playbook wordt deze taak maar één keer uitgevoerd maar er worden drie changes doorgevoerd.

      TASK [Ensure three users are present] ****************************************************************
      changed: [node1] => (item=dev_user)
      changed: [node1] => (item=qa_user)
      changed: [node1] => (item=prod_user)

Stap 4 - Loops over hashes

Stel dat de gebruikers die je wil toevoegen ook aan een bepaalde groep moeten toegevoegd worden. Met de module user kan je ook onmiddellijk de gebruiker in de juiste groep steken.

GebruikerGroep
dev_userftp
qa_userftp
prod_userapache

Naast de parameter name kan de parameter groups gebruikt worden. Om de loop nu te voorzien van 2 waardes creëren we meerdere key/value pairs (hash). De variabele {{item}} zal nu gebruik moeten maken van de subkey {{ item.groups }}.

  • Herschrijf je playbook zodat eerst, indien nodig, de groepen aangemaakt worden en daarna de gebruikers ook in die correcte groepen toegevoegd worden:

    loop_users.yml
    ---
    - name: Ensure users are present
    hosts: node1
    become: true

    tasks:
    - name: Ensure groups are present
    ansible.builtin.group:
    name: "{{ item }}"
    state: present
    loop:
    - ftp
    - apache
    - name: Ensure three users are present
    ansible.builtin.user:
    name: "{{ item.username }}"
    state: present
    groups: "{{ item.groups }}"
    loop:
    - { username: "dev_user", groups: "ftp" }
    - { username: "qa_user", groups: ["ftp", "apache"]}
    - { username: "prod_user", groups: "apache" }

  • Controleer na het uitvoeren of dit succesvol gelukt is:

[student@ControlHost ansible-files]$ ansible node1 -m command -a "groups qa_user"

© Deze workshop werd gebaseerd op de informatie van Red Hat Ansible Automation Platform