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 onderweb
op het hoogste niveau komen. De specifieke variabelen voor de groepcontrol
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"]
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 modulefetch
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 serviceapache2
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 denotify
.
- De
-
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 geenchanged
. 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 moduleuser
(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.
Gebruiker | Groep |
---|---|
dev_user | ftp |
qa_user | ftp |
prod_user | apache |
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