Aller au contenu
Infrastructure as Code medium

Boucles Ansible (loop) : itérer sur listes, dicts et résultats de lookup

75 min de lecture

Logo Ansible

loop: est la directive moderne Ansible (2.5+) pour itérer une tâche sur une liste, un dict ou un résultat de lookup. Elle remplace les anciens with_items:, with_dict:, with_subelements: (page suivante : Boucles legacy with_*). Cette page couvre la syntaxe loop:, l'option loop_control: (label, index_var, pause), la combinaison avec when:, et les patterns courants : itérer sur une liste de dicts, sur un dict, ou sur le résultat d'un lookup('fileglob', ...).

C'est la directive la plus utilisée pour paramétrer un playbook : un seul module qui crée 10 users en bouclant sur une liste, plutôt que 10 tâches dupliquées.

  • La syntaxe loop: <liste> et la variable item
  • loop_control: : label, index_var, pause, extended
  • Itérer sur une liste de dicts : loop: + item.<key>
  • Itérer sur un dict via dict2items
  • Combiner loop: + when: : filtrage par item
  • register: sur une boucle : récupérer la liste des résultats
- name: Installer plusieurs paquets
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop:
- nginx
- redis
- postgresql

À chaque itération, item vaut un élément de la liste. La tâche est exécutée 3 fois.

Le pattern le plus courant en Ansible. Chaque item est un dict, on accède aux clés via item.<key> :

vars:
users:
- { name: alice, shell: /bin/bash, enabled: true }
- { name: bob, shell: /bin/zsh, enabled: false }
- { name: charlie, shell: /bin/bash, enabled: true }
tasks:
- name: Créer les users
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
state: present
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
when: item.enabled

Sortie console :

TASK [Créer les users]
skipping: [db1.lab] => (item=bob)
changed: [db1.lab] => (item=alice)
changed: [db1.lab] => (item=charlie)

Sans loop_control.label:, Ansible afficherait le dict complet à chaque itération, illisible.

OptionEffet
label: "<expr>"Affichage console (au lieu du dict complet)
index_var: idxVariable contenant l'index courant (0-based)
pause: <secondes>Pause entre chaque itération (utile pour API rate-limited)
extended: trueActive ansible_loop.first, ansible_loop.last, ansible_loop.index
- name: Démo loop_control complet
ansible.builtin.debug:
msg: "Item {{ idx }} sur {{ users | length }} : {{ item.name }} (last={{ ansible_loop.last }})"
loop: "{{ users }}"
loop_control:
label: "{{ item.name }}"
index_var: idx
pause: 1
extended: true
vars:
ports:
nginx: 80
redis: 6379
postgresql: 5432
tasks:
- name: Lister les ports
ansible.builtin.debug:
msg: "{{ item.key }} : {{ item.value }}"
loop: "{{ ports | dict2items }}"
loop_control:
label: "{{ item.key }}"

dict2items transforme {a: 1, b: 2} en [{key: a, value: 1}, {key: b, value: 2}]. À l'inverse, items2dict reconstruit un dict.

- name: Distribuer tous les certificats locaux
ansible.builtin.copy:
src: "{{ item }}"
dest: "/etc/ssl/{{ item | basename }}"
mode: "0644"
loop: "{{ query('fileglob', 'files/certs/*.pem') }}"

query('fileglob', ...) retourne une liste de chemins matchant le glob côté control node. Préfère query à lookup quand on itère (query retourne toujours une liste).

when: est évalué à chaque itération :

- name: Installer uniquement les services activés
ansible.builtin.dnf:
name: "{{ item.name }}"
state: present
loop: "{{ services }}"
when: item.enabled

Pour les itérations où when: est faux, la tâche est marquée skipped (pas failed).

Quand vous capturez la sortie d'une tâche dans une boucle, le résultat est une liste :

- name: Vérifier des URLs en boucle
ansible.builtin.uri:
url: "https://{{ item }}/health"
status_code: 200
loop:
- api.example.com
- app.example.com
register: health_results
- name: Afficher tous les status
ansible.builtin.debug:
msg: "{{ item.item }} → {{ item.status }}"
loop: "{{ health_results.results }}"

Le résultat est dans health_results.results (liste), chaque entrée contient l'item original + les champs de la tâche (status, content, etc.).

Voici l'exemple validé sur le lab ecrire-code-boucles-loop :

---
- name: Challenge boucles-loop
hosts: db1.lab
become: true
vars:
challenge_users:
- { name: chal_alice, shell: /bin/bash, enabled: true }
- { name: chal_bob, shell: /bin/zsh, enabled: false }
- { name: chal_charlie, shell: /bin/bash, enabled: true }
tasks:
- name: Créer les users actifs
ansible.builtin.user:
name: "{{ item.name }}"
shell: "{{ item.shell }}"
state: present
loop: "{{ challenge_users }}"
loop_control:
label: "{{ item.name }}"
when: item.enabled
- name: Poser le récapitulatif
ansible.builtin.copy:
dest: /tmp/loop-result.txt
content: "{{ challenge_users | selectattr('enabled') | map(attribute='name') | sort | join(',') }}\n"
mode: "0644"

alice et charlie créés, bob skippé. Le récapitulatif loop-result.txt est généré côté Jinja sans loop (via selectattr | map | sort | join).

SymptômeCauseFix
Loop affiche le dict complet à chaque itérationPas de loop_control: label:Toujours ajouter label: "&#123;&#123; item.name &#125;&#125;"
'dict object' has no attribute 'foo'La clé n'existe pas dans certains itemsitem.foo | default('valeur') ou when: 'foo' in item
Loop trop lentModule qui supporte une liste mais on boucle quand mêmePasser la liste directement au module (name: ['a', 'b'])
register avec loop renvoie un dict pas une listeC'est une liste sous register_var.resultsUtiliser register_var.results, pas register_var directement
loop: ne propage pas les handlersC'est par design (un handler est dédupliqué)Pas de fix
loop: avec un pause: ralentit le playpause: est volontaire (rate limit API)Réduire la valeur ou retirer le pause:
  • loop: <liste> + item = boucle moderne (Ansible 2.5+). with_* est legacy.
  • loop_control: label: "{{ item.name }}" rend la sortie console lisible.
  • Pour itérer sur un dict, utiliser {{ ports | dict2items }} (key/value).
  • register: sur une boucle stocke la liste dans var.results.
  • Si le module supporte une liste (dnf, apt), passer la liste directement plutôt que boucler, plus rapide.
  • when: est évalué à chaque itération : utile pour filtrer par item.

Une boucle se juge sur sa sortie console autant que sur son résultat. Le lab vous fait itérer sur une liste simple puis sur une liste de dicts, rendre l'affichage lisible avec loop_control: label, et parcourir un dictionnaire via dict2items. La validation compte les comptes réellement créés sur la machine, ce qui met en évidence le filtrage par when: appliqué à chaque itération.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn