31 Sicherstellung der Idempotenz in Playbooks

31.1 Definition und Bedeutung der Idempotenz in Ansible

Idempotenz ist ein zentrales Konzept in Ansible und beschreibt die Fähigkeit eines Playbooks, eine Aufgabe so auszuführen, dass wiederholte Ausführungen dasselbe Ergebnis erzielen, ohne unerwünschte Nebenwirkungen zu verursachen. Mit anderen Worten, ein idempotentes Playbook stellt sicher, dass ein System durch wiederholte Ausführungen in denselben gewünschten Zustand versetzt wird, ohne unnötige Änderungen vorzunehmen.

31.1.1 Warum ist Idempotenz wichtig?

  1. Verlässlichkeit: Idempotente Playbooks garantieren, dass die wiederholte Anwendung desselben Playbooks auf ein Zielsystem keine unnötigen Änderungen oder Fehler verursacht.

  2. Vorhersehbarkeit: Durch Idempotenz können Sie sicherstellen, dass Ihre Playbooks konsistent und wiederholbar sind, unabhängig davon, wie oft sie ausgeführt werden.

  3. Effizienz: Idempotente Tasks verhindern unnötige Änderungen und sparen somit Zeit und Ressourcen, indem sie nur dann eingreifen, wenn tatsächlich eine Änderung erforderlich ist.

31.2 Sicherstellung der Idempotenz bei der Erstellung von Playbooks

Beim Erstellen von Playbooks in Ansible ist es entscheidend, darauf zu achten, dass jede Aufgabe idempotent ist. Dies bedeutet, dass Tasks so gestaltet sein müssen, dass sie den aktuellen Zustand des Zielsystems überprüfen und nur dann eine Änderung vornehmen, wenn dies erforderlich ist.

31.2.1 Verwendung von Ansible-Modulen

Die meisten Ansible-Module sind von Natur aus idempotent. Sie prüfen den aktuellen Zustand des Zielsystems und führen nur dann eine Änderung durch, wenn der gewünschte Zustand nicht bereits erreicht ist.

Beispiel:

- name: Stelle sicher, dass Apache installiert ist
  apt:
    name: apache2
    state: present

Das apt-Modul installiert Apache nur, wenn es noch nicht installiert ist, wodurch es idempotent ist.

31.2.2 Vermeidung von nicht-idempotenten Befehlen

Vermeiden Sie die Verwendung von Modulen oder Befehlen, die nicht idempotent sind, wie z.B. einfache Shell-Befehle, die jedes Mal ausgeführt werden, unabhängig vom aktuellen Zustand des Systems.

Beispiel für einen nicht-idempotenten Task:

- name: Erstelle ein Verzeichnis (nicht idempotent)
  command: mkdir /var/www/html

Dieser Befehl wird jedes Mal ausgeführt, selbst wenn das Verzeichnis bereits existiert, was zu einem Fehler führen kann, wenn das Verzeichnis bereits vorhanden ist.

31.2.3 Idempotenz bei command und shell-Modulen sicherstellen

Wenn Sie die Module command oder shell verwenden müssen, können Sie diese mit den Parametern creates, removes oder changed_when idempotent gestalten:

Beispiel mit creates:

- name: Kompiliere Anwendung nur, wenn Binärdatei nicht vorhanden
  command: make
  args:
    chdir: /opt/project
  creates: /opt/project/bin/myapp

Beispiel mit changed_when:

- name: Überprüfe Systemstatus
  command: systemctl is-active apache2
  register: apache_status
  changed_when: false
  failed_when: apache_status.rc not in [0, 3]

31.2.4 Bedingte Ausführung (when)

Die Verwendung des when-Schlüssels kann helfen, sicherzustellen, dass eine Aufgabe nur dann ausgeführt wird, wenn bestimmte Bedingungen erfüllt sind, wodurch Idempotenz gefördert wird.

Beispiel:

- name: Starte Apache neu, wenn die Konfiguration geändert wurde
  service:
    name: apache2
    state: restarted
  when: config_changed is defined and config_changed

Hier wird Apache nur neu gestartet, wenn die Variable config_changed auf true gesetzt ist, was auf eine vorherige Konfigurationsänderung hinweist.

31.2.5 Überprüfung des Zustands vor der Änderung

Es ist wichtig, den aktuellen Zustand eines Systems zu überprüfen, bevor Änderungen vorgenommen werden, um sicherzustellen, dass die Änderung tatsächlich notwendig ist.

Beispiel:

- name: Überprüfe, ob das Verzeichnis existiert, bevor es erstellt wird
  file:
    path: /var/www/html
    state: directory

Das file-Modul stellt sicher, dass das Verzeichnis nur dann erstellt wird, wenn es noch nicht existiert.

31.3 Gegenüberstellung: Idempotente vs. nicht-idempotente Umsetzung

Die folgende Tabelle zeigt häufige Aufgaben und deren idempotente Implementierung:

Aufgabe Nicht idempotent Idempotent (empfohlen)
Verzeichnis anlegen command: mkdir /var/www/html file: path=/var/www/html state=directory
Datei ändern shell: echo "Listen 8080" >> /etc/apache2/ports.conf lineinfile: oder blockinfile:
Dienst starten shell: systemctl start apache2 service: name=apache2 state=started
Paket installieren shell: apt-get install -y apache2 apt: name=apache2 state=present
Datei herunterladen shell: wget http://example.com/file.tar.gz get_url: url=http://example.com/file.tar.gz dest=/tmp/file.tar.gz
Benutzer erstellen command: useradd myuser user: name=myuser state=present

31.4 Best Practices und häufige Fallstricke

Um die Idempotenz in Playbooks sicherzustellen, sollten einige Best Practices beachtet und typische Fallstricke vermieden werden.

31.4.1 Best Practices:

  1. Verwenden Sie idempotente Module: Setzen Sie auf die in Ansible eingebauten Module, die standardmäßig idempotent sind, wie z.B. apt, yum, file, service, etc.

  2. Überprüfen Sie den Zielzustand: Implementieren Sie immer eine Überprüfung des aktuellen Zustands, bevor Sie eine Änderung vornehmen.

  3. Verwenden Sie creates, removes und changed_when: Wenn Sie Shell- oder Command-Module verwenden müssen, machen Sie diese mit diesen Parametern idempotent.

  4. Dokumentieren Sie die Idempotenz: Stellen Sie sicher, dass die Idempotenz in den Kommentaren und der Dokumentation des Playbooks explizit beschrieben ist.

  5. Testen Sie Idempotenz: Führen Sie Ihre Playbooks mehrmals hintereinander aus, um sicherzustellen, dass sie idempotent sind und keine unerwünschten Nebenwirkungen verursachen.

  6. Nutzen Sie automatisierte Tests: Verwenden Sie Tools wie Molecule für kontinuierliche Tests der Idempotenz.

31.4.2 Häufige Fallstricke:

  1. Fehlende Zustandsüberprüfung: Wenn eine Task ohne Zustandsüberprüfung ausgeführt wird, kann dies zu wiederholten Änderungen führen, die nicht notwendig sind.

  2. Nicht-idempotente Befehle: Verwenden von Befehlen wie command oder shell ohne die notwendige Absicherung führt oft zu nicht-idempotenten Playbooks.

  3. Unvorhersehbare Abhängigkeiten: Unvorhergesehene Abhängigkeiten zwischen Tasks können dazu führen, dass eine Änderung unerwartete Auswirkungen hat, die die Idempotenz verletzen.

  4. Fehlende Bedingte Logik: Wenn when-Bedingungen nicht richtig implementiert sind, kann dies dazu führen, dass Tasks unnötigerweise ausgeführt werden.

  5. Ignorieren von Return-Codes: Nicht-idempotente Scripts können durch falsche Interpretation von Exit-Codes entstehen.

31.5 Automatisierte Tests für Idempotenz

31.5.1 Molecule für Idempotenz-Tests

Molecule ist ein Test-Framework für Ansible, das besonders nützlich ist, um die Idempotenz von Playbooks zu testen. Es kann automatisiert überprüfen, ob ein Playbook beim zweiten Durchlauf keine Änderungen mehr meldet.

Grundlegende Molecule-Konfiguration:

# molecule/default/molecule.yml
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance
    image: ubuntu:20.04
provisioner:
  name: ansible
  inventory:
    host_vars:
      instance:
        ansible_python_interpreter: /usr/bin/python3
verifier:
  name: ansible

Idempotenz-Test in Molecule:

# Führt Playbook aus und testet Idempotenz
molecule test

Molecule führt das Playbook zweimal aus und überprüft, ob beim zweiten Durchlauf changed=0 gemeldet wird.

31.5.2 Manuelle Idempotenz-Tests

Für einfache Tests können Sie Ihr Playbook manuell zweimal ausführen:

# Erstes Mal ausführen
ansible-playbook -i inventory site.yml

# Zweites Mal ausführen - sollte keine Änderungen zeigen
ansible-playbook -i inventory site.yml

Bei idempotenten Playbooks sollte die zweite Ausführung changed=0 für alle Tasks anzeigen.

31.6 Beispiele für die Sicherstellung der Idempotenz

Um die Konzepte der Idempotenz zu verdeutlichen, folgen hier einige Beispiele, wie Idempotenz in Playbooks implementiert werden kann:

31.6.1 Beispiel 1: Idempotente Installation eines Pakets

- name: Stelle sicher, dass Apache installiert ist
  apt:
    name: apache2
    state: present

Dieses Playbook stellt sicher, dass Apache installiert ist, ohne es erneut zu installieren, wenn es bereits vorhanden ist.

31.6.2 Beispiel 2: Idempotente Konfigurationsänderung

- name: Stelle sicher, dass der Port 8080 in der Konfigurationsdatei gesetzt ist
  lineinfile:
    path: /etc/apache2/ports.conf
    regexp: '^Listen'
    line: "Listen 8080"
  notify: restart apache2

Der lineinfile-Task ändert die Konfiguration nur, wenn sie nicht bereits gesetzt ist.

31.6.3 Beispiel 3: Idempotente Verzeichnis-Erstellung

- name: Stelle sicher, dass das Verzeichnis /var/www/html existiert
  file:
    path: /var/www/html
    state: directory
    owner: www-data
    group: www-data
    mode: '0755'

Dieser Task stellt sicher, dass das Verzeichnis existiert, und erstellt es nur, wenn es noch nicht vorhanden ist.

31.6.4 Beispiel 4: Idempotente Datei-Download

- name: Lade Konfigurationsdatei herunter, wenn nicht vorhanden
  get_url:
    url: https://example.com/config.conf
    dest: /etc/myapp/config.conf
    owner: root
    group: root
    mode: '0644'
    force: no  # Überschreibt nicht, wenn Datei bereits existiert

31.6.5 Beispiel 5: Idempotente Service-Konfiguration

- name: Stelle sicher, dass Apache läuft und beim Start aktiviert ist
  service:
    name: apache2
    state: started
    enabled: yes

31.7 Erweiterte Techniken für Idempotenz

31.7.1 Verwendung von Check Mode

Der Check Mode (--check) von Ansible kann verwendet werden, um zu testen, welche Änderungen ein Playbook vornehmen würde, ohne sie tatsächlich auszuführen:

ansible-playbook -i inventory site.yml --check

31.7.2 Register und Changed_when kombinieren

- name: Überprüfe Konfigurationsdatei
  command: grep "Listen 8080" /etc/apache2/ports.conf
  register: port_check
  changed_when: false
  failed_when: false

- name: Ändere Port nur wenn nötig
  lineinfile:
    path: /etc/apache2/ports.conf
    regexp: '^Listen'
    line: "Listen 8080"
  when: port_check.rc != 0
  notify: restart apache2