[Ansible](https://www.ansible.com/) est un outil d'automatisation de configuration et gestion de serveurs : il permet le déploiement de logiciels et l'exécution de tâches via une connexion SSH.
Ansible fonctionne sans agent sur les serveurs (*agent-less*) et selon le concept d'**[idempotence](https://fr.wikipedia.org/wiki/Idempotence)** : on décrit l'état d'un serveur et des actions seront exécutées dans le but de rendre le serveur conforme à cette description. On pourra relancer Ansible plusieurs fois, l'état final reste le même : seules les actions nécessaires seront exécutées.
L'élément de base d'Ansible est le [module](#modules) : on peut exécuter une tâche (installation de paquets, copie de fichiers, etc.) en exécutant simplement `ansible -m mon_module`.
Pour regrouper plusieurs tâches, on utilise un [playbook](#playbook) : un fichier en syntaxe YAML qui va lister une succession de modules avec des arguments.
Au sein d'un playbook, on dispose d'options pratiques comme les [handlers](#handlers) : ils permettent le déclenchement d'une commande sous certaines conditions (redémarrage d'un service par exemple).
Si l'on veut organiser de façon poussée les différentes tâches, on utilisera des [roles](#roles) : il s'agit simplement d'inclure dans un playbook des fichiers YAML respectant une structure conventionnelle.
Enfin, pour s'exécuter sur un ensemble de machines, Ansible a besoin d'un [inventory](#inventory) : c'est la liste des serveurs potentiellement concernés.
Un module est comme une bibliothèque. Il constitue une couche d'abstraction par rapport au shell et commandes sous-jacentes. C'est cette couche qui permet l'idempotence et le fonctionnement sur plusieurs plateformes.
Les modules disposent de certains comportements et fonctionnalités communs : indication de succès/erreurs/changements, gestion des [variables](http://docs.ansible.com/ansible/playbooks_variables.html), des [conditions](http://docs.ansible.com/ansible/playbooks_conditionals.html), des [boucles](http://docs.ansible.com/ansible/playbooks_loops.html), des états de sortie…
Ce module ne permet que l'exécution de commandes simple (pas de pipe…) mais en échange il vérifie les commandes et les assainit pour limiter les injections.
* avec _lineinfile_, si l'argument _regexp_ n'est pas matché… il insère quand même la ligne ! _regexp_ n'est pas une condition pour l'insertion mais pour remplacer au lieu d'insérer !
* avec _lineinfile_, sauf cas tordus, l'argument _regexp_ doit matcher l'argument _line_ (sinon il va insérer la valeur de _line_ à chaque exécution !)
* _lineinfile_ va d'abord évaluer si _regexp_ matche et remplacer la dernière occurrence ; si _regexp_ ne matche pas, il ajoute alors _line_ (sans d'autre condition… même si elle existe déjà)
* _replace_ va remplacer uniquement si _regex_ est matché, comme la commande _sed_
* avec _lineinfile_, si l'on veut utiliser une référence (`\1`) dans _line_, ça donne une erreur, il faut utiliser _replace_
* avec _lineinfile_, l'argument `backrefs: yes` sert à utiliser une référence au sein de l'argument _regexp_ (et non pas au sein de l'argument _line_).
Ce module fait partie d'une courte liste de modules pour lesquels l'utilisation d'une boucle (avec `with_items` par exemple) ne provoque pas l'exécution séquentielle et répétée du module. Dans l'exemple ci-dessus le module utilisera "apt" intelligemment.
> *Note* : c'est pratique pour avoir la documentation exacte pour votre version d'Ansible. En effet, celle du site correspond à la dernière version et n'indique pas toujours toutes les différences.
Options utiles pour [ansible-playbook](https://manpages.debian.org/cgi-bin/man.cgi?query=ansible-playbook&apropos=0&sektion=0&manpath=Debian+unstable+sid&format=html&locale=en) :
*`-vvvv` : très verbeux (utile notamment pour debug SSH quand on a une erreur _unreachable_)
*`-k` / `--ask-pass` : demande le mot de passe pour la connexion SSH
*`-K` / `--ask-become-pass` : demande le mot de passe pour l'escalade (via sudo, su, doas…)
*`-l` / `--limit HOSTNAME` : limite la connexion à un ou plusieurs serveurs (attention, par défaut c'est *all*, cf `/etc/ansible/hosts`)
*`-f` / `--forks N` : nombre de process lancés en parallèle (par défaut 5)… peut être utile de mettre à 1 pour ne pas paralléliser
Les **handlers** sont des actions définies dans un playbook, qui ne sont exécutées que dans certains cas.
On utilise l'option `notify` au sein d'un module pour évoquer un handler. Celui-ci ne sera exécuté que si un module a effectivement provoqué un changement.
L'usage classique est de recharger un service après une modification de configuration : si la modification est réalisée => le service est rechargé, si la modification est déjà effectuée => aucune action.
Par défaut, l'exécution effective des handlers se fait **une seule fois** à la fin du playbook, quel que soit le nombre de fois où il a été demandé pendant l'exécution.
Dans des rôles longs, nous conseillons de purger les handlers de temps en temps (en fin de groupe d'action). En effet, si un playbook est interrompu les handlers ne sont pas forcément exécutés alors que l'action qui les a déclenchés a bien eu lieu. On insère alors l'action suivante :
Lorsqu'on a besoin d'utiliser des fichiers ou _templates_ à copier, des variables avec des valeurs par défaut, des handlers… on peut organiser tout cela dans un **role** en respectant la structure conventionnelle suivante :
La partie **inventory** correspond à la description de l'inventaire des serveurs à configurer et inclus un mécanisme de configuration individuelle et par groupe.
Il permet d'indiquer la liste des machines concernées par Ansible (peut être limité lors de l'exécution de la commande par l'option `-l`) et de pouvoir les ranger dans des groupes.
*`machine[01:57].example.com` : on peut indiquer une [pseudo-]expression régulière - ici ajoutera les machines _machine01.example.com_, _machine02.example.com_, _machine03.example.com_… _machine57.example.com_
*`HOSTNAME:2222` : ansible se connecte par ssh, et _HOSTNAME_ a un port SSH d'écoute différent qui est 2222
*`alias ansible_port=2222 ansible_host=192.0.2.42` : la machine _alias_ n'a pas un vrai FQDN mais pointera vers _192.0.2.42_ car on a indiqué des variables propres à Ansible. Il est existe aussi `ansible_connection` (local ou ssh) ou `ansible_user` (le nom de l'utilisateur de la machine distante avec lequel Ansible se connectera en ssh)
*`host1 http_port=80 maxRequestsPerChild=808` : des variables qui seront automatiquement disponibles pour les actions sur _host1_
*`[commercant:vars]` : des variables qui seront liées au groupe _commercant_.
On peut aussi créer des groupes de groupes en utilisant `:children`
On peut aussi découper le fichier "inventory" selon les groupes et les variables : <http://docs.ansible.com/ansible/intro_inventory.html#splitting-out-host-and-group-specific-data>
Les variables propres à Ansible : <http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters>
Les variables peuvent être définies à de multiples niveaux, chacun ayant une certaine précédence (extrait de la [documentation](http://docs.ansible.com/ansible/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable)) :
Pour gérer de nombreuses variables dans un projet, on peut stocker toutes celles qui correspondent à un groupe de serveur dans un fichier portant le nom du groupe, ainsi que toutes celles d'un serveur en particulier dans un fichier du nom du serveur. Voici l'arborescence conseillée :
`register` est un attribut d'action que l'on peut rajouter pour tout type de tâche et qui initialisera la variable (par le nom donné) avec les valeurs retournées par le module.
Pour `shell`, on a le droit à `.stdout`, `.stderr`, `.rc`… mais cela dépend des valeurs de retour du module.
*`display_args_to_stdout` : mettre à `True` si on veut voir tout le contenu du _tasks_ executé pour chaque étape écrit sur _stdout_
*`display_skipped_hosts` : mettre à `False` si on ne veut pas voir affiché sur _stdout_ l'information d'un _task_ qui n'est pas exécuté _(le nom de variable est confu - mais il s'agit bien de l'affichage du task)_
*`error_on_undefined_vars` : mettre à `True` pour être sûr que le script Ansible s'arrête si une variable n'est pas définie (alors qu'il y a utilisation de cette dernière dans une _task_)
*`retry_files_enabled` : mettre à `True` pour la création de fichier `.retry` après un échec d'Ansible, pour reprendre le travail précédent - ajouté en argument dans l'appel de la commande
Ansible utilise la bibliothèque [Jinja2](http://jinja.pocoo.org/docs/2.10/templates/) pour ses templates, ses filtres, ses conditions…
* fusionner et dédoublonner 2 listes :
~~~
a: [1, 2, 3]
b: [3, 4, 5]
c: a | union(b) | unique
~~~
Il existe plein de [filtres sur les listes](http://docs.ansible.com/ansible/latest/playbooks_filters.html#list-filters) ; `union`, `intersect`, `difference`, `unique`, `sort`…
* liste avec valeur par défaut (variable vide, indéfinie ou liste vide)
~~~
a: []
c: a | default([1, 2], true)
~~~
C'est le second paramètre (`true`) qui permet à `default()` d'agir lorsque la variable `a` n'est pas seulement nulle ou indéfinie, mais aussi en cas de chaîne vide, tableau vide…
Les messages d'erreurs ne sont pas le point fort d'Ansible. Il n'est pas toujours clair de savoir si c'est un soucis de syntaxe YAML, un problème de sémantique d'Ansible ou une erreur dans l'utilisation de Jinja2. De plus, Ansible tente de faire des recommandations, mais elles sont des fois plus déroutantes qu'éclairantes. En voici quelques unes que nous avons rencontrées.
Bien vérifier la syntaxe : cela peut être un guillemet mal fermé (ou mélange simples/doubles guillemets), ou encore histoire de crochet devenant une parenthèse…
Pour éviter que les différentes tâches s'appliquent une par une sur tout les hosts impliqués par l'exécution du playbook, on peut utiliser l'option `strategy` à la valeur `free` pour que chaques tâches sur un host puisse continuer dès la fin de son exécution sans attendre l'état des autres hosts concernés en cours.
> *Note*: ne plus se fier au texte `host changed` après texte de la tâche, car il pourrait s'agir d'une autre tâche affichée plus en haut dans le texte de l'historique.
*`serial` en en-tête contenant une valeur numérique qui représente le nombre de machines pour chaque tour d'éxecution de playbook, ou un pourcentage par rapport à la liste inventory concerné.
S'il manque une valeur pour la suite du script, soit on le gère en mettant une erreur, ou une valeur par défaut, mais sinon on peut aussi demander une saisie clavier :
Si on veut utiliser cette variable dans une tâche, il faut simplement utiliser le nom de la variable, et si on veut l'utiliser (explicitement) pour un play ne se trouvant pas dans le même fichier (donc ici la variable dans autre.yml s'appelera _prenom_de_autre_ et non prenom) :
En indiquant l'argument `creates` indiquant le chemin de fichier lors de l'utilisation du module shell, cette tâche ne s'exécutera que si le fichier indiqué par `creates` n'existe pas.
Le corollaire est possible avec l'argument `removes` qui empêche l'exécution si le fichier n'existe pas.
Si cet attribut est utilisé avec `delegate_to`, alors cette machine sera la seule à exécuter cette tâche. Sinon, c'est la première dans la liste de l'inventaire.
De cette manière, si un fichier portant le nom du serveur en cours existe, il sera utilisé, sinon on cherche un fichier du nom du groupe du serveur et enfin on cherche un fichier par défaut, valable pour tous les serveurs qui n'ont pas de configuration spécifique ou de groupe.
Par défaut, l'utilisateur se connectant sur le serveur distant l'utilisateur UNIX courant. On peut soit le préciser dans le fichier de conf principal d'Ansible avec `remote_user: michu`, dans l'inventaire pour un groupe ou un serveur précis ou encore en l'indiquant en argument lors de l'éxecution du playbook.
Afin de ne pas avoir à taper son mot de passe Vault à chaque utilisation, on peut stocker son mot de passe Vault dans un fichier chiffré par [GPG](HowtoGPG). Au préalable, il faut configurer GPG pour utiliser l'[agent GPG](HowtoGPG#agent-gpg).
* [Documentation officielle](http://docs.ansible.com/ansible/) (voir notamment la partie [Best Practices](http://docs.ansible.com/ansible/playbooks_best_practices.html))
* Vidéos [ansible-best-practices](https://www.ansible.com/ansible-best-practices) et [ansible-tips-and-tricks](https://www.ansible.com/ansible-tips-and-tricks)