# 2022/09/24 (atelier) : Ansible * [[https://doc.neutrinet.be/atelier-ansible-2022-09-19|Réunion précédente]] * [[https://doc.neutrinet.be/atelier-ansible-2022-09-24#|Pad de la réunion]] Présences : - HgO - Célo ## Météo //Moment informel durant lequel on exprime **en peu de mots** comment on se sent et si on a une attente forte pour la réunion.// //**Ce n'est pas un moment de discussion mais d'expression individuelle et ce n'est pas obligatoire :-)**// ### Attente(s) forte(s) //Si l'une ou l'autre personne exprime une attente forte, merci de vous en occuper en priorité ou de la noter dans le hub ou dans un point approprié//. ## Dans les épisodes précédents On a vu comment créer des VM via Vagrant, comment exécuter des commandes ansible sur ces VMs. Et on a joué avec l'inventaire, notamment on a vu qu'on pouvait grouper des VMs en groupes et sous-groupes, et qu'on pouvait y associer des variables. Les variables pouvant ainsi être séparées de l'inventaire. À la fin, on avait quelque chose comme: ``` .venv/ inventories/ - group_vars/ - all.yml - debian.yml - host_vars/ - debian1.yml - ubuntu1.yml - hosts.ini ansible.cfg Vagrantfile ``` On commence donc par réactiver le virtualenv de python: ``` source .venv/bin/activate ``` Note: Pour quitter un virtualenv, il suffit d'exécuter la commande `deactivate`, c'est plus simple que de quitter bash. Donc normalement, on a un préfixe `(.venv)` avant le prompt bash. Si on n'est pas sûr, on peut vérifier d'où vient pip: `pip --version` ## Création d'un playbook Ansible À présent, on va créer un fichier playbook.yml, qui va exécuter différentes tâches. ### Structure d'un playbook Playbook viendrait du rugby. Quand les joueurs·euses y jouent, ils ont un livret de jeu sur la manière dont l'équipe va se comporter. Ici, c'est pareil, sauf qu'on ne joue pas au rugby. On peut le voir plutôt comme un livret de cuisine. On a une liste de tâche qui s'appliquent à un ou un ensemble d'hôtes. On peut avoir des variables spécifiques à un inventaire mais aussi à un playbook. La structure d'un playbook ressemble à ceci (cf. https://supports.uptime-formation.fr/06-ansible/cours2/#structure-dun-playbook) : Un playbook commence par un tiret. En Yaml, ça indique qu'on a à faire à une liste. Ici c'est une liste de tâches qui seront effectuées sur des hosts. Puis une liste de hosts (ça peut en être un seul host ou un groupe de hosts). En général, on configure notre inventaire pour avoir un seul groupe pour ce playbook. On définit ensuite les variables. On peut avoir des fichiers séparés avec les variables ou les déclarer directement. On peut aussi utiliser les dossiers `group_vars` et `host_vars` qui sont dans le même répertoire que le fichier de playbook. C'est le même principe que l'inventaire. les pré-taches -> s'exécutent avant les autres tâches. Puis on a les rôles et enfin les tâches. Avoir les pré-tâches est utile, par exemple si on veut installer python avant d'appliquer les tâches d'un rôle. Puis les "handlers" sont déclenchés à la fin du playbook. Ca peut par exemple être de recharger un service comme nginx dont on aurait changé la configuration. Ensuite, on peut avoir dans le même fichier, un second playbook (commençant par - name). ### Création d'un petit playbook On créé un fichier `playbook1.yml` dans un dossier `./playbooks`. ```yaml - name: Mon playbook hosts: all tasks: - ping: ``` Quand on ne sait pas comment fonctionne tel module, ou même quel module choisir, on peut se référer à la doc ansible qui est très bien faite. Par ex: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/ping_module.html Chaque module renvoie des valeurs. Dans la doc, cela indique les valeurs par défaut. On va reprendre notre lab vagrant qu'on avait fait, avec quelques modifications. Tout d'abord, on définit la variable suivante pour dire à vagrant d'utiliser le provider libvirt par défaut. : ``` export VAGRANT_DEFAULT_PROVIDER=libvirt ``` On peut persister ceci en rajoutant la commande dans notre `~/.bashrc`. À chaque fois qu'on ouvre un terminal, vagrant saura qu'on veut utiliser libvirt comme provider par défaut. Ensuite, on modifie le Vagrantfile pour avoir ceci: ```vagrant Vagrant.configure("2") do |config| config.ssh.insert_key = false # to use the global unsecure key instead of one insecure key per VM config.vm.define :ubuntu1 do |debian1| debian1.vm.box = "debian/bullseye64" debian1.vm.hostname = "debian1" debian1.vm.network :private_network, ip: "10.10.10.11" end config.vm.define :debian1 do |debian2| debian2.vm.box = "debian/bullseye64" debian2.vm.hostname = "debian2" debian2.vm.network :private_network, ip: "10.10.10.12" end config.vm.synced_folder "./", "/vagrant", disabled: true config.vm.provider "libvirt" do |libvirt| # Enable forwarding of forwarded_port with id 'ssh'. libvirt.forward_ssh_port = true # Customize the amount of memory on the VM: libvirt.memory = "1024" end end ``` On a rajouté une IP fixe à nos deux machines, ce sera plus simple pour la suite de l'atelier. On dit également à vagrant d'utiliser la même clé SSH pour toutes les VMs. Même si ce n'est pas sécurisé, on s'en fiche parce que c'est juste un labo :) On aura deux interfaces réseau sur les machines. La clé se trouve dans `~/.vagrant.d/insecure_private_key`, on peut l'ajouter à notre ssh-agent pour être tranquille. Et enfin, on modifie l'inventaire `inventories/hosts.ini`: ``` [debian] debian1 debian2 ``` On supprime tout ce qui concerne ubuntu. Dans les variables, on indique les IPs : - dans le fichier inventories/host_vars/debian1.yml, on met `ansible_host: 10.10.10.11` - dans le fichier inventories/host_vars/debian2.yml, on met `ansible_host: 10.10.10.12` On peut tester la connexion ssh: ``` ansible -m ping all ``` Si on a des soucis avec le fingerprint de la clé ssh des VMs, on peut rajouter ceci dans `ansible.cfg`: ``` [defaults] ... host_key_checking = false ``` C'est pas sécurisé, mais à nouveau c'est juste un labo... ### Comment exécuter un playbook ? Pour lancer notre playbook : ``` ansible-playbook playbooks/playbook1.yml ``` Différentes tâches s'affichent : ``` PLAY [Mon playbook] ************************************************************************** TASK [Gathering Facts] *********************************************************************** ok: [debian1] ok: [debian2] TASK [ping] ********************************************************************************** ok: [debian1] ok: [debian2] PLAY RECAP *********************************************************************************** debian1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 debian2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Dans la première tâche PLAY, Ansible indique le nom du playbook en cours. Dans la seconde, il indique la liste des tâches, à commencer par la tâche Gathering Facts. Cette tâche n'était pas définie mais a été lancée. Si on ne dit rien à Ansible, il va chercher les facts des VM. Il récupère notamment l'OS de la VM car ça lui sera utile. On peut désactiver ce comportement: ```yaml - name: Mon playbook gather_facts: false ... ``` Dans TASK, il effectue la tâche qu'on a définie. Ici il fait un ping, comme on le lui a demandé. Dans PLAY RECAP, il fait le récapitulatif de ce qui a fait, modifié sur le serveur, et les éventuelles erreurs. ### Comment debugguer son playbook ? On va installer nginx, et puis on débuggera à partir de là. On rajoute cette tâche à la place de notre ping. ``` tasks: - name: Installation de nginx apt: name: nginx state: present ``` Attention à l'indentation ! Il faut faire une indentation de deux entre les "dictionnaires". Le premier tiret est une liste de tâche qui se trouve dans la liste des playbooks. Pour une tâche, on a un premier dictionnaire qui indique le nom de la tâche et le module invoqué. Et on a un sous-dictionnaire qui représente les paramètres du module. Ici, on installe le paquet nginx via le module apt et on demande à ce que le paquet soit présent (au lieu de `absent` ou `latest`) Ici, le lancement du playbook échoue : ``` ansible-playbook playbooks/playbook1.yml PLAY [Mon playbook] ********************************************************************************************************************************************************************************* TASK [Installation de nginx] ************************************************************************************************************************************************************************ fatal: [debian2]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "msg": "No package matching 'nginx' is available"} fatal: [debian1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "msg": "No package matching 'nginx' is available"} PLAY RECAP ****************************************************************************************************************************************************************************************** debian1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 debian2 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 ``` La raison est liée au fait qu'on n'a pas fait d'apt update avant. Il suffit de rajouter le paramètre suivant à la tâche: ```yaml ... state: present update_cache: true ``` Mais cela échoue encore : ``` ansible-playbook playbooks/playbook1.yml PLAY [Mon playbook] ********************************************************************************************************************************************************************************* TASK [Installation de nginx] ************************************************************************************************************************************************************************ fatal: [debian2]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)"} fatal: [debian1]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"}, "changed": false, "msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)"} PLAY RECAP ****************************************************************************************************************************************************************************************** debian1 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 debian2 : ok=0 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0 ``` Pour débugguer, on peut utiliser comme vu la dernière fois l'argument -vvv : ``` The full traceback is: File "/tmp/ansible_apt_payload_mqokgpgd/ansible_apt_payload.zip/ansible/modules/apt.py", line 1336, in main File "/usr/lib/python3/dist-packages/apt/cache.py", line 551, in update with _WrappedLock(apt_pkg.config.find_dir("Dir::State::Lists")): File "/usr/lib/python3/dist-packages/apt/cache.py", line 74, in __enter__ raise LockFailedException(("Failed to lock directory %s: %s") % fatal: [debian1]: FAILED! => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "invocation": { "module_args": { "allow_change_held_packages": false, "allow_downgrade": false, "allow_unauthenticated": false, "autoclean": false, "autoremove": false, "cache_valid_time": 0, "clean": false, "deb": null, "default_release": null, "dpkg_options": "force-confdef,force-confold", "fail_on_autoremove": false, "force": false, "force_apt_get": false, "install_recommends": null, "lock_timeout": 60, "name": "nginx", "only_upgrade": false, "package": [ "nginx" ], "policy_rc_d": null, "purge": false, "state": "present", "update_cache": true, "update_cache_retries": 5, "update_cache_retry_max_delay": 12, "upgrade": null } }, "msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)" } ``` Ici, c'est plus lisible car tout n'est pas regroupé sur une seule ligne. On retrouve notre message mais on a tout le détail de la manière dont le playbook a été appliqué. Une autre façon de debugguer, c'est d'utiliser... la tâche debug ! ```yaml - debug: msg: "hello" ``` On peut faire dire à la tache débug des messages par défaut. Ici, on voit apparaître la tâche debug : ``` TASK [debug] **************************************************************************************************************************************************************************************** task path: /home/celo/Documents/Ateliers-Ansible/Ansible-Test/playbooks/playbook1.yml:6 ok: [debian1] => { "msg": "hello" } ok: [debian2] => { "msg": "hello" } ``` On peut aussi lui dire d'afficher les variables. ```yaml - name: "Installation de nginx" nginx: name: nginx state: present update_cache: true register: nginx_result failed_when: false - debug: var: nginx_result # équivalent à msg: "{{ nginx_result }}" ``` ```register``` et ```failed_when``` sont ici des paramètres de la tâche. Les tâches ont toujours un résultat. Le débug montre bien cela, on affiche le résultat de la commande. On peut mettre ce résultat dans une variable, ce qui est pratique quand on veut vérifier par exemple qu'un fichier existe. Un module d'Ansible fait cela, et on peut stocker ça pour retrouver si le fichier existait ou pas. Dans failed_when: false, on force la tâche à ne pas échouer de manière à pouvoir récupérer quelque chose. Et cette fois, on place la tâche debug après l'installation de nginx, car elle va afficher le résultat du module. Ici, on constate que la tâche Installation de nginx n'a pas échoué (comme on lui a dit que ça n'échouait jamais), mais on retrouve les infos de débuggage dans un format un peu plus digeste que lorsque ça échoue : ``` TASK [Installation de nginx] ************************************************************************************************************************************************************************ ok: [debian2] ok: [debian1] TASK [debug] **************************************************************************************************************************************************************************************** ok: [debian1] => { "nginx_result": { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "failed": false, "failed_when_result": false, "msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)" } } ok: [debian2] => { "nginx_result": { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "failed": false, "failed_when_result": false, "msg": "Failed to lock apt for exclusive operation: Failed to lock directory /var/lib/apt/lists/: E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)" } } ``` ### Comment tester l'exécution d'un playbook ? On peut dire à Ansible de voir si le playbook se lance mais sans faire de changements, cela se fait avec l'option ```--check```. On appelle ça un "dry run". On remet dans notre fichier (on enlève les debug): ```yaml tasks: - name: Installation de nginx apt: name: nginx state: present update_cache: true ``` ### Comment exécuter des commandes en mode superutilisateur ? Dans Ansible, par défaut, on n'est pas root. Il faut lui définir qu'on veut utiliser sudo. On peut le faire avec l'argument --become. On peut aussi ajouter become: true dans le playbook au même niveau que les hosts. ```yaml hosts: all become: true ... ``` On voit que la tâche est passée en changée, donc ça a marché : ``` ansible-playbook playbooks/playbook1.yml --become PLAY [Mon playbook] ********************************************************************************************************************************************************************************* TASK [Installation de nginx] ************************************************************************************************************************************************************************ changed: [debian1] changed: [debian2] PLAY RECAP ****************************************************************************************************************************************************************************************** debian1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 debian2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` Si on relance la tâche, on voit que le playbook va plus vite, et ne change rien :) ### Comment exécuter des commandes en tant qu'un autre utilisateur ? Pour une tâche donnée, on peut rajouter un paramètre pour lui dire d'exécuter la tâche en tant qu'un utilisateur, par ex: ```yaml apt: name: nginx state: present update_cache: true become_user: vagrant ``` Avec ce paramètre, on constate que le playbook échoue à nouveau avec l'erreur ```E:Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied)``` ### Qu'est que l'idempotence ? Concrètement, l'idée est qu'on ne doit pas avoir peur de lancer à nouveau le playbook. L'idée est que ça ne change rien dans le système si la valeur a déjà été changée. Si nginx est déjà installé, il ne le réinstalle pas. Si on a un restart, est-ce que ce sera idempotent ? ```yaml - name: Restart nginx systemd: name: nginx state: restarted ``` On retire aussi le `become_user: vagrant` de tout à l'heure. Ici, lorsqu'on applique le playbook, il redémarre nginx. Lorsqu'on relance nginx, il fait pareil... ce n'est pas idempotent ! Si on a des services web qui tournent sur le serveur, ils sont tous dans les choux. ### Comment redémarrer un service en préservant l'idempotence ? On utilise les handlers. Ceux-si sont utilisés par ex quand on modifie une config. Le handleur n'est déclanché que s'il est notifié que quelque chose a changé dans la config. On ajoute dans les paramètres de la tâche un notify. Les handlers sont exécutés tout à la fin du playbook, lorsque toutes les tâches ont réussi. Attention, s'il y a une tâche qui échoue, par défaut les handlers ne vont pas être exécutés. Il y a des paramètres dans ansible pour modifier ce comportement. On va créer un fichier de config pour nginx, que l'on place dans `./playbooks/files/nginx.conf`: ```nginx server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } } ``` Par défaut, Ansible va chercher les fichiers de configuration dans playbooks/files/. On pourrait utiliser un sous-dossier nginx dans files. On va utiliser le module copy pour mettre le fichier de configuration sur les VMs: ```yaml tasks: ... - name: Configuration de nginx copy: src: nginx.conf dest: /etc/nginx/sites-available/default notify: Restart nginx handlers: - name: Restart nginx systemd: name: nginx state: restarted ``` On peut voir qu'on ne spécifie pas le dossier `files` puisque ansible va chercher directement dans ce dossier. On notifie le handler quand la config a changé. Et on a défini un handler pour redémarrer nginx. Ansible va se baser sur le nom du handler pour savoir lequel déclencher. En éxécutant le playbook avec l'argument --diff, on peut voir ce qu'il change dans le fichier de config. On voit qu'après une première exécution du playbook, le fichier de config est modifié et le handler se déclenche. Si on relance une seconde fois, le fichier n'est plus modifié, et le handler ne se déclenche pas. On a donc un playbook idempotent \o/ ### Comment générer des fichiers de configuration ? Par générer, on entend avoir des variables dans la config. Si par exemple on veut définir le port d'écoute de nginx, on peut l'utiliser dans une variable, et le mettre dans l'inventer. On appelle ça un template... et ça va se trouver dans un dossier playbook/templates. On déplace notre fichier nginx.conf dans ce nouveau dossier. On renomme aussi le fichier en `nginx.conf.j2` En haut du fichier, on rajoute des accolades avec ansible_managed. les doubles accolades montrent que c'est une variables. La variable ansible_managed est une variable d'ansible qui indique que ansible se charge de la gestion de ce fichier et que toute modificaiton sera écrasée au prochain lancement du playbook. Le mot-clé `comment` indique de commenter le résultat de ansible manage pour l'ajouter un commentaire. C'est ce qu'on appelle un filtre dans la syntaxe jinja2. Il permet de manipuler le contenu d'une variable. Ici, ce filtre (ou fonction) va juste modifier le contenu de la variable pour mettre des `#` devant chaque ligne. ``` {{ ansible_managed | comment }} server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } } ``` On modifie notre playbook: ```yaml tasks: ... - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/default.conf notify: Restart nginx ``` On remplace juste le module copy par le module template. Et bien entendu, on renomme la source en `nginx.conf.j2` Si le handler échoue pour une raison ou une autre, il ne va pas ce déclencher à nouveau puisque la config n'est plus modifiée. Pour éviter ça, on va tromper ansible en rajoutant une nouvelle ligne par exemple dans le fichier de config, ce qui va forcer la tâche à être modifiée. Sur la VM, on peut vérifier que le fichier porte bien l'indication qu'il est géré par ansible : ```nginx # # Ansible managed # server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; server_name _; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } } ``` À titre d'exercice, on pourrait s'amuser à définir une variable `domain` et remplacer la ligne `server_name _;` pour que l'utilisateur puisse définir le nom de domaine qu'il veut. Pour le fichier de configuration, on peut choisir différent paramètres : l'utilisateur propriétaire, le groupe, les permissions... ```yaml - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/default owner: root group: root mode: "0644" # équivalent à "u=rw,g=r,o=r" notify: Restart nginx ``` Ici, on met root, c'est pour la démo. Mais c'est de toute façon une bonne pratique d'indiquer root et les permissions. Le linter d'ansible le conseille d'ailleurs. Ceci foncitonne également pour le module copy. ```--diff ```indique aussi les changements appliqués sur les fichiers. On peut aussi demander à Ansible de vérifier le fichier (uniquement sur les template cette fois-ci) avec une commande : ```yaml - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/default owner: root group: root mode: "0644" # équivalent à "u=rw,g=r,o=r" validate: nginx -t -c %s notify: Restart nginx ``` Le %s représente le fichier de destination. Ce paramètre est obligatoire, sinon ansible va râler. Avec cette option, si on a fait une erreur dans le fichier de config, le playbook échoue et on évite de casser le serveur. Avec l'option -vvv, on peut voir le message : ``` "msg": "failed to validate", "stderr": "nginx: [emerg] \"server\" directive is not allowed here in /home/vagrant/.ansible/tmp/ansible-tmp-1664032024.215524-28788-236571956266121/source:5\nnginx: configuration file /home/vagrant/.ansible/tmp/ansible-tmp-1664032024.215524-28788-236571956266121/source test failed\n", "stderr_lines": [ "nginx: [emerg] \"server\" directive is not allowed here in /home/vagrant/.ansible/tmp/ansible-tmp-1664032024.215524-28788-236571956266121/source:5", "nginx: configuration file /home/vagrant/.ansible/tmp/ansible-tmp-1664032024.215524-28788-236571956266121/source test failed" ], ``` ### La syntaxe de jinja2 jinja2 est un moteur de template très utilisé dans python. On a vu la première syntaxe avec les variables (`{{ ma_variable }}`). On peut aussi mettre des conditions : ```j2 {% if temperature >= 19 %} blablabla {{ ma_variable }} {% else %} bloubloubloub {% endif %} ``` On peut aussi faire des boucles: ```j2 {% for temperature in temperatures %} {% if loop.first %}début{% endif %} {{ loop.index }} {{ temperature }} {% if loop.last %}fini{% endif %} {% endfor %} ``` Les variables loop.* sont propres aux boucles. La variable `loop` est un dictionnaire, et on accède à ses éléments via le `.`. On peut aussi faire `loop["index"]`, c'est équivalent. Si on a une variable `temperatures` qui contient `[19, 20, 21]`, cela donnera: ``` début 0 19 1 20 2 21 fini ``` On peut directement remplacer `temperatures` dans la boucle par une liste comme `[19, 20, 21]`. Il y a des filtres qui permettent de manipuler les variables, et on en a une liste ici qui couvre pas mal de cas d'usage : https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html Et aussi la doc de jinja2 pour les filtres : https://jinja.palletsprojects.com/en/3.0.x/templates/#builtin-filters ### Comment créer des tâches conditionnelles ? Une tâche conditionnelle s'applique sous certaines conditions. Dans le cours d'uptime formation, c'est placé au même niveau que les handler. Donc pour avoir un playbook idempotent, on peut utiliser en fait les handler ou les tâches conditionnelles. En général, on utilise le handler, mais dans certains cas, ça peut être utile d'utiliser une tâche conditionnelle. Ca s'utilise avec le paramètre when. On peut par exemple l'utiliser pour choisir le gestionnaire de paquet en fonction de l'OS. Ici, on a utilisé apt pour debian, mais ça ne conviendrait pas pour du OpenSuse ou Redhat. Il y a une liste de variables magiques créées par Ansible pour nous faciliter la vie : https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html#vars-and-facts La variable qui nous intéresse est `ansible_os_family` qui indique si on est plutôt dans la famille Redhat, Debian, OpenSuse, etc. ```yaml - name: Installation de nginx (Debian) apt: name: nginx state: present update_cache: true when: ansible_os_family == 'Debian' - name: Installation de nginx (Redhat) yum: name: nginx state: present when: ansible_os_family == 'RedHat' ``` Ici, il ne faut pas d'accolade parce que when présuppose qu'on utilise des variables. Ici si l'OS n'était pas un Debian, la tâche serait skipée. Pour cela, il faut bien sûr que les Gather Facts soient activés. On voit que la tâche installation de nginx (RedHat), est skippée : ``` TASK [Installation de nginx (RedHat)] *************************************************************************************************************************************************************** skipping: [debian1] skipping: [debian2] ``` ### Des modules génériques Après, l'idée d'Ansible c'est d'être le plus générique possible. On peut se dire qu'on aimerait avoir un truc simple qui fonctionne directement pour les RedHat et les Debian. On peut utiliser le module `package` qui est générique, mais possède moins de fonctionnalités spécifiques à `apt` ou `yum`, mais qui fait le taff la plupart du temps: ```yaml - name: Installation de nginx package: name: nginx state: present update_cache: true ``` Et pareil pour systemd, on peut utiliser le module `service` qui est plus générique: ```yaml - name: Restart nginx service: name: nginx state: restarted ``` Bon, on a une erreur avec le validate, et comme on arrive à la fin de l'atelier, on va juste commenter la ligne. À titre d'exercice, on pourrait créer une tâche qui s'assure que nginx est dans l'état `started` après l'installation. Comme ça, si jamais la config a fait crasher nginx, on le verra au lancement suivant, puisque la tâche apparaîtra comme modifiée. ### Comment créer des boucles ? On peut créer des boucles dans les playbook. Si on a une boucle pour configurer nginx sur différents domaines, on utilise une variable domains qu'on utilise dans loop. Les variables, comme on l'a vu, peuvent être défini à différents endroits. Ici, c'est un exercice, en principe il faut mieux définir ça dans l'inventaire. ```yaml vars: domains: - brocoli.be - patate.be tasks: ... - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/{{ item }} owner: root group: root mode: "0644" # équivalent à "u=rw,g=r,o=r" notify: Restart nginx loop: "{{ domains }}" ``` Par défaut, le nom d'un élément est `item`. Dans la config, on va dire à nginx de restreindre sa config pour un domaine en particulier. Donc, on remplace la ligne `server_name _;` par `server_name {{ item }};`: ```j2 {{ ansible_managed | comment }} server { listen 80 default_server; listen [::]:80 default_server; root /var/www/html; server_name {{ item }}; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; } } ``` Ici, on va voir que la configuration se fait pour les différents domaines : ``` TASK [Configuration de nginx] *********************************************************************************************************************************************************************** changed: [debian2] => (item=brocoli.be) changed: [debian1] => (item=brocoli.be) changed: [debian2] => (item=patate.be) changed: [debian1] => (item=patate.be) ``` Si on a plusieurs variables, `item` n'est pas très clair, on peut le modifier pour avoir `domain` par exemple. On rajoute des paramètres pour la boucle. Ici, le nom de la variable de boucle, qu'on appellera donc `domain`. ```yaml - name: Configuration de nginx template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/{{ domain }} owner: root group: root mode: "0644" # équivalent à "u=rw,g=r,o=r" notify: Restart nginx loop: "{{ domains }}" loop_control: loop_var: domain ``` Tous les paramètres de boucle se trouvent dans la doc : https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html On modifie du coup le fichier de config, pour remplacer `item` par `domain`. ### Bonnes pratiques https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html https://medium.com/polarsquad/ansible-best-practices-part-1-b3391b3c6f68 https://polarsquad.com/blog/ansible-best-practices-part-2 ## Création d'un rôle - Structure d'un rôle - Différences entre module et rôle - Différences entre les variables - Conversion du playbook précédent vers un rôle - Différence entre import et include ## Molecule pip + virtualenv plugin molecule-vagrant - Installation de molecule - Création d'une VM via molecule - Comment se connecter à la VM ? - Destruction de la VM - Comment exécuter son rôle avec molecule ? - Et si la VM n'a pas toutes les dépendances requises pour exécuter le rôle ? ## Secrets - Qu'est-ce qu'un vault ? - Où placer ses secrets ? - Comment afficher le contenu d'un vault ? - Où placer la clé de chiffrement ? - Comment garder la clé de chiffrement secrète dans un repo git ? - Comment ne pas logguer des secrets ? Commande ansible-doc pour avoir la doc. -> équivalent au man page ## Keycloak ## Prochaine réunion Prochain atelier Ansible : 24/10 à 14h Lieu : Chez Célo (Crainhem) ## Météo de fin //Moment informel durant lequel on exprime **en peu de mots** comment, à titre personnel, la réunion a été vécue que ce soit positif ou négatif.// //Si une ou plusieurs **tension est née** durant la réunion, il est peut-être nécessaire d'envisager l'une ou l'autre réunion pour y remédier.// {{tag>infra atelier}}