Outils pour utilisateurs

Outils du site


fr:rapports:2022:09-24

2022/09/24 (atelier) : Ansible

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.

snippet.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:

snippet.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/hostvars/debian1.yml, on met ansible_host: 10.10.10.11 - dans le fichier inventories/hostvars/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:

snippet.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:

snippet.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 !

snippet.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.

snippet.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):

snippet.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.

snippet.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:

snippet.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 ?

snippet.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:

snippet.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:

snippet.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 ansiblemanaged. les doubles accolades montrent que c'est une variables. La variable ansiblemanaged 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:

snippet.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 :

snippet.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…

snippet.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 :

snippet.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 :

snippet.j2
{% if temperature >= 19 %}
blablabla
{{ ma_variable }}
{% else %}
bloubloubloub
{% endif %}

On peut aussi faire des boucles:

snippet.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.

snippet.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:

snippet.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:

snippet.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.

snippet.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 }};:

snippet.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.

snippet.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

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.

fr/rapports/2022/09-24.txt · Dernière modification : 2023/11/06 15:18 de hgo