**Cette page a été importée automatiquement de notre ancien wiki mais n'a pas encore été révisée.**
# Howto Ansible
<http://docs.ansible.com/>
Ansible est une plateforme de configuration et gestion de serveurs : il permet le déploiement de logiciels et l'execution de tâches via une connexion SSH.
Ansible est *agent-less* et utilise le concept d’*[idempotence](https://fr.wikipedia.org/wiki/Idempotence)* : on décrit l'état d'un serveur dans un fichier YAML appelé *playbook* et des actions seront executé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.
Ansible peut exécuter des actions sur des serveurs distants sous :
* *Debian* 6 et supérieur (nécessite uniquement _Python_ ce qui est le cas par défaut)
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 utilisateur (si connexion SSH sans clé)
* *-K / --ask-become-pass* : demande le mot de passe utilisateur pour sudo
* *-l / --limit HOSTNAME* : limite la connexion au serveur HOSTNAME (attention, par défaut c'est *all*, cf `/etc/ansible/hosts`)
* *-f / --forks N* : nombre de process lancé en parallèle (par défaut 5)... peut être utile de mettre à 1 pour ne pas paralléliser
Un playbook est un ensemble de "plays" ; un "play" contient au minimum *hosts* (serveurs cible) et *tasks* (ensemble de tâches), et peut également contenir :
* *vars* : des variables
* *handlers* : des actions déclenchées si une tâche a été exécutée (via l'option `notify`)
* *roles* : des rôles pour avoir des playbooks organisés et modulaires
- name: exemple avec le module MYSQL_USER (nécessite le paquet python_mysqldb)
mysql_user:
name: mysqladmin
password: my_password
priv: "*.*:ALL,GRANT"
state: present
config_file: /root/.my.cnf
update_password: on_create
- name: exemple avec l'ajout de plusieurs lignes dans un fichier
blockinfile:
dest: /etc/apache2/envvars
block: |
## Set umask for writing by Apache user.
## Set rights on files and directories written by Apache
- name: exemple
systcl:
- name: exemple
alternatives:
- name: exemple
service:
- name: exemple
mount:
~~~
#### replace: vs lineinfile:
* lineinfile: si regexp= n'est pas matché... il insère quand même la ligne ! avec lineinfile: regexp= n'est pas une condition pour l'insertion mais une condition pour remplacer au lieu d'insérer !
* avec lineinfile: sauf cas tordus, regexp= doit matcher line= (sinon il va insérer line= à l'infini !)
* lineinfile: va 1. regarder si regexp= existe et il remplace la dernière ligne, 2. si regexp n'existe pas, il ajoute line= (sans d'autre condition... même si elle existe déjà)
* replace: il va remplacer uniquement si regex est matché, logique (comme sed)
* si l'on veut utiliser une référence (\1) dans line# avec lineinfile:> erreur, il faut utiliser replace:
* avec lineinfile: backrefs=yes c'est pour utiliser une référence au sein de regexp= (et non pas au sein de line=)
### vars
Gestion des variables :
~~~
vars:
ip: 31.170.9.129
conf_file: /etc/foo.conf
tasks:
- shell: echo {{ ip }} {{ conf_file }}
~~~
Pour gérer de nombreuses variables dans un projet, voici l'arborescence conseillée :
~~~
group_vars/
group1 # here we assign variables to particular groups
group2 # ""
host_vars/
hostname1 # if systems need specific variables, put them here
hostname2 # ""
~~~
### Handlers
La directive `notify` permet de déclencher un `handler` après la fin d'éxecution d'une tâche :
~~~
tasks:
- apt: ...
notify: Done
handlers:
- name: Done
shell: echo "It's done"
~~~
Le redémarrage d'un service devrait toujours être effectué via handler.
`register`. est un élément/champ que l'on peut rajouter pour tout type de tâche et qui initialisera la variable (par le nom donné) avec les valeurs retour du module.
Pour shell, on a le droit à `.stdout`, `.stderr`, `.rc`, ... Mais cela dépend bien des valeurs de retour du module.
Mais pour être sûr des valeurs initialisées, on peut utiliser `debug` afin d'analyser le résultat.
~~~
- stat: path=/etc/passwd
register: st
- debug: var=st
- fail: msg="Whoops! file ownership has changed"
when: st.stat.pw_name != 'root'
~~~
Pour certains modules, `register` est un passage obligatoire pour une utilisation cohérente des éléments (stat...).
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 regrouper dans des groupes.
host1 <http_port=80> maxRequestsPerChild=808 #des variables qui seront automatiquement auto-completé lié à cet host
[commercant]
mercerie
chapeautier
[commercant:vars]
ntp_server=ntp.mercerie.example.com
proxy=proxy.mercerie.example.com
~~~
* *hostname.internal* : host hors groupe
* *[<httpservers>]* : le nom du groupe (pour les serveurs <http).> Les noms de hosts qui suivent appartiendront à ce groupe
* *machine[01:57].evolix.net* : on peut indiquer une [pseudo-]expression régulière - ici ajoutera les machines _machine01.evolix.net_, _machine02.evolix.net_, _machine03.evolix.net_, ..., _machine57.evolix.net_
* *<http.evolix.net:2222*> : ansible se connecte par ssh, et _<http.evolix.net_> a un port ssh d'écoute différent qui est 2222
* *[dbservers]* : groupe pour les serveurs de base de donnée
* *machine50.example.com* : cette machine est déjà présente dans le groupe _<httpservers_,> mais sera aussi accessible à partir du groupe _dbservers_
* *alias ansible_port=2222 ansible_host=192.168.1.50* : la machine alias n'est pas un vrai FQDN mais pointera vers _192.168.1.50_ car on a indiqué des variables propre à _ansible_. Il est aussi disponible `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 auto-completé lié à host1
* *[commercant:vars]* : des variables qui seront liés 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>]
### ansible.cfg
Options utiles (TODO : à revoir) :
* *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éfini (alors qu'il y a utilisation de cette dernière dans une _task_)
* *force_color* : mettre à `1` pour forcer la couleur
* *forks* : le nombre de processus en parallèle possible lors déploiement du script ansible sur nombreux _hosts_.
* *module_lang* : changer la langue
* *module_name* : le module par défaut (`command`) lors de l'utilisation de la commande `ansible`
* *hosts* : accès vers les _hosts_ par défaut (`all`)
* *private_key_file* : le chemin pour la clé pem
* *remote_port* : le port ssh par défaut (`22`)
* *remote_user* : l'utilisateur pour la connexion ssh par défaut (`root`)
* *retry_files_enabled* : mettre à `True` pour la création de fichier `.retry` après une failure de ansible pour reprendre le travail précédent - ajouté en argument dans l'appel de la commande
## Erreurs
### unbalanced jinja2 block or quotes
~~~
fatal: [test.evolix.net]: FAILED! => {"failed": true, "reason": "error while splitting arguments, either an unbalanced jinja2 block or quotes"}
~~~
Vérifier bien la syntaxe du document qui est référé pour cette erreur. Cela peut être une quote mal fermé (ou mélange simple quote, double quote), ou encore histoire de crochet devenant une parenthèse...
### UNREACHABLE!
~~~
fatal: [test.evolix.net]: UNREACHABLE! => {"changed": false, "msg": "SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue", "unreachable": true}
~~~
Malheureusement, cela arrive souvent sur certaines machines (une?), mais pas de solutions pour le moment -> boire du café - beaucoup de café.
Du côté de la machine distante, dans le fichier `/var/log/auth.log`:
Mais si tentative de déploiement ansible juste après la session supprimé (par ex la 182), c'est à ce moment là qu'on se retrouve avec des *UNREACHABLE* -> savoir pourquoi sshd supprime la session ssh...
Il est de toute façon préférable de ne pas mettre *all* dans le champs *hosts* dans le playbook pour éviter un -_possible_- oublie de "limite" de machines.
### Lancement tâches hosts asynchrone
Pour éviter que les différentes tâches s'appliquent une par une sur tout les hosts impliqués par le déploiement du play ansible, 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.
~~~
- hosts: all
...
strategy: free
~~~
_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é plus en haut dans le texte de l'historique_
### Fréquence des hosts
Lors d'une appel d'un play, on peut indiquer une fréquence sur le nombre d'host touché par l'éxecution du playbook.
*`Fork` pour le nombre de hosts simultanés (changeable dans le fichier _ansible.cfg_ - mettre une valeur importante > centaine).
*`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é.
### Cowsay
~~~
____________________
<NOMOREHOSTSLEFT>
--------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
____________
<PLAYRECAP>
------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
~~~
Pour avoir les messages de la vache : *cowsay*.
Si installer, pour le désactiver : `export ANSIBLE_NOCOWS=1`
Disponible aussi dans la conf du fichier `/etc/ansible/ansible.cfg`
Voir la doc pour plus de features : <http://jinja.pocoo.org/docs/dev/>
### Lire une entrée au clavier
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 valeur a être tapé sur le clavier :
Malheureusement pour le moment, doit se situer avant `tasks`.
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) :
### Ne pas lancer une commande shell si le fichier existe
En initialisant le champs *creates* indiquant le chemin de fichier lors de l'utilisation du module shell, cette tâche là ne s'executera seulement si le fichier (chemint *creates* correspondant) n'existe pas.
Et le contraire (si le fichier existe la tâche se lancera), avec le champs *removes*.
Disponible sur certains module (comme *shell*).
Plus simple et rapide que de tester le fichier par le module *stat* juste avant.
### Lancer tâche sur machine précise (voir local)
~~~
- name: /etc/hosts
shell: cat /etc/hosts
register: tmp
delegate_to: localhost
- debug: var=tmp.stdout
~~~
Pour local, peut être remplacer par la directive `local_action`.
Qui aura pour effet d'appeler le include avec comme argument : item[0]=alice, item[1]=machine1; puis item[0]=alice, item[0]=machine2; ... puis item[0]=bob, item[1]=machine1, ...
Par défaut, l'utilisateur se connectant sur le serveur distant est {{ ansible_user_id }} (càd uid courant UNIX). On peut soit le préciser dans le fichier de conf principal de ansible avec `remote_user : michu` ou en l'indiquant en argument lors de l'éxecution du playbook.
### Éviter que la commande shell indique élement 'changed'
Sur tout les modules, chaque taches retourne un statut sur son résultat :
* Ok : Tout s'est bien passé
* Changed : Tout s'est bien passé mais il y a eu modification par rapport à l'état précédent (droits fichiers,...)
* Failed : Raté
Le soucis est qu'il parrait bien difficile pour le module shell de connaître après execution de la commande s'il y a eu modification. Par défaut, il y a changement - quand bien même l'utilisation du shell se fait pour afficher un résultat (id, date, ...).
* [Documentation officielle](http://docs.ansible.com/ansible/) (voir notamment la partie [Best Practices](http://docs.ansible.com/ansible/playbooks_best_practices.html))
* [Ansible 101 - on a Cluster of Raspberry Pi 2s](https://www.youtube.com/watch?v=ZNB1at8mJWY)
* Sysadmin Casts (épisodes [43](https://sysadmincasts.com/episodes/43-19-minutes-with-ansible-part-1-4), [45](https://sysadmincasts.com/episodes/45-learning-ansible-with-vagrant-part-2-4), [46](https://sysadmincasts.com/episodes/46-configuration-management-with-ansible-part-3-4) et [47](https://sysadmincasts.com/episodes/47-zero-downtime-deployments-with-ansible-part-4-4))