Aller au contenu principal

Les taches asynchrones sous Ansible

· 4 minutes de lecture
Stéphane ROBERT

logo ansible

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