Les commandes ad-hoc sont des appels directs de modules Ansible qui fonctionnent de façon idempotente mais ne présente pas les avantages du code qui donne tout son intérêt à l’IaC:
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é: # debut 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 '
# on peut sauter des lignes dans interrompre la liste ou le dictionnaire en court
- nom: poires
couleur: jaune
pesticide: sans
légumes: #Liste de 3 éléments
- courgettes
- salade
- 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
roles:
- 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 taches 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 taches 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 roles 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 developpement. Cf. cours 3.
name:
qui décrit lors de l’execution la tache en court : un des principes de l’IaC est l’intelligibilité des opérations.Pour valider la syntaxe il est possible d’installer et utiliser ansible-linter
sur les fichiers YAML.
Il est possible d’importer le contenu d’autres fichiers dans un playbook:
import_tasks
: importe une liste de tâches (atomiques)import_playbook
: importe une liste de play contenus dans un playbook.Les deux instructions précédentes désignent un import statique qui est résolu avant l’exécution.
Au contraire, include_tasks
permet d’intégrer une liste de tâche dynamiquement pendant l’exécution
Par exemple:
vars:
apps:
- app1
- app2
- app3
tasks:
- include_tasks: install_app.yml
loop: "{{ apps }}"
Ce code indique à Ansible d’executer une série de tâches pour chaque application de la liste. On pourrait remplacer cette liste par une liste dynamique. Comme le nombre d’import ne peut pas facilement être connu à l’avance on doit utiliser include_tasks
.
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 roles, 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 roles (cf partie sur les roles)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 role.
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 tache est sautée (skipped) durant l’exécution.
loop:
Cette directive permet d’executer une tache 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 tache 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écris dans la documentation: https://docs.ansible.com/ansible/latest/user_guide/playbooks_debugger.html