Mon premier playbook
Objectif de cet atelier pratique
- Rédiger nos premiers playbooks Ansible
Démarrer le labo
Les playbooks sont le point de départ d'un projet Ansible. Dans le cas de figure le plus simple, ils contiennent un ensemble ordonné de tâches (tasks) qui décrivent comment atteindre les objectifs de configuration des Target Hosts. Là encore, c'est un petit peu abstrait, et vous allez mieux comprendre en mettant la main à la pâte.
Placez-vous dans le répertoire du huitième atelier pratique :
Voici les quatre machines virtuelles de cet atelier :| Machine virtuelle | Adresse IP |
|---|---|
ansible |
192.168.56.10 |
rocky |
192.168.56.20 |
debian |
192.168.56.30 |
suse |
192.168.56.40 |
Démarrez les VM :
Connectez-vous au Control Host :
L'environnement de cet atelier est préconfiguré et prêt à l'emploi :
-
Ansible est installé sur le Control Host.
-
Le fichier
/etc/hostsdu Control Host est correctement renseigné. -
L'authentification par clé SSH est établie sur les trois Target Hosts.
-
Le répertoire du projet existe et contient une configuration de base et un inventaire.
-
Direnv est installé sans toutefois être activé pour le projet.
Rendez-vous dans le répertoire du projet :
Hello Ansible !
Ansible n'est pas un langage de programmation au sens strict du terme. En revanche, c'est bien quelque chose avec un vocabulaire et une syntaxe qui nous permet d'automatiser notre travail. Nous allons donc respecter la tradition et rédiger un premier playbook « Hello world ».
Les playbooks sont écrits en YAML avec une extension de fichier .yml ou
.yaml par convention. Créez un fichier hello-ansible.yml à la racine du
projet et éditez-le comme ceci :
Vérifiez la syntaxe de vos fichiers YAML avec yamllint
La syntaxe de YAML est assez pointilleuse pour les espaces et les alignements, et vous pouvez vite vous retrouver à vous arracher les cheveux face à un dysfonctionnement inexplicable.
Pour ma part, j'aime bien me servir de l'outil yamllint, un simple
vérificateur de syntaxe en ligne de commande. Installez-le avec le
gestionnaire de paquets de la distribution et invoquez-le en fournissant
simplement le nom de votre fichier YAML en argument. Si yamllint ne vous
dit rien, c'est que c'est bon.
Organiser les playbooks
Ansible nous laisse pas mal de libertés pour organiser nos projets. En revanche, une série de bonnes pratiques a fini par s'imposer pour éviter que les projets ne finissent en pagaille.
Une de ces pratiques consiste à réunir les playbooks dans un répertoire
playbooks à la racine du projet. Nous allons donc créer ce répertoire pour y
ranger notre premier playbook :
$ mkdir -v playbooks
mkdir: created directory 'playbooks'
$ mv -v hello-ansible.yml playbooks/
renamed 'hello-ansible.yml' -> 'playbooks/hello-ansible.yml'
Ma configuration est à l'ouest
Et c'est là où les choses se compliquent un peu. En effet, tant que vous vous trouvez à la racine du projet, Ansible identifie correctement la configuration et l'inventaire :
$ ansible --version | head -n 2
ansible [core 2.14.14]
config file = /home/vagrant/ansible/projets/ema/ansible.cfg
Or, si vous vous placez dans le répertoire playbooks pour éditer ou lancer
vos playbooks, Ansible est à l'ouest pour la configuration du projet :
$ cd playbooks/
$ pwd
/home/vagrant/ansible/projets/ema/playbooks
$ ansible --version | head -n 2
ansible [core 2.14.14]
config file = /etc/ansible/ansible.cfg
Ici, vous avez grosso modo deux solutions :
-
Vous restez cantonné à la racine du projet pour le gérer. Vous pouvez toujours lancer vos playbooks en utilisant le chemin relatif
playbooks/playbook.yml, mais ce n'est pas très commode. C'est un peu ce qui se passe dans la plupart des tutos que j'ai pu glaner sur le web. -
Vous utilisez
direnvavec la variable d'environnementANSIBLE_CONFIG. C'est un petit investissement qui en vaut la peine, étant donné que vous pouvez naviguer librement dans l'arborescence du projet sans vous soucier de la prise en charge correcte de la configuration.
Rectifier le tir avec direnv
Nous allons opter pour la deuxième solution :
$ cd ..
$ pwd
/home/vagrant/ansible/projets/ema
$ echo 'export ANSIBLE_CONFIG=$(expand_path ansible.cfg)' > .envrc
direnv: error /home/vagrant/ansible/projets/ema/.envrc is blocked.
Run `direnv allow` to approve its content
$ direnv allow
direnv: loading ~/ansible/projets/ema/.envrc
direnv: export +ANSIBLE_CONFIG
À partir de là, la configuration d'Ansible reste valable indépendamment de l'endroit où je me trouve dans l'arborescence du projet :
$ pwd
/home/vagrant/ansible/projets/ema
$ ansible --version | head -n 2
ansible [core 2.14.14]
config file = /home/vagrant/ansible/projets/ema/ansible.cfg
$ cd playbooks/
$ pwd
/home/vagrant/ansible/projets/ema/playbooks
$ ansible --version | head -n 2
ansible [core 2.14.14]
config file = /home/vagrant/ansible/projets/ema/ansible.cfg
Anatomie d'un playbook
Maintenant que ce problème est réglé, regardons un peu le contenu de ce premier playbook :
-
Les trois signes
---sont un marqueur YAML qui indique le début du document. -
De même, les trois signes
...indiquent la fin d'un document YAML. Ils ne sont pas obligatoires, mais j'aime bien faire les choses proprement. -
Le mot-clé
hostsindique les Target Hosts auxquels je veux m'adresser. -
Le mot-clé
tasksnous indique une série de tâches à accomplir pour amener les Target Hosts vers un état souhaité. -
Pour l'instant, l'appel du module
debugconstitue notre seule et unique tâche. -
Le paramètre
msgpermet de définir le message à afficher. -
Du point de vue de YAML, notre playbook est une liste avec un seul élément. Ne vous cassez pas trop la tête là-dessus pour l'instant.
Lancer un playbook
Après toutes ces palabres, nous allons enfin pouvoir lancer notre playbook :
$ ansible-playbook hello-ansible.yml
PLAY [localhost] **********************************************************
TASK [Gathering Facts] ****************************************************
ok: [localhost]
TASK [debug] **************************************************************
ok: [localhost] => {
"msg": "Hello Ansible!"
}
PLAY RECAP ****************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
-
Le résultat est initié par
PLAYet la liste des Target Hosts, en l'occurrencelocalhost. -
Ensuite nous avons la liste des tâches avec une description succincte et le résultat correspondant.
-
Notez que la tâche
Gathering Factsne figure pas dans notre playbook. Le comportement par défaut d'Ansible consiste à commencer un play en rassemblant tout un tas d'informations sur les Target Hosts concernés. -
La dernière ligne nous offre un récapitulatif. Deux tâches ont été effectuées avec succès (
ok=2) sur la ciblelocalhost. Il n'y a pas eu de changement (changed=0). La cible était bien joignable (unreachable=0) et aucune tâche n'a échoué (failed=0).
Bonjour tout le monde !
Maintenant que nous connaissons le fonctionnement de base d'un playbook, nous
allons nous adresser à tous les hôtes de notre labo. Éditez un deuxième
playbook hello-all.yml comme ceci :
Lancez ce playbook :
$ ansible-playbook hello-all.yml
PLAY [all] ****************************************************************
TASK [Gathering Facts] ****************************************************
ok: [debian]
ok: [suse]
ok: [rocky]
TASK [debug] **************************************************************
ok: [rocky] => {
"msg": "Hello Ansible!"
}
ok: [debian] => {
"msg": "Hello Ansible!"
}
ok: [suse] => {
"msg": "Hello Ansible!"
}
PLAY RECAP ****************************************************************
debian : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
rocky : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
suse : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
Simuler une panne
Essayons de voir ce qui se passe lorsqu'un des systèmes cible devient injoignable :
Voilà ce que ça donne :
$ ansible-playbook hello-all.yml
PLAY [all] ****************************************************************
TASK [Gathering Facts] ****************************************************
ok: [debian]
ok: [rocky]
fatal: [suse]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect
to the host via ssh: ssh: connect to host suse port 22: Connection
timed out", "unreachable": true}
TASK [debug] **************************************************************
ok: [rocky] => {
"msg": "Hello Ansible!"
}
ok: [debian] => {
"msg": "Hello Ansible!"
}
PLAY RECAP ****************************************************************
debian : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
rocky : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0
ignored=0
suse : ok=0 changed=0 unreachable=1 failed=0 skipped=0 rescued=0
ignored=0
-
Une fois qu'Ansible s'est rendu compte que l'hôte
susen'était pas joignable, il l'a éliminé du jeu d'emblée. -
Les hôtes ne sont pas gérés dans un ordre particulier, ce qui est dû à la gestion parallèle des tâches dans la configuration par défaut d'Ansible.
-
Le récapitulatif final est plutôt évident.
Relancez l'hôte suse :
Plus ou moins de forks ?
L'inventaire cite les Target Hosts dans un certain ordre :
Dans la configuration par défaut, Ansible lance cinq forks en parallèle pour
accélérer les opérations. Si votre réseau est performant et que vous disposez
d'un nombre important de Target Hosts, vous pouvez augmenter le nombre de
forks grâce au paramètre -f (--forks).
Dans notre environnement labo, nous ne verrons pas vraiment la différence. En revanche, nous pouvons nous amuser à limiter l'exécution à un seul fork, ce qui forcera Ansible à traiter une cible après l'autre :
