Beherrschung von mehrstufigen Bereitstellungen mit sequenziellen Ansible-Playbooks

Erfahren Sie, wie Sie komplexe, mehrstufige Anwendungsbereitstellungen mit Ansible entwerfen und ausführen. Dieser Leitfaden behandelt die Erstellung sequenzieller Playbooks für verschiedene Bereitstellungsphasen, die Implementierung effektiver Fehlerbehandlung und die Entwicklung von Rollback-Strategien. Meistern Sie die Kunst der robusten, automatisierten Anwendungsbereitstellung mit praktischen Beispielen und Best Practices.

Beherrschung von mehrstufigen Bereitstellungen mit sequenziellen Ansible-Playbooks

Mehrstufige Ansible-Bereitstellungen werden notwendig, wenn "Dateien kopieren und Dienst neu starten" nicht mehr ausreicht. Eine echte Bereitstellung kann eine Datenbankmigration, eine Feature-Flag-Änderung, ein Paket-Rollout, einen Dienstneustart, einen Health Check und einen Rollback-Pfad erfordern, falls die neue Version fehlschlägt. Wenn all dies in einem großen Playbook mit unklaren Grenzen lebt, wird jeder fehlgeschlagene Deploy zu einer Leseübung.

Sequenzielle Playbooks geben jeder Phase eine klare Aufgabe. Sie können sie aus einer CI/CD-Pipeline, AWX, Ansible Automation Platform oder einem einfachen Shell-Skript ausführen. Der wichtige Teil ist nicht das Tool, das den Knopf drückt. Der wichtige Teil ist, dass die Bereitstellung eine Reihenfolge hat, jede Phase sicher wiederholt werden kann und die Fehlerbehandlung explizit ist.

Warum sequenzielle Playbooks für mehrstufige Bereitstellungen?

Die Bereitstellung einer Anwendung umfasst oft mehr als nur das Kopieren von Dateien. Möglicherweise müssen Sie:

  • Die Umgebung vorbereiten: Verzeichnisse erstellen, Berechtigungen setzen, Abhängigkeiten installieren.
  • Die Datenbank aktualisieren: Schema-Migrationen durchführen, Anfangsdaten einspielen.
  • Anwendungscode bereitstellen: Neue Code-Versionen übertragen, Dienste neu starten.
  • Dienste konfigurieren: Anwendungskonfigurationen aktualisieren, Daemons neu laden.
  • Post-Deployment-Prüfungen durchführen: Smoke-Tests ausführen, Dienstverfügbarkeit überprüfen.

Die Reihenfolge ist wichtig, weil einige Operationen leicht rückgängig zu machen sind und andere nicht. Das Zurücksetzen eines Symlinks auf die vorherige Version ist normalerweise einfach. Das Rückgängigmachen einer destruktiven Datenbankmigration ist möglicherweise nicht möglich. Dieser Unterschied sollte den Bereitstellungsplan formen, bevor jemand YAML schreibt.

Die Aufteilung in verschiedene, sequenzielle Playbooks bietet mehrere Vorteile:

  • Modularität: Jedes Playbook konzentriert sich auf eine einzelne Phase, was sie leichter verständlich, wartbar und wiederverwendbar macht.
  • Lesbarkeit: Komplexe Logik wird in überschaubare Teile aufgeteilt.
  • Kontrolle: Sie können bestimmte Phasen unabhängig oder als Teil eines größeren Workflows ausführen.
  • Fehlerisolierung: Wenn ein Fehler in einer Phase auftritt, ist es einfacher, die Ursache zu identifizieren und bestimmte Änderungen rückgängig zu machen, ohne andere Teile der Bereitstellung zu beeinträchtigen.
  • Idempotenz: Gut geschriebene Playbooks sind von Natur aus idempotent, was bedeutet, dass das mehrmalige Ausführen denselben Effekt hat wie das einmalige Ausführen. Dies ist entscheidend für sichere Wiederholungen.

Es gibt einen Kompromiss. Separate Playbooks erhöhen den Orchestrierungsaufwand. Variablen, Artefakte und Status müssen möglicherweise von einer Phase zur nächsten verschoben werden. Für einen kleinen internen Dienst kann ein Playbook mit getaggten Blöcken ausreichen. Für eine kundenorientierte Anwendung mit Migrationen und Rollback-Anforderungen zahlt sich die zusätzliche Struktur normalerweise aus.

Entwerfen Ihres mehrstufigen Bereitstellungsworkflows

Bevor Sie Ansible-Code schreiben, planen Sie Ihre Bereitstellungsphasen. Identifizieren Sie die logischen Schritte, ihre Abhängigkeiten und die Ausführungsreihenfolge. Ein typischer Workflow könnte wie folgt aussehen:

  1. Pre-Deployment-Prüfungen: Stellen Sie sicher, dass die Zielumgebung bereit ist.
  2. Datenbankmigration: Wenden Sie notwendige Datenbankschema-Änderungen an.
  3. Anwendungsbereitstellung: Stellen Sie die neue Version des Anwendungscodes bereit.
  4. Dienstneustart/-neuladung: Bringen Sie die Anwendungsdienste mit dem neuen Code online.
  5. Post-Deployment-Verifizierung: Führen Sie Tests durch, um den Erfolg der Bereitstellung zu bestätigen.

Überlegen Sie für jede Phase, welche Ansible-Aufgaben erforderlich sind und welches Playbook sie enthalten wird.

Entscheiden Sie auch, welche Phasen den Produktionszustand ändern dürfen. Ein Smoke-Test-Playbook sollte nicht leise die Konfiguration reparieren. Ein Preflight-Playbook sollte keine fehlenden Pakete installieren, es sei denn, dies ist ausdrücklich Teil des Bereitstellungsvertrags. Die Trennung von schreibgeschützten Prüfungen von mutierenden Schritten macht den Workflow vertrauenswürdiger.

Hier ist ein praktisches Verzeichnislayout:

deploy/
  inventories/
    staging.ini
    production.ini
  group_vars/
    all.yml
    production.yml
  playbooks/
    00-preflight.yml
    01-migrate-db.yml
    02-deploy-app.yml
    03-reload-services.yml
    04-smoke-test.yml
    rollback-app.yml

Die Zahlen sind nicht magisch. Sie machen die Reihenfolge in Dateilisten und CI-Protokollen sichtbar.

Ausführen von Playbooks sequenziell

Ansible bietet eine unkomplizierte Möglichkeit, Playbooks nacheinander mit den Befehlen --playbook-dir und ansible-playbook auszuführen. Die einfachste Methode ist, Befehle in Ihrer CI/CD-Pipeline oder auf der Kommandozeile zu verketten.

Nehmen wir an, Sie haben die folgenden Playbook-Dateien:

  • 01-database-migration.yml
  • 02-deploy-application.yml
  • 03-restart-services.yml
  • 04-smoke-tests.yml

Sie können sie wie folgt sequenziell ausführen:

ansible-playbook -i inventory.ini 01-database-migration.yml
ansible-playbook -i inventory.ini 02-deploy-application.yml
ansible-playbook -i inventory.ini 03-restart-services.yml
ansible-playbook -i inventory.ini 04-smoke-tests.yml

In der Praxis umschließen Sie diese Sequenz so, dass eine fehlgeschlagene Phase die Pipeline stoppt:

set -euo pipefail

ansible-playbook -i inventories/production.ini playbooks/00-preflight.yml
ansible-playbook -i inventories/production.ini playbooks/01-migrate-db.yml
ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml
ansible-playbook -i inventories/production.ini playbooks/03-reload-services.yml
ansible-playbook -i inventories/production.ini playbooks/04-smoke-test.yml

set -e ist für sich genommen keine Bereitstellungsstrategie, verhindert aber den schlimmsten Fehler: das Fortfahren nach einer fehlgeschlagenen Phase, als ob nichts passiert wäre. CI-Systeme bieten normalerweise ihr eigenes Fehlerverhalten, aber dieselbe Idee gilt.

Verwenden von ansible-playbook --skip-tags oder --limit

In fortgeschritteneren Szenarien könnten Sie mehrere logische Schritte in einem einzigen Playbook kombinieren, aber Tags verwenden, um die Ausführung zu steuern. Für eine echte mehrstufige Trennung werden jedoch in der Regel separate Playbooks bevorzugt. Wenn Sie eine Teilmenge von Playbooks ausführen oder bestimmte überspringen möchten, können Sie Befehlszeilenargumente verwenden.

Überspringen eines Playbooks: Wenn 03-restart-services.yml aufgrund eines vorübergehenden Dienstproblems fehlschlägt, könnten Sie nach der Behebung der Ursache nur diese Phase erneut ausführen. Überspringen Sie Phasen nicht blind, wenn frühere Phasen Artefakte oder Zustände erzeugen, von denen spätere Phasen abhängen.

Einschränken auf eine bestimmte Phase: Sie können die Ausführung auch mit dem Flag --limit auf einen bestimmten Host oder eine bestimmte Gruppe beschränken, was für Tests nützlich sein kann.

Für Rolling Deployments kann --limit auch den Schaden begrenzen:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml --limit web_canary

Führen Sie die Bereitstellung gegen einen Host oder eine kleine Gruppe durch, verifizieren Sie sie und fahren Sie dann mit dem Rest der Gruppe fort. Dies ist besonders nützlich, wenn Ihr Load Balancer das Draining von Hosts vor dem Neuladen oder Neustarten unterstützt.

Einbeziehen von Fehlerbehandlung und Rollback-Strategien

Robuste Bereitstellungen erfordern einen Plan für den Fall, dass etwas schief geht.

ignore_errors und failed_when

Standardmäßig stoppt Ansible die Ausführung, wenn eine Aufgabe fehlschlägt. Sie können dieses Verhalten steuern:

  • ignore_errors: true: Erlaubt dem Playbook, auch dann fortzufahren, wenn eine Aufgabe fehlschlägt. Verwenden Sie dies mit Vorsicht, typischerweise für nicht kritische Aufgaben oder wenn Sie eine nachfolgende Aufgabe zur Bereinigung oder Kompensation haben.
  • failed_when:: Definieren Sie benutzerdefinierte Bedingungen, unter denen eine Aufgabe als fehlgeschlagen betrachtet werden soll. Dies ist leistungsstark für die Behandlung erwarteter nicht tödlicher Fehler oder die Validierung bestimmter Ergebnisse.
- name: Dienststatus prüfen (potenziell nicht tödlich)
  command: systemctl status myapp
  register: service_status
  ignore_errors: true

- name: Fehlschlagen, wenn Dienst nicht aktiv ist
  fail:
    msg: "Dienst myapp läuft nicht!"
  when: "service_status.rc != 0"

Verwenden Sie ignore_errors sparsam. Es ist oft besser, das Ergebnis zu registrieren und eine klare Entscheidung zu treffen. Ein Bereitstellungsprotokoll voller ignorierter Fehler lehrt die Leute, das Lesen von Fehlern einzustellen.

Bevorzugen Sie für Befehle zweckgebundene Module, wenn sie existieren. Verwenden Sie beispielsweise ansible.builtin.service, ansible.builtin.systemd, ansible.builtin.copy, ansible.builtin.template und Paketmodule anstelle von Shell-Befehlen. Module bieten in der Regel eine bessere Idempotenz und klarere geänderte und fehlgeschlagene Zustände.

Rollback-Playbooks

Für kritische Bereitstellungen sollten Sie dedizierte Rollback-Playbooks haben. Diese Playbooks sollten so konzipiert sein, dass sie die Änderungen rückgängig machen, die von ihren entsprechenden Bereitstellungs-Playbooks vorgenommen wurden.

  • 01-database-migration-rollback.yml: Macht Schemaänderungen rückgängig.
  • 02-deploy-application-rollback.yml: Stellt die vorherige Anwendungsversion bereit oder stellt ein Backup wieder her.
  • 03-restart-services-rollback.yml: Startet Dienste in ihrem vorherigen Zustand neu.

Datenbank-Rollback verdient besondere Aufmerksamkeit. Einige Migrationen können nicht sicher rückgängig gemacht werden, nachdem Schreibvorgänge mit dem neuen Schema begonnen haben. Ein sichereres Muster ist oft Expand-and-Contract: Fügen Sie abwärtskompatible Schemaänderungen hinzu, stellen Sie Anwendungscode bereit, der sowohl mit alten als auch mit neuen Formen arbeiten kann, füllen Sie Daten bei Bedarf nach und entfernen Sie dann alte Spalten oder Felder in einer späteren Bereitstellung.

Mit diesem Modell bedeutet Rollback normalerweise, den Anwendungscode zurückzusetzen und das kompatible Schema zu belassen, anstatt zu versuchen, eine riskante Datenbankänderung unter Druck rückgängig zu machen.

Beispiel für Rollback-Trigger: Wenn in Ihrer CI/CD-Pipeline das Playbook 04-smoke-tests.yml fehlschlägt, würden Sie die Ausführung der Rollback-Playbooks in umgekehrter Reihenfolge auslösen.

# Wenn 04-smoke-tests.yml fehlschlägt:
ansible-playbook -i inventory.ini 03-restart-services-rollback.yml
ansible-playbook -i inventory.ini 02-deploy-application-rollback.yml
ansible-playbook -i inventory.ini 01-database-migration-rollback.yml

Verwenden von block, rescue und always

Die Ansible-Konstrukte block, rescue und always bieten eine strukturiertere Möglichkeit, Fehler innerhalb eines einzelnen Playbooks zu behandeln. Obwohl sie nicht für die Sequenzierung über Playbooks hinweg gedacht sind, eignen sie sich hervorragend, um eine Reihe von Aufgaben zu kapseln, die fehlschlagen könnten, und zu definieren, was im Fehlerfall zu tun ist.

- block:
    - name: Neuen Anwendungscode bereitstellen
      copy:
        src: /path/to/new/app/
        dest: /var/www/myapp/

    - name: Anwendungsdienst neu starten
      service:
        name: myapp
        state: restarted

  rescue:
    - name: Versuch, zur vorherigen Version zurückzukehren
      copy:
        src: /path/to/old/app/
        dest: /var/www/myapp/

    - name: Anwendungsdienst nach Rollback neu starten
      service:
        name: myapp
        state: restarted

  always:
    - name: Bereitstellungsversuch protokollieren
      debug:
        msg: "Bereitstellungsversuch abgeschlossen."

Dieser Ansatz ist nützlich, um verwandte Aufgaben innerhalb eines einzelnen Bereitstellungsphasen-Playbooks zu gruppieren.

Für ein Playbook-übergreifendes Rollback sollte der Orchestrator die Entscheidung treffen. Eine CI-Pipeline kann Rollback-Playbooks nur dann ausführen, wenn eine spätere Phase fehlschlägt. AWX-Job-Workflows können dieselben Erfolgs- und Fehlerzweige visuell modellieren. Halten Sie den Rollback-Befehl langweilig und geübt.

Übergeben des Release-Status zwischen Phasen

Sequenzielle Playbooks benötigen oft eine gemeinsame Release-Kennung. Beispielsweise muss die Bereitstellungsphase wissen, welches Artefakt installiert werden soll, der Smoke-Test muss wissen, welche Version zu erwarten ist, und das Rollback muss die vorherige Version kennen.

Übergeben Sie diesen Status explizit:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml \
  -e release_version=2026.05.24.3 \
  -e artifact_url=https://artifacts.example.com/myapp/2026.05.24.3.tar.gz

Innerhalb des Playbooks protokollieren Sie, was geändert wurde:

- name: Aktuellen Release-Marker schreiben
  ansible.builtin.copy:
    dest: /opt/myapp/current-release.txt
    content: "{{ release_version }}\n"
    owner: root
    group: root
    mode: "0644"

Dieser Marker hilft bei Vorfällen. Wenn jemand per SSH auf einen Host zugreift, kann er sehen, welche Version der Host zu laufen glaubt. Sie können auch das Smoke-Test-Playbook so einrichten, dass es den Marker liest und mit dem erwarteten Release vergleicht.

Fortgeschrittene Überlegungen

Verwalten des Status zwischen Playbooks

Manchmal muss eine Aufgabe in einem Playbook ein anderes Playbook über ihr Ergebnis informieren. Sie können dies erreichen mit:

  • Fact Caching: Wenn Fact Caching aktiviert ist, können Fakten, die von einem Playbook gesammelt wurden, für nachfolgende Playbooks verfügbar sein, die innerhalb derselben Ansible-Sitzung ausgeführt werden.
  • Temporäre Dateien/Datenbanken: Schreiben Sie kritische Statusinformationen oder Ausgaben in eine temporäre Datei oder eine dedizierte Statustabelle, die nachfolgende Playbooks lesen können.

Bevorzugen Sie expliziten Status gegenüber verstecktem Status. Fact Caching kann nützlich sein, aber es kann auch verwirren, wenn Werte veraltet sind oder wenn ein Runner Caching aktiviert hat und ein anderer nicht. Release-Dateien, Artefakt-Metadaten, CI-Variablen und Bereitstellungsaufzeichnungen sind einfacher zu überprüfen.

Versionskontrolle und Orchestrierungstools

Für komplexe Orchestrierungen sollten Sie in Betracht ziehen, Ihre sequenziellen Ansible-Playbooks in ein übergeordnetes Tool zu integrieren:

  • CI/CD-Pipelines: Tools wie Jenkins, GitLab CI, GitHub Actions oder CircleCI eignen sich hervorragend zum Definieren und Auslösen mehrstufiger Bereitstellungen. Sie definieren die Sequenz der ansible-playbook-Befehle innerhalb der Pipeline-Konfiguration.
  • Ansible Tower/AWX: Für unternehmensweite Orchestrierung bietet Ansible Tower (jetzt Automation Platform) oder sein Open-Source-Pendant AWX eine robuste Benutzeroberfläche zum Planen, Überwachen und Verwalten komplexer Jobvorlagen, die mehrere Playbooks verketten können.

Wenn mehrere Personen dasselbe System bereitstellen, wird die zentrale Orchestrierung weniger eine Frage der Bequemlichkeit als vielmehr der Kontrolle. Sie bietet konsistente Inventare, Anmeldeinformationen, Audit-Logs, Genehmigungen und eine sichtbare Historie, welche Phase fehlgeschlagen ist. Diese Details sind während eines Produktionsvorfalls wichtig.

Tagging für granulare Kontrolle

Obwohl wir uns für separate Playbooks für verschiedene Phasen aussprechen, können Sie auch Tags innerhalb von Playbooks verwenden. Wenn Sie ein sehr großes Playbook für eine einzelne Phase haben (z. B. Datenbankmigration), können Sie bestimmte Aufgaben taggen und nur diese mit ansible-playbook --tags <tag_name> ausführen.

Dies dient eher der granularen Kontrolle innerhalb einer Phase als der Sequenzierung zwischen Phasen.

Best Practices für mehrstufige Bereitstellungen

  • Playbooks fokussiert halten: Jedes Playbook sollte eine Sache gut machen (z. B. Datenbankmigration, Anwendungsbereitstellung).
  • Playbooks klar benennen: Verwenden Sie eine Namenskonvention, die die Phase und Reihenfolge widerspiegelt (z. B. 01-, 02-).
  • Idempotenz implementieren: Stellen Sie sicher, dass alle Aufgaben idempotent sind, um sichere Wiederholungen zu ermöglichen.
  • Rollbacks testen: Testen Sie regelmäßig Ihre Rollback-Verfahren, um sicherzustellen, dass sie wie erwartet funktionieren.
  • Versionskontrolle verwenden: Speichern Sie alle Ihre Playbooks und Inventardateien in einem Versionskontrollsystem (wie Git).
  • Orchestrierung automatisieren: Verwenden Sie CI/CD-Pipelines oder Tools wie Ansible Tower/AWX, um die Ausführung Ihrer sequenziellen Playbooks zu automatisieren.
  • Dokumentieren Sie Ihren Workflow: Dokumentieren Sie klar die Phasen, ihren Zweck, Abhängigkeiten und Rollback-Verfahren.
  • Smoke-Tests realistisch gestalten: Überprüfen Sie den tatsächlichen Endpunkt, den Login-Pfad, den Queue-Worker oder den Hintergrundjob, der wichtig ist. Eine einfache Prozessprüfung reicht nicht aus.
  • Produktionsinventare schützen: Verwenden Sie separate Inventare und Anmeldeinformationen für Staging und Produktion. Ein Tippfehler in --limit sollte nicht am falschen Ort bereitstellen.
  • Serielles Rollout verwenden, wenn möglich: serial ermöglicht es Ihnen, jeweils ein paar Hosts zu aktualisieren und anzuhalten, bevor die gesamte Gruppe betroffen ist.
- name: Anwendung schrittweise bereitstellen
  hosts: web
  serial: 2
  tasks:
    - name: Release installieren
      ansible.builtin.unarchive:
        src: "{{ artifact_path }}"
        dest: /opt/myapp/releases/{{ release_version }}
        remote_src: true

Mit serial verarbeitet Ansible Hosts in Batches. Kombinieren Sie es mit Load-Balancer-Draining, wenn Ihre Anwendung nicht neu gestartet werden kann, ohne aktive Anfragen zu verlieren.

Ein konkreter Bereitstellungsablauf

Eine sichere Ansible-Bereitstellung für eine Webanwendung könnte wie folgt aussehen:

00-preflight.yml prüft den Festplattenspeicher, bestätigt, dass das Ziel-Release existiert, überprüft die Datenbankkonnektivität und stellt sicher, dass sich die Hosts in der erwarteten Umgebung befinden. Es ändert das System nicht.

01-migrate-db.yml führt nur abwärtskompatible Migrationen durch. Es zeichnet die Migrationsversion auf und schlägt fehl, wenn die Datenbank bereits vor dem angeforderten Release liegt.

02-deploy-app.yml lädt das Artefakt herunter, entpackt es in ein versioniertes Release-Verzeichnis, erstellt die Konfiguration aus Vorlagen und aktualisiert einen current-Symlink. Es startet noch keine Dienste neu.

03-reload-services.yml drainiert jeden Host vom Load Balancer, lädt den Dienst neu oder startet ihn neu, wartet auf den lokalen Health-Endpunkt und gibt den Host dann wieder in den Dienst.

04-smoke-test.yml ruft den öffentlichen Endpunkt über denselben Pfad auf, den Benutzer nehmen. Es überprüft den Antworttext oder den Versionsendpunkt, nicht nur einen 200 von einer Load-Balancer-Standardseite.

Dieser Ablauf ist langsamer als ein Ein-Befehl-Neustart. Es ist auch viel einfacher, darüber nachzudenken, wenn der Deploy auf halbem Weg fehlschlägt.

Die Gewohnheit, die dies zum Funktionieren bringt

Sequenzielle Ansible-Playbooks funktionieren am besten, wenn jedes einen engen Vertrag hat: was es erwartet, was es ändert, wie es den Erfolg beweist und was zu tun ist, wenn es fehlschlägt. Dieser Vertrag ist wichtiger als die Anzahl der YAML-Dateien.

Beginnen Sie mit den Phasen, die Ihr tatsächliches Risiko widerspiegeln: Preflight, Migration, Deploy, Reload, Smoke-Test, Rollback. Halten Sie die Befehle langweilig. Testen Sie das Rollback, bevor Sie es brauchen. Wenn eine Bereitstellung fehlschlägt, sollten Sie in der Lage sein, auf die genaue Phase zu zeigen, die fehlgeschlagen ist, und den nächsten Schritt zu entscheiden, ohne den gesamten Automatisierungsbaum erneut lesen zu müssen.