![Logo Ansible](images/logo-ansible.svg)
# Playbooks Ansible
Jouer et rejouer facilement un jeu de commandes prédéfini sur un ensemble de machines.
![Schéma archi globale](images/ansible-archi-playbooks.png)
## Format du Playbook
* Il se présente sous la forme d'un simple fichier texte au format `yaml`.
* Il peut donc être facilement versionné dans un outil de gestion de version.
### yaml
* Format ouvert de représentation de données.
* Acronyme récursif de _YAML Ain't Markup Language_.
* Permet de représenter des données complexes tout en conservant une excellente lisibilité.
* Utilisation en forte progression ces dernières années.
Ansible, GitLab, Docker Compose, Kubernetes manifests, etc...
* Site officiel : https://yaml.org/
### YAML basics
https://fr.wikipedia.org/wiki/YAML
* _attention_ ! l'indentation se fait avec un ou plusieurs espaces, jamais avec des tabulations !
* Les commentaires sont signalés par le signe dièse `#` et se prolongent sur toute la ligne.
* Les éléments de listes sont dénotés par le tiret `-`, suivi d'une espace, à raison d'un élément par ligne.
* Les tableaux sont de la forme `clé: valeur`, à raison d'un couple par ligne.
### YAML basics
https://fr.wikipedia.org/wiki/YAML
* Les chaînes de caractères peuvent être entourées de guillemets doubles `"`, ou simples `'`, sachant qu'un guillemet s'échappe avec un antislash `\`, alors qu'une apostrophe s'échappe avec une autre apostrophe.
* Les chaînes de caractères peuvent de plus être représentées par un bloc indenté avec des modificateurs facultatifs pour conserver `|` ou éliminer `>` les retours à la ligne.
* Plusieurs documents rassemblés dans un seul fichier sont séparés par trois traits d'union `---`.
### YAML basics
https://fr.wikipedia.org/wiki/YAML
```
---
receipt: Oz-Ware Purchase Invoice
date: 2012-08-06
customer:
given: Dorothy
family: Gale
items:
- part_no: A4786
descrip: Water Bucket (Filled)
price: 1.47
quantity: 4
- part_no: E1628
descrip: High Heeled "Ruby" Slippers
size: 8
price: 100.27
quantity: 1
bill-to: &id001
street: |
123 Tornado Alley
Suite 16
city: East Centerville
state: KS
ship-to: *id001
specialDelivery: >
Follow the Yellow Brick
Road to the Emerald City.
Pay no attention to the
man behind the curtain.
...
```
### yaml - Définition d’une collection (-)
```yaml
# Une liste de fruits
fruits:
- pomme
- orange
- framboise
- mangue
```
Forme abrégée :
```yaml
# Une liste de fruits
fruits: ['pomme', 'orange', 'framboise', 'mangue']
```
### Définition d’un dictionnaire (key: value)
```yaml
# Un utilisateur
martin:
name: Martin Dupond
job: developer
skill: python
```
Forme abrégée :
```yaml
# Un utilisateur
martin: { name: Martin Dupond, job: developer, skill: python }
```
## Tasks
* Les commandes d'un Playbook sont découpées en instructions unitaires appelées _tâches_ (tasks).
* Chaque tâche exécute un module Ansible avec des paramètres spécifiques.
* Format d'une tâche :
```none
- name: Description de la tâche
:
:
:
:
```
### Liste de tâches
* Un playbook peut décrire une liste de plusieurs tâches.
* Les tâches seront exécutées dans l'ordre d'apparition et de façon séquentielle sur chacune des machines cibles.
```none
tasks: <----------------------------- liste de tâche
- name: Ma tâche 1 <--------------- tâche 1
:
:
:
:
- name: Ma tâche 2 <--------------- tâche 2
:
:
:
```
### Exemple réel
```yaml
- hosts: web # exécution d'un 'Play' sur le groupe 'web'
tasks:
- name: Installation of Apache Package # tâche 1
yum:
name: httpd
state: present
update_cache: yes
- name: Ensure Apache is running (and enabled at boot) # tâche 2
service: name=httpd state=started enabled=yes
```
* Ici on mixe les deux types de syntaxes (normale et abrégée).
* __Les bonnes pratiques préconisent l'utilisation de la syntaxe normale.__
### Exemple réel
avec syntaxe normale
```yaml
- hosts: web
tasks:
- name: Installation of Apache Package
yum:
name: httpd
state: present
update_cache: yes
- name: Ensure Apache is running (and enabled at boot)
service:
name: httpd
state: started
enabled: yes
```
### Notion de Play
```yaml
---
- hosts: webservers # Play 1 sur le groupe webservers
tasks:
- name: My task
...
- hosts: databases # Play 2 sur le groupe databases
tasks:
- name: My task...
...
```
* Chaque Play contient sa propre liste de tâches et cible un ensemble spécifique de machines.
## Lancer un playbook
`$ ansible-playbook -i `
```none
$ ansible-playbook playbook.yaml -i ./hosts
PLAY [web] *******************************************************************
TASK [setup] *****************************************************************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
TASK [Installation du package Apache] ****************************************
changed: [web2.formation.sii.fr]
changed: [web1.formation.sii.fr]
TASK [Ensure Apache is running (and enable it at boot)] **********************
changed: [web1.formation.sii.fr]
changed: [web2.formation.sii.fr]
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
```
* Notre Playbook comportait 2 tâches seulement, nous en voyons 3 !
* Ansible a automatiquement ajouté à l'exécution une tâche nommée _setup_ dont l'objectif est de récupérer les _facts_ des machines cibles.
## Récapitulatif du Playbook
```none
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
```
* Sur les 2 machines cibles :
- `ok` : 3 tâches ont été exécutées avec succès.
- `changed` : 2 tâches ont modifié l'état du système.
- `unreachable` : Toutes les machines étaient joignables.
- `failed` : Aucune tâche n'a échoué.
## Relancer un playbook
```none
$ ansible-playbook playbook.yaml -i ./hosts --become
PLAY [web] *******************************************************************
TASK [setup] *****************************************************************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
TASK [Installation du package Apache] ****************************************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
TASK [Ensure Apache is running (and enable it at boot)] **********************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0
```
## Première exécution
```none
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
```
## Seconde exécution
```none
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=0 unreachable=0 failed=0
```
## Ordre d'exécution
* Il est possible de contrôler l'ordre dans lequel les machines cibles sont adressées.
* L'ordre par défaut est l'ordre d'apparition dans l'inventaire.
```yaml
- hosts: all
order: sorted # l'ordre est défini ici
gather_facts: False
tasks:
- debug:
var: inventory_hostname
```
Ordre | Description
- | -
`inventory` | Ordre d'apparition dans l'inventaire. C'est le choix par défaut.
`reverse_inventory` | Ordre inverse d'apparition dans l'inventaire.
`sorted` | Ordre alphabétique des noms de machines.
`reverse_sorted` | Ordre alphabétique inverse des noms de machines.
`shuffle` | Ordre aléatoire.
## Démarrer l'exécution à un endroit précis
`$ ansible-playbook playbook.yaml --start-at-task="my task"`
Cette commande démarre l'exécution du playbook à partir de la tâche nommée `my task`.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_startnstep.html
## Ignorer le code de retour d'une commande
* Les modules _command_ et _shell_ sont sensibles au code de retour des commandes.
* Pour ignorer les erreurs sur une commande qui renvoie un code > 0 on peut utiliser `ignore_errors`.
* Ou, utiliser `failed_when`.
* De même, `changed_when` peut être utilisé.
```yaml
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
```
## Déclaration de variables
```yaml
- hosts: web
vars:
- app_directory: /var/www/html
- app_user: apache
- app_group: apache
tasks:
- name: Modify permission on {{ app_directory }}
file:
dest: '{{ app_directory }}'
mode: 0755
owner: '{{ app_user }}'
group: '{{ app_group }}'
recurse: yes
```
Les variables sont déclarées dans _vars_
et résolues avec `{{ }}`.
```none
$ ansible-playbook playbook.yaml -i ./hosts
PLAY [web] ****************************************************************
TASK [setup] **************************************************************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
TASK [Modify permission of directory /var/www/html] ***********************
changed: [web1.formation.sii.fr]
changed: [web2.formation.sii.fr]
PLAY RECAP ****************************************************************
web1.formation.sii.fr : ok=2 changed=1 unreachable=0 failed=0
web2.formation.sii.fr : ok=2 changed=1 unreachable=0 failed=0
```
## Les _handlers_ et les _notify_
```yaml
- hosts: web
vars:
- apache_listen_port: 8080
tasks:
- name: Modify Apache configuration
lineinfile:
dest: /etc/httpd/conf/httpd.conf
regexp: '^Listen '
line: 'Listen {{ apache_listen_port }}'
notify: Reload Apache # Signale que la configuration
# d'Apache doit être rechargée
handlers:
- name: Reload Apache # Recharge la configuration
service:
name: httpd
state: reloaded
```
```none
$ ansible-playbook playbook.yaml -i ./hosts
PLAY [web] *******************************************************************
TASK [setup] *****************************************************************
ok: [web1.formation.sii.fr]
ok: [web2.formation.sii.fr]
TASK [Modify Apache configuration] *******************************************
changed: [web1.formation.sii.fr]
changed: [web2.formation.sii.fr]
RUNNING HANDLER [Reload Apache] *********************************************
changed: [web1.formation.sii.fr]
changed: [web2.formation.sii.fr]
PLAY RECAP *******************************************************************
web1.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
web2.formation.sii.fr : ok=3 changed=2 unreachable=0 failed=0
```
## Idempotence
* L'idempotence signifie qu'une opération a le même effet qu'on l'applique une ou plusieurs fois.
* Les commandes d'un Playbook doivent être écrites de manière à produire le même résultat quel que soit le nombre de fois où elles sont exécutées sur une même cible.
## Travaux pratiques
![Travaux pratiques](images/tp.gif)
[TP Ansible : playbooks](travaux-pratiques/tp-ansible-playbooks.html)
## Variables sur la ligne de commande
Il est possible d'initialiser des variables directement sur la ligne de commande avec l'option `--extra-vars` (ou `-e`).
* Ces variables peuvent être définies sous la forme :
- chaîne de caractères
`$ ansible-playbook playbook.yaml --extra-vars "my_var_1=foo my_var_2=bar"`
- json
`$ ansible-playbook playbook.yaml --extra-vars '{"my_var_1":"foo","my_var_2":"bar"}'`
`$ ansible-playbook playbook.yaml --extra-vars '{"my_var":"foo","my_list":["foo","bar"]}'`
* Utilisez le fomat _json_ si vous voulez passer autre chose que des _strings_ :
- booleans
- integers
- floats
- lists
- ...
## Découper un Playbook
* Un Playbook peut se présenter sous la forme d'un fichier unique.
* Toutefois il est possible de le découper en plusieurs fichiers séparés afin de mieux organiser et favoriser la ré-utilisation de certaines parties.
* Il existe plusieurs manières de découper un Playbook :
les _includes_, les _imports_, et les _roles_.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse.html
### Includes et Imports
* Disponibles à partir de Ansible v2.4.
* Permettent le découpage des tâches d'un gros Playbook en fichiers plus petits.
* Ces fichiers peuvent ensuite être appelés :
- depuis un ou plusieurs Playbooks
- plusieurs fois dans un même Playbook.
### Import dynamique vs statique
* Les commandes `import` permettent
un chargement statique.
(import_playbook, import_tasks, etc.)
* Les commandes `include` permettent
un chargement dynamique.
(include_tasks, include_role, etc.)
### Statique vs Dynamique
* _Statique_
Ansible traite les imports statique au moment de l'analyse du Playbook (avant l'exécution).
* _Dynamique_
Ansible traite les imports dynamiques au fur et à mesure durant l'exécution du Playbook.
* Les imports statiques et dynamiques peuvent être mixés, toutefois cela n'est pas recommandé car cela rend le debug des Playbooks plus complexe.
### Import de Playbooks
* Il est possible d'importer un ou plusieurs Playbooks à l'intérieur d'un Playbook maître, avec `import_playbook`.
### Roles
* Plus puissants que les _includes_ et les _imports_.
* Permettent d'empaqueter un ensemble de tâches ainsi que les variables, handlers et autres autres éléments associés.
* Les _roles_ peuvent être facilement ré-utilisés et partagés.
## Travaux pratiques
![Travaux pratiques](images/tp.gif)
[TP Ansible : imports](travaux-pratiques/tp-ansible-imports.html)