WORKSHOP 3: Playbooks
In de vorige workshop leerden we ad-hoc commands
gebruiken. Handig voor eenvoudige opdrachten maar ontoereikend voor complexe configuratie- of management taken. In zo'n gevallen zijn playbooks
veel geschikter.
Met deze workshop zullen we playbooks gebruiken om Apache web servers te installeren met Ansible.
Doelstelling
In deze workshop leer je:
- De volgende Ansible-modules begrijpen en gebruiken a.d.h.v.
Playbooks
:apt
moduleservice
modulecopy
module
Workshop
Playbooks
zijn bestanden die de gewenste configuratie en stappen beschrijven (desired state
). Playbooks kunnen lange, complexe en manuele administratieve taken veranderen in eenvoudige, herhaalbare en voorspelbare configuraties.
Bij een playbook
gaan we enkele van die verschillende ad-hoc
-taken nemen, die je daarnet gebruikt hebt en deze in een herhaalbare set van plays
en tasks
plaatsen.
Een playbook
kan meerdere plays
hebben en een play
kan meerdere tasks
hebben. In zo'n task
wordt dan een module
aangesproken zie ook documentatie Playbooks.
Stap 1 - Playbook Basics
Playbooks zijn tekst-bestanden in YAML
-stijl.
Daarbij is het volgende van belang:
- Een YAML-file (playbook) start steeds met 3 koppeltekens =>
( --- )
. - consequente indentatie (inspringen) m.b.v.
spaties
en géén tabs.
Verder zijn volgende keywords in de playbook belangrijk:
hosts
: de host of groep uit de inventory waarop de playbook moet toegepast worden.tasks
: de bewerkingen die moeten worden uitgevoerd door Ansible-modules bijhorende optiesbecome:
: privilege escalation => identiek als-b
(become) in ad-hoc commands =>sudo-rechten
de volgorde binnen een playbook is belangrijk aangezien Ansible de plays
en tasks
sequentieel zoals in de playbook uitvoert.
Een playbook is ook (meestal) idempotent
. Dit betekent dat het veilig is om eenzelfde playbook meerdere keren uit te voeren. Dit zou dus aanpassingen ook maar 1 keer uitvoeren. Er wordt m.a.w. op voorhand gekeken of de aanpassing nodig is of niet (zie ook).
de meeste Ansible-modules zijn effectief idempotent. Enkele uitzonderingen zijn dit door hun aard van werking niet. In dat geval moet je daar wel rekening mee houden.
Stap 2 - Een mappen- en bestandsstructuur voor je Playbook
Tijd voor je eerste playbook! In dit onderdeel stellen we een playbook samen die een Apache-webserver opzet in 3 stappen:
apache2
package installeren- de apache2
service
activeren en starten - een web.html bestand
kopiëren
naar elke host.
Er is een best practice
wat betreft de mappen-structuur voor playbooks. Je kan dit best eens bekijken voor volgende ansible-projecten. Voor deze oefening houden we het nog even simpel.
- Maak een nieuwe map in je home-folder aan met de naam
ansible-files
.
[student@ControlHost ~]$ cd ~
[student@ControlHost ~]$ mkdir ansible-files
[student@ControlHost ~]$ cd ansible-files
[student@ControlHost ansible-files]$
- Voorzie een gelijkaardige ìnventory (yaml-stijl) met de naam "hosts.yml" in deze map zoals in de vorige workshop en laat de dummy-host achterwege.
---
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>
vars:
ansible_user: ubuntu
ansible_password: PASSWORD
ansible_connection: ssh
ansible_become_pass: Azerty123
control:
hosts:
ansible-1:
ansible_host: <A.A.A.A>
vars:
ansible_user: student
ansible_password: PASSWORD
ansible_connection: local
vars:
ansible_port: 22
- Maak ook een
ansible.cfg
bestand aan waarin opgegeven wordt dat je met deze inventory gaat werken. - Pas deze ansible.cfg ook hier zo aan dat ssh-fingerprints vertrouwd worden
[defaults]
inventory= ./hosts
host_key_checking=False
- Maak een nieuw bestand aan met de naam
apache.yml
en start er je eersteplay
. Gebruik hiervoor vi, vim of Visual Studio Code om het bestand te bewerken.
---
- name: Apache server installed
hosts: node1
become: true
Bovenstaand fragment is makkelijk te begrijpen. In deze playbook werd:
- een naam gegeven aan de play via het keyword
name:
. - bepaald op welke hosts deze playbook moet uitgevoerd worden via
hosts
. - aangegeven dat er met sudo-rechten moet gewerkt worden op de remote hosts via
become
.
het is in dit geval duidelijk dat er privilege escalation nodig is aangezien er voor het installeren van pakketten op een systeem steeds root-permissies (sudo-rechten) noodzakelijk zijn.
Nu we onze eerste play
gestart hebben zullen we die verder aanvullen met een eerste task
.
Met de eerste taak gaan we verifiëren of de laatste versie van Apache aanwezig is en indien nodig deze te installeren.
- Pas je playbook verder aan met de eerste taak binnen de play:
---
- name: Apache server installed
hosts: node1
become: true
tasks:
- name: Latest Apache version installed
ansible.builtin.apt:
name: apache2
state: latest
Zoals eerder gezegd is de aliniëring van regels en keywords cruciaal. Zorg zeker dat de t
in tasks
verticaal gelijk staat met de b
van become
.
In principe zorgt een IDE (zoals vscode) daar automatisch voor.
Ansible doet zelf een boolean-conversie, zodat je true ook als 'yes' of 'True' kan meegeven.
Om zeker geen problemen te hebben met automatische linting blijf je best bij de officiële manier om booleans te gebruiken in yaml: 'true' en 'false'
- Het kan zeker handig zijn om je wat in te werken in de
YAML-syntax
(zie documentatie). - Een
linter
kan je helpen om de systax van je Ansible- of YAML-files te controleren. In Visual Studio Code kan je een Ansible extension installeren die je helpt via syntax-highlighting en linting. :::
In bovenstaand fragment voegden we volgende items toe:
-
Het keyword
tasks:
waaronder alle tasks zullen opgelijst worden binnen deze play -
Een concrete task die we een eigen gekozen naam kunnen geven met
name:
. We refereren naar de gebruikte ansible-module via de modulenaam, hierapt
. Elke nieuwe task ondertasks
wordt gestart met een"-"
. -
Binnen deze specifieke
task
geven we parameters mee door opnieuw in te springen:name:
om het pakket te definiëren dat we willen installeren metapt
.state:
waarmee we willen meegeven in welke "status" de package zich moet bevinden
-
Sla je playbook op en verlaat je editor.
In deze (en volgende voorbeelden) gebruiken we steeds latest
bij de state
parameter. Meestal is dat geen goed idee aangezien je dan geen vat hebt op welke versie precies zal geïnstalleerd worden. Men kan beter aan version pinning
doen waarbij de exacte versie vastgelegd wordt.
Een linter, zoals ansible-lint
zal je dat ook duidelijk maken (zie documentatie)
Stap 3 - Playbook starten
Een Ansible Playbook kan je starten door op de Control Host het commando ansible-playbook
te gebruiken.
Maar vooraleer een playbook effectief te laten lopen is het een goed idee om de syntax van je playbook eens te controleren op fouten (ook als je een linter gebruikt kan dit een extra controle zijn).
- Voer volgende controle uit:
[student@ControlHost ansible-files]$ ansible-playbook --syntax-check apache.yml
- Als er geen fouten in vorige test zaten, kan je de playbook nu effectief uitvoeren:
[student@ControlHost ansible-files]$ ansible-playbook apache.yml
PLAY [Apache server installed] **********************************************************
TASK [Gathering Facts] *******************************************************************
ok: [node1]
// highlight-start
TASK [latest Apache version installed] ***************************************************
changed: [node1]
// highlight-end
PLAY RECAP **********************************************************
// highlight-next-line
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Je output zou, zoals hierboven, geen fouten mogen aangeven maar wel een overzicht geven van de taken die uitgevoerd werden aangevuld met een recap
(samenvatting).
Er is ook een "built-in" taak met de naam Gathering Facts
die automatisch uitgevoerd wordt bij het begin van élke play
.
Deze Gathering Facts
verzamelt informatie van elke host die beheerd wordt in deze play. Deze info kunnen we later ook gebruiken (zie verdere workshops).
- Connecteer even rechtstreeks met
node1
via SSH en controleer of Apache geïnstaleerd werd.
[student@ControlHost ansible-files]$ ssh ubuntu@x.x.x.x
ubuntu@10.129.36.30 password:
ubuntu@IaC-Managed-Host-1:~$ dpkg -l apache2
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name Version Architecture Description
+++-==============-=================-============-=================================
// highlight-next-line
ii apache2 2.4.52-1ubuntu4.4 amd64 Apache HTTP Server
ubuntu@IaC-Managed-Host-1:~$ exit
[student@ControlHost ansible-files]$
Let op de ii
, wat aangeeft dat het packet het pakket succesvol geïnstalleerd werd.
- Controleer ook even het resultaat via een ad-hoc command:
[student@ControlHost ansible-files]$ ansible node1 -m command -a "dpkg -l apache2"
- Voer je playbook van daarnet nogmaal uit en vergelijk de output met de eerste keer.
De output zou van
changed
moeten veranderd zijn naarok
, en de kleur van geel naar groen. Daarnaast zou dePLAY RECAP
nu ook aangepast moeten zijn. Vergelijk!
[student@ControlHost ansible-files]$ ansible-playbook apache.yml
Stap 4 - Playbook uitbreiden: Apache starten en activeren
In de volgende stap breiden we de playbook uit zodat de Apache service zal starten én dat deze ook bij elke reboot zal opgestart zijn. Voor dat laatste moeten we de service "enablen" (module documentatie "service")
- Pas je playbook aan:
---
- name: Apache server installed
hosts: node1
become: true
tasks:
- name: Latest Apache version installed
ansible.builtin.apt:
name: apache2
state: latest
- name: Apache enable and running
ansible.builtin.service:
name: apache2
enabled: true
state: started
-
Zoals bij de vorige taak werden er nieuwe lijnen toegevoegd :
- een 2de taak met eigen gekozen naam (nieuwe taak is een nieuw item in de lijst =>
"-"
. - de module werd gespecifieerd (
service
) - parameters voor de module werden meegegeven:
- een 2de taak met eigen gekozen naam (nieuwe taak is een nieuw item in de lijst =>
-
Deze aangepaste playbook kunnen we nogmaals uitvoeren:
[student@ControlHost ansible-files]$ ansible-playbook apache.yml
Bekijk de output. Daar zou je nu de nieuwe taak TASK [Apache enable and running]
moeten toegevoegd zien.
- Gebruik een ad-hoc command om te controleren of de service effectief online en enabled is met
systemctl status apache2
.
Stap 5 - Playbook uitbreiden: Een webpagina toevoegen
Als in de vorige stap Apache effectief goed gestart werd dan kunnen daar proberen naartoe te surfen.
Met een ad-hoc command dat we lokaal
gaan uitvoeren op onze Ansible Control Host maken we een http-request naar onze webserver op node1
(uiteraard zou je ook met een gewone browser moeten kunnen surfen naar deze webserver).
[student@ControlHost ansible-files]$ ansible localhost -m uri -a "url=http://A.A.A.A"
localhost | SUCCESS => {
"accept_ranges": "bytes",
"changed": false,
"connection": "close",
"content_length": "10671",
"content_type": "text/html",
"cookies": {},
"cookies_string": "",
"date": "Tue, 18 Apr 2023 11:51:37 GMT",
"elapsed": 0,
"etag": "\"29af-5f99adb6c9004\"",
"last_modified": "Tue, 18 Apr 2023 11:46:25 GMT",
"msg": "OK (10671 bytes)",
"redirected": false,
"server": "Apache/2.4.52 (Ubuntu)",
"status": 200,
"url": "http://10.129.24.184",
"vary": "Accept-Encoding"
}
[student@ControlHost ansible-files]$
Je zou hier als resultaat opnieuw een SUCCESS
moeten krijgen.
Verifieer ook de HTTP status code die 200 zou moeten zijn voor een succesvolle request.
De default installatie van Apache geeft immers een default informatie pagina weer.
We willen nu via Ansible deze default pagina aanpassen met een eigen HTML-pagina.
- Maak in de huidige projectmap een submap aan met de naam
files
en creëer er een tekstbestand met de naam web.html:
[student@ControlHost ansible-files]$ mkdir files
[student@ControlHost ansible-files]$ nano ./files/web.html
<body>
<h1>Apache is running fine</h1>
</body>
Je gebruikte al eerder de Ansible module copy
in een ad-hoc command. Nu gaan we deze zelfde module gebruiken als task in een Playbook om een file te kopiëren van de lokale Ansible Control Host naar de webserver(s) (documentatie module copy).
- Pas je playbook verder aan met opnieuw een nieuwe task:
---
- name: Apache server installed
hosts: node1
become: true
tasks:
- name: Latest Apache version installed
ansible.builtin.apt:
name: apache2
state: latest
- name: Apache enable and running
ansible.builtin.service:
name: apache2
enabled: true
state: started
- name: Copy html file
ansible.builtin.copy:
src: web.html
dest: /var/www/html/index.html
- Voer je playbook nogmaals uit:
[student@ControlHost ansible-files]$ ansible-playbook apache.yml
Ondanks web.html in een subfolder zit (files) werd het pad naar deze subfolder niet opgegeven bij src
. Toch wist Ansible het bestand te vinden. Ansible gaat steeds in de huidige directory én in de /files map op zoek naar de gewenste bestanden.
Uiteraard kan je ook absolute en relatieve paden meegeven bij de src
-parameter.
- Controleer het resulaat eens via een browser. Surf naar je webserver
node1
(http://X.X.X.X). De pagina zou nu de aangepaste tekst moeten vertonen.
Stap 6 - Ansible @ Full Force
Tot nu toe hebben we Ansible telkens gebruikt om de configuratie van 1 host aan te passen. Uiteraard komt Ansible tot zijn volle waarde als we de playbooks kunnen gebruiken t.o.v. meerdere hosts (datacenter,...) tegelijkertijd.
Zoals je nog weet hebben we in de inventory (nu met de naam hosts
in projectfolder) een groep voorzien met 3 webservers in (node1
,node2
en node3
).
- Pas je playbook aan zodat de
play
nu uitgevoerd zal worden op alle hosts van de groepweb
:
---
- name: Apache server installed
hosts: web
become: true
tasks:
- name: latest Apache version installed
apt:
name: apache2
state: latest
- name: Apache enable and running
service:
name: apache2
enabled: true
state: started
- name: copy html file
copy:
src: web.html
dest: /var/www/html/index.html
Het uitvoeren van deze playbook zal even wat langer duren omdat alle taken (installeren Apache,...) nog moeten uitgevoerd worden. In het resulterende overzicht zou je mooi moeten zien welke hosts al in orde waren (node1
) en welke hosts nog aangepast werden (node2
en node3
).
[student@ControlHost ansible-files]$ ansible-playbook apache.yml
PLAY [Apache server installed] ************************************************************************
TASK [Gathering Facts] ********************************************************************************
ok: [node1]
ok: [node2]
ok: [node3]
TASK [latest Apache version installed] ****************************************************************
ok: [node1]
changed: [node2]
changed: [node3]
TASK [Apache enable and running] **********************************************************************
ok: [node1]
ok: [node3]
ok: [node2]
TASK [copy html file] *********************************************************************************
ok: [node1]
changed: [node2]
changed: [node3]
// highlight-start
PLAY RECAP ********************************************************************************************
node1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
// highlight-end
-
Controleer nu even of je effectief kan surfen via je browser (of via curl-command) naar de 2 nieuwe hosts.
-
Eventueel kan je bovenstaande met 1
ad-hoc
command uitvoeren:
[student@ControlHost ansible-files]$ ansible node2,node3 -m uri -a "url=http://localhost/"
Hiermee gebruik je de module uri
om op elke host even een web-request te doen naar zijn eigen (localhost).
Als resultaat zou je 2 maal SUCCESS
moeten krijgen en 2 maal status code 200
.
Zoals je ziet wordt ons Ansible-project al iets uitgebreider. Om alles goed gestructureerd bij te houden kan je best de aanbevolen mappen-structuur van Ansible volgen. Bekijk zeker eens de aanbevelingen daarover op de documentatie-site van Ansible. Dit zal je zeker nodig hebben bij je project.
(C) Deze workshop werd gebaseerd op de informatie van Red Hat Ansible Automation Platform