Aller au contenu principal

Les taches asynchrones sous Ansible

· 4 minutes de lecture
Stéphane ROBERT
Consultant DevOps

Par défaut, Ansible lance les tâches de manière synchrone, en maintenant la connexion au nœud distant ouverte jusqu'à ce que l'action soit terminée. Cela signifie que dans un playbook, chaque tâche bloque la tâche suivante par défaut, ce qui signifie que les tâches suivantes ne s'exécuteront pas tant que la tâche en cours ne sera pas terminée.

Ce comportement peut poser des problèmes. Par exemple, une tâche peut prendre plus de temps que ne le permet les paramètres de la session SSH, provoquant un délai d'attente.

Vous souhaiteriez aussi qu'une tache de longue durée s'exécute en arrière-plan pendant que vous lancez d'autres tâches. C'est dans ces cas que le mode asynchrone va vous permettre de gérer l'exécution des tâches de longue durée.

Lancer des taches unitaires de manière asynchrone

Vous pouvez exécuter des opérations de longue durée en arrière-plan avec la commande ansible. Comme par exemple :

  • Téléchargement d'un gros fichier à partir d'une URL
  • Exécution d'un script connu pour s'exécuter pendant une longue durée
  • Redémarrage du serveur distant et attendre son retour à la vie

Il suffit d'ajouter les paramètres -B timeout définissant le temps maxi d'exécution de la tache et -P val.

Prenons l'exemple d'une mise à jour des packages d'une centos 8:

ansible all -B 3600 -P 0 -a -b "dnf -y update"
builder | CHANGED => {
    "ansible_job_id": "77959305201.6882",
    "changed": true,
    "finished": 0,
    "results_file": "/home/vagrant/.ansible_async/77959305201.6882",
    "started": 1
}

Vous remarquez qu'à la sortie nous obtenons un job id que nous pouvons utiliser par la suite pour connaître le status d'une tache avec le module async_status.

ansible all -b -m async_status -a 'jid=562302021282.7210'
builder | SUCCESS => {
    "ansible_job_id": "562302021282.7210",
    "changed": false,
    "finished": 0,
    "started": 1
}

Si vous avez lancé la tache en changeant d'utilisateur, par exemple avec un become:true -b, il faudra bien demander le status avec le même utilisateur!

Lancer des taches asynchrone dans vos playbooks

Ce mécanisme d'asynchronisme existe également pour les playbooks. Ici le comportement du mode asynchrone sera fonction de la valeur poll. Un exemple :

---
- hosts: all
  become: true
  tasks:

  - name: Simulate long running op (15 sec), wait for up to 45 sec, poll every 5 sec
    ansible.builtin.command: /bin/sleep 15
    async: 45
    poll: 5

Lançons ce playbook :

PLAY [all] *****

TASK [Gathering Facts] *******
ok: [builder]

TASK [Simulate long running op (15 sec), wait for up to 45 sec, poll every 5 sec] ****
ASYNC POLL on builder: jid=254432505432.51116 started=1 finished=0
ASYNC POLL on builder: jid=254432505432.51116 started=1 finished=0
changed: [builder]

PLAY RECAP *****
builder                    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Vous remarquez les interrogations au status du job lancé en arrière plan? Si vous souhaitez lancer des taches en parallèle il suffit de passer 0 au paramètre poll.

Dans ce cas Ansible ne nettoiera pas le cache de travail il faudra alors utiliser le module async_status en lui passant l'option mode: cleanup

---
- hosts: all
  become: true
  tasks:
    - name: Run an async task
      ansible.builtin.yum:
        name: docker-io
        state: present
      async: 1000
      poll: 0
      register: yum_sleeper

    - name: Check on an async task
      async_status:
        jid: "{{ yum_sleeper.ansible_job_id }}"
      register: job_result
      until: job_result.finished
      retries: 100
      delay: 10

On peut également utiliser un compteur d'essai retries qui arrivé à 0 fera échouer la tache. Ici on checke le status de la tache pour la terminer si ell passe à finished en l'enregistrant dans la variable job_result.

Une autre solution, utiliser le module wait_for :

---
  - name: Update server an reboot
    hosts: all
    tasks:
      - name: Update
        become: yes
        become_user: root
        tags: Patch
        shell: "yum -y update"
        register: patchresult

      - name: Reboot the server
        tags: reboot
        become: yes
        become_user: root
        shell: "sleep 5 && reboot"
        async: 1
        poll: 0

      - name: Wait for the reboot and reconnect
        wait_for:
          port: 22
          host: '{{ (ansible_ssh_host|default(ansible_host))|default(inventory_hostname) }}'
          search_regex: OpenSSH
          delay: 10
          timeout: 60
        connection: local

Pour lancer des taches asynchrones encore plus rapidement sur de nombreux serveurs il faudra jouer avec le paramètre --forks ou dans la configuration d'ansible.

Je rappelle que ce billet n'est pas le seul mais un d'une longue série ...