28 Playbook-Blöcke und Fehlerbehandlung

28.1 Einführung in Blöcke (block, rescue, always)

Blöcke in Ansible sind eine Möglichkeit, mehrere Aufgaben (Tasks) zu gruppieren und diese als eine logische Einheit zu behandeln. Dies ist besonders nützlich, wenn es darum geht, Fehlerbehandlung und bedingte Ausführungen in Playbooks zu integrieren. Mit Blöcken können Sie sicherstellen, dass bestimmte Aktionen immer ausgeführt werden, unabhängig davon, ob eine vorherige Aufgabe erfolgreich war oder nicht.

28.1.1 Grundlegende Syntax für Blöcke

Ein Block besteht aus einer oder mehreren Aufgaben, die zusammengefasst werden. Zusätzlich können rescue- und always-Sektionen verwendet werden, um Fehler zu behandeln und sicherzustellen, dass bestimmte Aufgaben immer ausgeführt werden.

Beispiel für einen einfachen Block:

---
- name: Beispiel für die Verwendung von Blöcken
  hosts: all
  tasks:
    - name: Block mit mehreren Tasks
      block:
        - name: Installiere Apache
          apt:
            name: apache2
            state: present

        - name: Konfiguriere Apache
          lineinfile:
            path: /etc/apache2/ports.conf
            regexp: '^Listen'
            line: "Listen 8080"
      rescue:
        - name: Fehlerbehandlung, falls ein Fehler auftritt
          debug:
            msg: "Ein Fehler ist aufgetreten und wurde abgefangen."

      always:
        - name: Task, der immer ausgeführt wird
          debug:
            msg: "Dieser Task wird immer ausgeführt, egal ob ein Fehler aufgetreten ist oder nicht."

28.1.2 block

Der block-Schlüssel definiert eine Gruppe von Aufgaben, die als Einheit behandelt werden. Alle Aufgaben in einem Block werden nacheinander ausgeführt, es sei denn, es tritt ein Fehler auf.

28.1.3 rescue

Der rescue-Block wird nur ausgeführt, wenn eine der Aufgaben im block-Abschnitt fehlschlägt. Dies ist nützlich, um alternative Aktionen auszuführen oder Fehler zu protokollieren, bevor das Playbook weiterläuft.

28.1.4 always

Der always-Block wird immer ausgeführt, unabhängig davon, ob eine Aufgabe im block-Abschnitt erfolgreich war oder nicht. Dies ist ideal, um Aufräumarbeiten oder Protokollierungen sicherzustellen.

28.2 Fehlerbehandlung in Playbooks (ignore_errors, failed_when)

Fehlerbehandlung ist ein wichtiger Aspekt bei der Erstellung robuster Playbooks. Ansible bietet verschiedene Mechanismen, um auf Fehler zu reagieren und Playbooks stabil zu halten.

28.2.1 ignore_errors

Mit ignore_errors können Sie Ansible anweisen, einen Fehler in einer bestimmten Aufgabe zu ignorieren und das Playbook fortzusetzen.

Beispiel:

- name: Installiere ein Paket, ignoriere Fehler
  apt:
    name: ein_paket
    state: present
  ignore_errors: yes

In diesem Beispiel wird das Playbook auch dann fortgesetzt, wenn die Installation des Pakets fehlschlägt.

28.2.2 failed_when

Mit failed_when können Sie benutzerdefinierte Bedingungen definieren, die bestimmen, wann eine Aufgabe als fehlgeschlagen betrachtet werden soll. Dies ist nützlich, wenn eine Task erfolgreich sein könnte, aber bestimmte Ergebnisse dennoch als Fehler behandelt werden sollen.

Beispiel:

- name: Überprüfe den freien Speicherplatz
  command: df -h /
  register: disk_space

- name: Überprüfe, ob der Speicherplatz kritisch ist
  fail:
    msg: "Nicht genügend freier Speicherplatz auf der Festplatte."
  when: disk_space.stdout.find('100%') != -1

In diesem Beispiel wird die Playbook-Ausführung gestoppt, wenn der freie Speicherplatz auf der Festplatte 100 % erreicht.

28.2.3 Erweiterte Fehlerbehandlung mit register und failed_when

Die Kombination von register, when und failed_when ermöglicht sehr präzise Fehlerbehandlung basierend auf den tatsächlichen Ergebnissen der ausgeführten Befehle.

Praxisbeispiel - Webserver-Verfügbarkeitsprüfung:

- name: Prüfe Webserver-Verfügbarkeit
  block:
    - name: Versuche Verbindung zum Webserver
      shell: curl -s -o /dev/null -w "%{http_code}" http://localhost
      register: http_result
      failed_when: http_result.stdout != "200"

    - name: Prüfe Antwortzeit des Webservers
      shell: curl -s -o /dev/null -w "%{time_total}" http://localhost
      register: response_time
      failed_when: response_time.stdout | float > 2.0

  rescue:
    - name: Webserver-Problem erkannt
      debug:
        msg: "Webserver antwortet nicht korrekt. HTTP-Code: {{ http_result.stdout | default('unbekannt') }}"

    - name: Versuche Webserver-Neustart
      service:
        name: apache2
        state: restarted

Erweiterte Bedingungen mit mehreren Registervariablen:

- name: System-Gesundheitsprüfung
  shell: "{{ item.command }}"
  register: health_checks
  failed_when: 
    - health_checks.rc != 0
    - "'error' in health_checks.stdout.lower()"
  loop:
    - { command: "systemctl status apache2" }
    - { command: "df -h /" }
    - { command: "free -m" }

28.3 Wiederholungen und bedingte Ausführungen (loop, when)

Ansible bietet Mechanismen, um Aufgaben basierend auf Bedingungen und Schleifen auszuführen. Dies ermöglicht eine flexible und dynamische Steuerung des Playbook-Ablaufs.

28.3.1 loop

loop ermöglicht die Wiederholung einer Aufgabe für eine Liste von Elementen. Dies ist besonders nützlich, wenn Sie dieselbe Aufgabe für mehrere Elemente ausführen müssen, wie z.B. das Installieren mehrerer Pakete oder das Erstellen mehrerer Benutzer.

Beispiel für die Verwendung von loop:

- name: Installiere eine Liste von Paketen
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - apache2
    - mysql-server
    - php

In diesem Beispiel wird die apt-Task für jedes Paket in der Liste ausgeführt.

28.3.2 when

Mit when können Sie bedingte Aufgaben definieren, die nur ausgeführt werden, wenn eine bestimmte Bedingung erfüllt ist. Dies ist nützlich, um Aufgaben dynamisch basierend auf Variablenwerten oder Fakten zu steuern.

Beispiel für die Verwendung von when:

- name: Installiere Apache nur auf Debian-Systemen
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

In diesem Beispiel wird Apache nur installiert, wenn das Betriebssystem ein Debian-Derivat ist.

28.3.3 Kombination von loop und when

Sie können loop und when kombinieren, um Aufgaben basierend auf Bedingungen für jedes Element in einer Liste auszuführen.

Beispiel:

- name: Installiere Pakete auf Debian-Systemen
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - apache2
    - mysql-server
    - php
  when: ansible_os_family == "Debian"

Hier wird jedes Paket in der Liste nur dann installiert, wenn das Betriebssystem des Zielhosts Debian ist.

28.4 Fehlerbehandlungsstrategien und Best Practices

Eine gute Fehlerbehandlung ist entscheidend, um Playbooks robust und zuverlässig zu gestalten. Durch die Implementierung von Fehlerbehandlungsstrategien können Sie sicherstellen, dass Ihr Playbook auch in Ausnahmefällen stabil bleibt und dass Probleme frühzeitig erkannt und behoben werden.

28.4.1 Best Practices:

  1. Verwendung von block, rescue, always: Nutzen Sie Blöcke, um sicherzustellen, dass zusammenhängende Aufgaben als Einheit behandelt werden, und um Fehler gezielt abzufangen und zu behandeln.

  2. Nutzung von failed_when mit register: Definieren Sie klare Bedingungen basierend auf tatsächlichen Kommando-Ergebnissen, unter denen eine Aufgabe als fehlgeschlagen betrachtet wird.

  3. Aufräumarbeiten: Verwenden Sie den always-Block, um sicherzustellen, dass Aufräumarbeiten oder Protokollierungen auch bei Fehlern durchgeführt werden.

  4. Verwendung von ignore_errors mit Vorsicht: Verwenden Sie ignore_errors nur in Situationen, in denen ein Fehler wirklich ignoriert werden kann, ohne den weiteren Ablauf des Playbooks zu gefährden.

  5. Logging und Debugging: Nutzen Sie Debug- und Log-Module, um im Fehlerfall detaillierte Informationen zu sammeln und Probleme leichter diagnostizieren zu können.

  6. Proaktive Statusprüfungen: Verwenden Sie register in Kombination mit Bedingungen, um Systemzustände zu prüfen, bevor kritische Operationen ausgeführt werden.

Beispiel für eine umfassende Fehlerbehandlung:

---
- name: Beispiel für umfassende Fehlerbehandlung
  hosts: all

  tasks:
    - name: Block mit Fehlerbehandlung
      block:
        - name: Prüfe Systemvoraussetzungen
          shell: systemctl is-active network
          register: network_status
          failed_when: network_status.stdout != "active"

        - name: Installiere Apache
          apt:
            name: apache2
            state: present

        - name: Konfiguriere Apache
          lineinfile:
            path: /etc/apache2/ports.conf
            regexp: '^Listen'
            line: "Listen 8080"

        - name: Teste Apache-Konfiguration
          shell: apache2ctl configtest
          register: config_test
          failed_when: config_test.rc != 0

      rescue:
        - name: Logge detaillierte Fehlerinformationen
          debug:
            msg: |
              Ein Fehler ist aufgetreten:
              - Netzwerkstatus: {{ network_status.stdout | default('unbekannt') }}
              - Apache-Konfiguration: {{ config_test.stderr | default('nicht getestet') }}

        - name: Versuche Rollback
          apt:
            name: apache2
            state: absent
          ignore_errors: yes

      always:
        - name: Protokolliere Abschluss und Systemstatus
          debug:
            msg: "Playbook abgeschlossen. Systemzeit: {{ ansible_date_time.iso8601 }}"

28.5 Ausblick: Blöcke und Fehlerbehandlung in Rollen

Die in diesem Kapitel behandelten Konzepte - block, rescue, always, loop und when - sind auch innerhalb von Rollen einsetzbar und ermöglichen dort eine modularisierte, wiederverwendbare Fehlerbehandlung. In Rollen können Sie diese Mechanismen nutzen, um:

Dies wird besonders relevant, wenn Sie komplexe, mehrschichtige Anwendungsdeployments mit Ansible automatisieren möchten.