Pour rappel, les avantages du code Ansible, qui donne tout son intérêt à l’Infrastructure-as-Code sont :
La dimension incrémentale du code rend en particulier plus aisé de construire une infrastructure progressivement en la complexifiant au fur et à mesure plutôt que de devoir tout plannifier à l’avance.
Le playbook
est une sorte de script Ansible, c’est-à-dire du code.
Le nom provient du football américain : il s’agit d’un ensemble de stratégies qu’une équipe a travaillé pour répondre aux situations du match. Elle insiste sur la versatilité de l’outil.
Les playbooks ansible sont écrits au format YAML.
A quoi ça ressemble ?
- 1
- Poire
- "Message à caractère informatif"
clé1: valeur1
clé2: valeur2
clé3: 3
marché: # début du dictionnaire global "marché"
lieu: Crimée Curial
jour: dimanche
horaire:
unité: "heure"
min: 9
max: 14 # entier
fruits: # liste de dictionnaires décrivant chaque fruit
- nom: pomme
couleur: "verte"
pesticide: avec # les chaines sont avec ou sans " ou '
- nom: poires
couleur: jaune
pesticide: sans
légumes: # liste de 3 éléments
- courgettes
- salade
# on peut sauter des lignes sans interrompre la liste ou le dictionnaire en cours
- potiron
# fin du dictionnaire global
Pour mieux visualiser l’imbrication des dictionnaires et des listes en YAML on peut utiliser un convertisseur YAML -> JSON : https://www.json2yaml.com/.
Notre marché devient:
{
"marché": {
"lieu": "Crimée Curial",
"jour": "dimanche",
"horaire": {
"unité": "heure",
"min": 9,
"max": 14
},
"fruits": [
{
"nom": "pomme",
"couleur": "verte",
"pesticide": "avec"
},
{
"nom": "poires",
"couleur": "jaune",
"pesticide": "sans"
}
],
"légumes": [
"courgettes",
"salade",
"potiron"
]
}
}
Observez en particulier la syntaxe assez condensée de la liste “fruits” en YAML qui est une liste de dictionnaires.
---
- name: premier play # une liste de play (chaque play commence par un tiret)
hosts: serveur_web # un premier play
become: yes
gather_facts: false # récupérer le dictionnaires d'informations (facts) relatives aux machines
vars:
logfile_name: "auth.log"
var_files:
- mesvariables.yml
pre_tasks:
- name: dynamic variable
set_fact:
mavariable: "{{ inventory_hostname + 'prod' }}" #guillemets obligatoires
rôles:
- flaskapp
tasks:
- name: installer le serveur nginx
apt: name=nginx state=present # syntaxe concise proche des commandes ad hoc mais moins lisible
- name: créer un fichier de log
file: # syntaxe yaml extensive : conseillée
path: /var/log/{{ logfile_name }} #guillemets facultatifs
mode: 755
- import_tasks: mestaches.yml
handlers:
- systemd:
name: nginx
state: "reloaded"
- name: un autre play
hosts: dbservers
tasks:
...
Un playbook commence par un tiret car il s’agit d’une liste de plays.
Un play est un dictionnaire yaml qui décrit un ensemble de tâches ordonnées en plusieurs sections. Un play commence par préciser sur quelles machines il s’applique puis précise quelques paramètres faculatifs d’exécution comme become: yes
pour l’élévation de privilège (section hosts
).
La section hosts
est obligatoire. Toutes les autres sections sont facultatives !
La section tasks
est généralement la section principale car elle décrit les tâches de configuration à appliquer.
La section tasks
peut être remplacée ou complétée par une section roles
et des sections pre_tasks
post_tasks
Les handlers
sont des tâches conditionnelles qui s’exécutent à la fin (post traitements conditionnels comme le redémarrage d’un service)
pre_tasks
roles
tasks
post_tasks
handlers
Les rôles ne sont pas des tâches à proprement parler mais un ensemble de tâches et ressources regroupées dans un module, un peu comme une librairie dans le développement. Nous explorerons les rôles au cours 3.
name:
qui décrit lors de l’exécution la tâche en cours : un des principes de l’Infrastructure-as-Code est l’intelligibilité des opérations.Pour valider la syntaxe il est possible d’installer et utiliser ansible-linter
sur les fichiers YAML.
L’élévation de privilège est nécessaire lorsqu’on a besoin d’être root
pour exécuter une commande ou plus généralement qu’on a besoin d’exécuter une commande avec un utilisateur différent de celui utilisé pour la connexion on peut utiliser:
Au moment de l’exécution l’argument --become
en ligne de commande avec ansible
, ansible-console
ou ansible-playbook
.
La section become: yes
hosts
) : toutes les tâches seront executée avec cette élévation par défaut.Pour executer une tâche avec un autre utilisateur que root (become simple) ou celui de connexion (sans become) on le précise en ajoutant à become: yes
, become_user: username
Ansible utilise en arrière plan un dictionnaire contenant de nombreuses variables.
Pour s’en rendre compte on peut lancer :
ansible <hote_ou_groupe> -m debug -a "msg={{ hostvars }}"
Ce dictionnaire contient en particulier:
ansible_user
par exemple)ansible_os_family
) et récupéré au lancement d’un playbook.La plupart des fichiers Ansible (sauf l’inventaire) sont traités avec le moteur de template python JinJa2.
Ce moteur permet de créer des valeurs dynamiques dans le code des playbooks, des rôles, et des fichiers de configuration.
Les variables écrites au format {{ mavariable }}
sont remplacées par leur valeur provenant du dictionnaire d’exécution d’Ansible.
Des filtres (fonctions de transformation) permettent de transformer la valeur des variables: exemple : {{ hostname | default('localhost') }}
(Voir plus bas)
Les fichiers de templates (.j2) utilisés avec le module template, généralement pour créer des fichiers de configuration peuvent contenir des variables et des filtres comme les fichier de code (voir au dessus) mais également d’autres constructions jinja2 comme:
if
: {% if nginx_state == 'present' %}...{% endif %}
.for
: {% for host in groups['appserver'] %}...{% endfor %}
.{% include 'autre_fichier_template.j2' %}
On peut définir et modifier la valeur des variables à différents endroits du code ansible:
vars:
du playbook.var_files:
group_vars
, host_bars
defaults
des rôles (cf partie sur les rôles)set_facts
.--extra-vars "version=1.23.45 other_variable=foo"
Lorsque définies plusieurs fois, les variables ont des priorités en fonction de l’endroit de définition.
L’ordre de priorité est plutôt complexe: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable
En résumé la règle peut être exprimée comme suit: les variables de runtime sont prioritaires sur les variables dans un playbook qui sont prioritaires sur les variables de l’inventaire qui sont prioritaires sur les variables par défaut d’un rôle.
groups.all
et groups['all']
sont deux syntaxes équivalentes pour désigner les éléments d’un dictionnaire.https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
Les plus utiles:
hostvars
: dictionaire de toute les variables rangées par hote de l’inventaire.ansible_host
: information utilisée pour la connexion (ip ou domaine).inventory_hostname
: nom de la machine dans l’inventaire.groups
: dictionnaire de tous les groupes avec la liste des machines appartenant à chaque groupe.Pour explorer chacune de ces variables vous pouvez utiliser le module debug
en mode adhoc ou dans un playbook:
ansible <hote_ou_groupe> -m debug -a "msg={{ ansible_host }}"
ou encore:
ansible <hote_ou_groupe> -m debug -a "msg={{ groups.all }}"
Les facts sont des valeurs de variables récupérées au début de l’exécution durant l’étape gather_facts et qui décrivent l’état courant de chaque machine.
ansible_os_family
est un fact/variable décrivant le type d’OS installé sur la machine. Elle n’existe qu’une fois les facts récupérés.! Lors d’une commande adhoc ansible les facts ne sont pas récupérés : la variable ansible_os_family
ne sera pas disponible.
La liste des facts peut être trouvée dans la documentation et dépend des plugins utilisés pour les récupérés: https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html
when
Elle permet de rendre une tâche conditionnelle (une sorte de if
)
- name: start nginx service
systemd:
name: nginx
state: started
when: ansible_os_family == 'RedHat'
Sinon la tâche est sautée (skipped) durant l’exécution.
loop:
Cette directive permet d’executer une tâche plusieurs fois basée sur une liste de valeur:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
exemple:
- hosts: localhost
tasks:
- name: exemple de boucle
debug:
msg: "{{ item }}"
loop:
- message1
- message2
- message3
On peut également controler cette boucle avec quelques paramètres:
- hosts: localhost
vars:
messages:
- message1
- message2
- message3
tasks:
- name: exemple de boucle
debug:
msg: "message numero {{ num }} : {{ message }}"
loop: "{{ messages }}"
loop_control:
loop_var: message
index_var: num
Cette fonctionnalité de boucle était anciennement accessible avec le mot clé with_items:
qui est maintenant déprécié.
Pour transformer la valeur des variables à la volée lors de leur appel on peut utiliser des filtres (jinja2) :
{{ hostname | default('localhost') }}
La liste complète des filtres ansible se trouve ici : https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html
Avec Ansible on dispose d’au moins trois manières de debugger un playbook:
Rendre la sortie verbeuse (mode debug) avec -vvv
.
Utiliser une tâche avec le module debug
: debug msg="{{ mavariable }}"
.
Utiliser la directive debugger: always
ou on_failed
à ajouter à la fin d’une tâche. L’exécution s’arrête alors après l’exécution de cette tâche et propose un interpreteur de debug.
Les commandes et l’usage du debugger sont décrits dans la documentation: https://docs.ansible.com/ansible/latest/user_guide/playbooks_debugger.html