Schleifen mit Ansible – Tutorial Teil 11

Schleifen ermöglichen in Ansible den gleichen Task auf eine Liste von Elementen komfortabel auszuführen.

Ansible Logo (Quelle: Von Ansible/Red Hat – Wikipedia)

Dateien anlegen

Nehmen wir mal an, wir wollen in einem Play eine Anzahl von Testdateien erzeugen. Dies lässt sich erreichen, in dem wir für jedes Verzeichnis eine ansible.builtin.file Task in das Play hinein schreiben. Das ist allerdings unschön und bläht das Play unnötig auf. Zunächst brauchen wir einen subtask, um das test Verzeichnis anzulegen und die Testdateien aus einem vorhergehenden Lauf zu löschen.Dabei habe ich auch selbst gelernt, dass du nicht mehrere gleiche Tasks in ein Play untereinander schreiben kannst. Ansible nimmt dann immer nur den letzten. Daher spalte ich das in zwei separate Tasks auf.

Datei: subtasks/setuploop.yml

--- 
- name: create test dir
  ansible.builtin.file:
    path: test
    state: directory
- name: delete test files
  ansible.builtin.file:
    path: "test/*"
    state: absent

Das eigentliche Play muss ebenso strukturiert sein. Es soll die vier Dateien

  • test1.json
  • test1.txt
  • test2.txt
  • test3.txt
  • test4.dat

im Testverzeichnis erzeugen. Dies entspricht einem touch Kommando in der bash.

Datei: roles/noloop/tasks/main.yml

- include: ../subtasks/setuploop.yml
- name: erzeuge test1.txt
  ansible.builtin.file:
    path: "test/test1.txt"
    state: touch
- name: erzeuge test1.json
  ansible.builtin.file:
    path: "test/test1.json"
    state: touch
- name: erzeuge test2.txt
  ansible.builtin.file:
    path: "test/test2.txt"
    state: touch
- name: erzeuge test3.txt
  ansible.builtin.file:
    path: "test/test3.txt"
    state: touch
- name: erzeuge testr4.dat
  ansible.builtin.file:
    path: "test/test4.dat"
    state: touch

Der Lauf des Playbook läuft erwartungsgemäß

Ausführung des noloop.yml Playbook
raspithek.de - Olli Graf
running-noloopCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
Ausführung des noloop.yml Playbook

Allerdings wiederholt das Play den gleichen Task mehrfach mit unterschiedlichen Werten. Das ist unschön und bläht das Play nur auf. Eleganter funktioniert das mit einer Schleife

with_items: Schleife

Eine Schleife bekommt in Ansible meist eine Liste übergeben. Für jeden Eintrag in dieser Liste wird der Task, an den die Schleife gebunden ist, wiederholt.

Datei: roles/with_items/tasks/main.yml

- include: ../subtasks/setuploop.yml

- name: erzeuge Testdateien
  ansible.builtin.file:
    path: "test/{{ item }}"
    state: touch
  with_items:
    - test1.txt
    - test1.json
    - test2.txt
    - test3.txt
    - test4.dat

Allein durch die YML Syntax erkennst du schon, der einzige ansible.builtin.file Task in dem Play wird mit einer with_loop: Schleife versehen, die die Liste der Dateien enthält. Die Schleife legt den aktuellen Dateinamen in einer Variable item ab, die nur innerhalb der Schleifenkonstrukts gültig ist.

Ausführung des with_items Play
raspithek.de - Olli Graf
running-with-itemsCreative Commons Attribution-NonCommercial-ShareAlike 4.0 International License . loading=
Ausführung des with_items Play

Dieser Schleifentyp war der erste, den ich kennengelernt habe. Inzwischen gilt er als veraltet und wird möglicherweise irgendwann nicht mehr unterstützt. Ich wollte ihn nur der Vollständigkeit erwähnen.

loop: Schleife

Die modernere und zukunftssichere Schleife ist die loop: Schleife, die mit Ansible Version 2.5 eingeführt wurde. Das Play unterscheidet sich kaum vom Vorgänger.

Datei: roles/loop/tasks/main.yml

- include: ../subtasks/setuploop.yml

- name: erzeuge Testdateien
  ansible.builtin.file:
    path: "test/{{ item }}"
    state: touch
  loop:
    - test1.txt
    - test1.json
    - test2.txt
    - test3.txt
    - test4.dat

Du verwendest einfach loop: statt with_items:, alles andere bleibt gleich.

externe Liste

Unschön ist jetzt nur noch, dass die Dateinamen fest im Play eingebunden sind. Wir können sie aber in eine externe Datei auslagern.

Datei loop_files.yml

testfiles:
    - test1.txt
    - test1.json
    - test2.txt
    - test3.txt
    - test4.dat

Im Playbook lesen wir diese Datei mit Hilfe von vars_files: ein

Datei: loop.yml

- hosts: "{{ target }}"
  vars_files:
    - loop_files.yml
  gather_facts: false
  remote_user: pi
  roles:
    - loop

Im Play können wir jetzt die feste Liste aus der loop: löschen und über die Liste testfiles aus der externen Datei laufen.

Datei: roles/loop/tasks/main.yml

- include: ../subtasks/setuploop.yml

- name: erzeuge Testdateien
  ansible.builtin.file:
    path: "test/{{ item }}"
    state: touch
  loop: "{{ testfiles }}"

with_lines:

Die with_lines: Schleife iteriert über alle Einträge einer Liste, die zeilenweise im with_lines Statement definiert wird. In diesem Beispiel wird ein find Kommando aufgerufen, dass alle *.conf Dateien in /etc sucht. Der Output von find landet dann in der items Variable. Als zweites wird ein ls auf Homeverzeichnis aufgerufen.

Date: roles/with_lines/tasks/main.yml

- name: "Beispiel: Alle .conf Dateien in /etc auflisten"
  debug:
    msg: "Gefundene Konfigurationsdatei: {{ item }}"
  with_lines:
    - "find /etc -name '*.conf' -type f 2>/dev/null | head -5"
    - ls ~

Ausgabe des with_lines: Playbook.
Ausgabe des with_sequence Playbook
Ausgabe des with_sequence Playbook

with_fileglob:

with_fileglob: baut eine Schleife aufgrund einer regular Expression auf, so dass über eine Liste von Dateien gelaufen werden kann. Damit lässt sich das find von oben so schreiben. Die regular expression /etc/*.conf baut eine Liste aller Dateien in /etc, die auf .conf enden auf.

Datei: roles/with_fileglob/tasks/main.yml

- name: "Beispiel: Alle .conf Dateien in /etc auflisten"
  debug:
    msg: "Gefundene Konfigurationsdatei: {{ item }}"
  with_fileglob:
    - /etc/*.conf
Ausgabe des with-fileglob: Playbook
Ausgabe des with-fileglob: Playbook

with_sequence:

Mit with_sequence kannst du eine Schleife mit Zähler bauen. Dazu gibst du Start- start= und Endwert end= als Parameter an. Mit dem Parameter format= wird ein String formatiert, in den der aktuelle Zähler eingesetzt wird.

Datei: roles/with_sequence/tasks/main.yml

- name: "Beispiel: Zählschleife"
  debug:
    msg: "User {{ item }}"
  with_sequence: start=1 end=5 format="user%02d"

Im Beispiel zählt die Schleife von 1 bis 5 und erzeugt einen String „userxx“. Der aktuelle Wert des Zählers wird immer zweistellig %02d angehängt.

with_dict:

Die with_dict: Schleife ist with_lines: sehr ähnlich, mit dem Unterschied, dass jede Zeile einen Dictionaryeintrag enthält über den iteriert wird.

Datei: roles/with_dict/tasks/main.yml

- name: Benutzer mit bestimmten Shells anlegen
  hosts: localhost
  become: yes
  vars:
    users:
      alice: /bin/bash
      bob: /bin/zsh
      carol: /bin/sh

  tasks:
    - name: Benutzer anlegen
      user:
        name: "{{ item.key }}"
        shell: "{{ item.value }}"
      with_dict: "{{ users }}"

In diesem Beispiel wird über ein Dictionary, in dem zu einem Benutzernamen eine Shell als Wert zugeordnet ist. So etwas in der Art findest du auch in der /etc/passwd wieder.

retries:

Nicht selten kommt es vor, dass ein Task warten soll, bis ein bestimmter Zustand eingetreten ist.

Ein häufiger Anwendungsfall ist, zu warten, bis ein Dockercontainer gestartet ist. Dafür steht uns die retries: Schleife zur Verfügung. Mit ihr können wir eine bestimmte Anzahl von Wiederholungen durchlaufen. Das Schlüsselwort until: bestimmt den Zustand, bei dem die Schleifenbedingung erfüllt ist und die Schleife beendet wird.

Datei: roles/retries/tasks/main.yml

- name: Warte bis Container läuft
  docker_container_info:
    name: homeassistant
  register: container_info
  until: container_info.container.State.Status == "running"
  retries: 20
  delay: 3
  # Wartet bis Container vollständig gestartet ist

Dieses Beispiel prüft in maximal 20 Wiederholungen, ob der Container homeassistant läuft. Dabei wird 3 immer 3 Sekunden nach jedem Schleifendurchlauf gewartet(delay: 3). docker_container_info fragt den Zustand des Containers „homeassistant“ ab und legt das Ergebnis in der Variable container_info ab. Diese können wir dann in until: als Bedingung benutzen.

Wichtig bei dieser Schleifenart ist, dass until: eine Bedingung enthält, die auch wirklich eintreten kann. Außerdem solltest du retries: nicht zu hoch ansetzen.

Ich wechsle jetzt auf meinen Homeassistant Raspi, um das Playbook zu testen.In einer Login-Shell teste ich das Playbook, in einer anderen stoppe ich zunächst mal den homeassistant Container.

Durch retries: wartet das Play so lange, bis der Container wieder gestartet ist.
Durch retries: wartet das Play so lange, bis der Container wieder gestartet ist.

Nachdem ich das Playbook gestartet habe, fahre ich den Container wieder hoch. Im Bild siehst du, dass es erst nach drei Versuchen weiterläuft.

loop: oder with_?

Alle Schleifen, die mit with_ beginnen, gelten inzwischen als veraltet und sollten nicht mehr eingesetzt werden. Sie lassen sich aber in der Regel durch eine loop: ersetzen.

So lässt sich das with_fileglob: von oben wie folgt in eine loop: umschreiben.

loop: "{{ query('fileglob', '/etc/*.conf') }}"

Für die anderen Schleifenarten habe ich die loop: Syntax tabellarisch zusammengestellt, ohne sie näher auszuprobieren.

with_ Schleifeloop: SyntaxBemerkung
with_itemsloop: „{{ items }}“1:1 Ersetzung
with_listloop: „{{ mylist }}“mehr oder weniger identisch zu with_items
with_dictloop: „{{ mydict }}“
with_nestedloop: „{{ mylist1 }}“
with_indexed_itemsloop: „{{ items }}““
with_togetherloop: „{{ list1 }}“
with_sequenceloop: „{{ query(’sequence‘, ’start=0 end=4 stride=1′) }}“Nutzt Lookup-Plugin sequence
with_fileglobloop: „{{ query(‚fileglob‘, ‚/etc/*.conf‘) }}“
with_linesloop: „{{ lookup(‚pipe‘, ‚command‘, wantlist=True) }}“Nutzt Lookup-Plugin pipe
with_filesloop: „{{ lookup(‚file‘, ‚myfile.txt‘) }}“Für Inhalte einzelner Dateien
with_random_choiceloop: „{{ [1,2,3] }}“

Fazit

Mit Schleifen kannst du wiederkehrende Tasks in Ansible einfach wiederholen, ohne zusätzlichen ohne schreiben zu müssen. Das hält das Play schlank und übersichtlich.

Bei neuen Playbooks solltest du aber auf with_* Schleifen verzichten und besser auf die moderne loop: Variante zurückgreifen.

Ich habe in diesem Post die Playbooks weggelassen, da sie eh immer nur die eine jeweilige Rolle des Play besitzen. Du findest sie aber im Git-Repo.

Schreibe einen Kommentar

Insert math as
Block
Inline
Additional settings
Formula color
Text color
#333333
Type math using LaTeX
Preview
\({}\)
Nothing to preview
Insert
Creative Commons License
Except where otherwise noted, the content on this site is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Olli Graf - raspithek.de
WordPress Cookie Hinweis von Real Cookie Banner