Warum die Reihenfolge deiner Deployments wichtiger ist als deine Pipeline

Du hast eine neue Version deiner Anwendung bereit. Die Pipeline ist grün. Das Team schaut zu. Du drückst auf Deploy. Wenige Minuten später tauchen Fehler in den Logs auf. User melden, dass sie keine Käufe abschließen können. Das Datenbank-Team sagt, das Schema-Update wurde erst nach dem Start der Anwendung ausgeführt – nicht davor.

Dieses Szenario ist nicht ungewöhnlich. Es passiert, weil Deployment selten bedeutet, dass eine einzelne Anwendung eine einzelne Sache tut. Es geht darum, wie diese Anwendung mit allem anderen verbunden ist, von dem sie abhängt. Diese Verbindungen – und die damit verbundenen Risiken – zu verstehen, ist der Unterschied zwischen einem reibungslosen Deployment und einem Produktionsvorfall.

Abhängigkeiten sind nicht nur Code

Eine moderne Anwendung läuft fast nie allein. Sie liest aus einer Datenbank. Sie ruft eine API für die Zahlungsabwicklung auf. Sie verwendet eine Drittanbieter-Bibliothek, um Bilder zu skalieren. Sie ist auf eine Message Queue angewiesen, um Benachrichtigungen zu versenden. Jede dieser Komponenten ist eine Abhängigkeit, und jede kann brechen, wenn du eine neue Version deployst.

Die häufigste Abhängigkeit ist die Datenbank. Deine Anwendung speichert Benutzerdaten in PostgreSQL oder MySQL. Wenn du eine neue Version deployst, liest und schreibt diese Version Daten anders als die alte. Vielleicht hat die alte Version die Adresse eines Benutzers in einer einzigen Spalte gespeichert, und die neue Version teilt sie in Straße, Stadt und Postleitzahl auf. Wenn die neue Version startet, bevor das Datenbankschema aktualisiert wurde, kann sie vorhandene Daten nicht lesen. Das ist ein Breaking Change – die alte und die neue Version sind inkompatibel.

Zu den Abhängigkeiten gehören auch APIs von anderen Teams oder externen Diensten. Stell dir vor, deine Anwendung ruft eine Zahlungs-API auf. Die neue Version erwartet eine Antwort in einem anderen JSON-Format. Wenn diese API noch nicht aktualisiert wurde, erhält deine Anwendung Daten, die sie nicht parsen kann, und Transaktionen schlagen fehl. Das Problem ist, dass du nicht immer kontrollieren kannst, wann sich diese API ändert. Ein anderes Team hat möglicherweise einen eigenen Deployment-Zeitplan und weiß nicht, dass deine Anwendung von einem bestimmten Antwortformat abhängt.

Auch Drittanbieter-Bibliotheken und Pakete sind Abhängigkeiten. Wenn du eine Bibliothek von Version 1.0 auf 2.0 aktualisierst, können Funktionen umbenannt worden sein oder ihre Signaturen geändert haben. Wenn deine Anwendung immer noch den alten Funktionsnamen aufruft, führt der Code zur Laufzeit zu einem Fehler. Deshalb verfolgen erfahrene Teams ihre Abhängigkeiten klar – in Dateien wie requirements.txt, package.json oder go.mod – und testen neue Bibliotheksversionen, bevor sie sie in der Produktion einsetzen.

Das versteckte Risiko: Die Deployment-Reihenfolge

Abhängigkeiten beeinflussen direkt die Reihenfolge, in der du Dinge deployst. Wenn Anwendung A von einer Datenbank abhängt, die ein Schema-Update benötigt, musst du zuerst die Datenbank aktualisieren und dann Anwendung A deployen. Wenn Anwendung B eine API von Anwendung C aufruft, musst du zuerst Anwendung C deployen, dann Anwendung B. Diese Reihenfolge ist wichtig, denn wenn du sie umkehrst, sucht die neu deployte Anwendung nach Daten oder Diensten, die noch nicht verfügbar sind. Die Folge sind Fehler, fehlgeschlagene Anfragen oder eine vollständig funktionsuntüchtige Anwendung.

Das folgende Sequenzdiagramm zeigt die korrekte Deployment-Reihenfolge und was passiert, wenn sie umgekehrt wird:

sequenceDiagram participant DB as Datenbank participant API as Backend API participant FE as Frontend participant Err as Fehler-Log Note over DB,FE: Korrekte Reihenfolge DB->>DB: Schema-Update API->>API: Neue Version deployen FE->>FE: Neue Version deployen Note over FE,Err: Alle Dienste kompatibel Note over DB,FE: Falsche Reihenfolge (umgekehrt) FE->>API: Daten anfragen API->>DB: Neues Schema abfragen DB-->>API: Schema nicht gefunden API-->>FE: Fehlerantwort FE->>Err: Fehler protokollieren

Das Risiko wächst mit der Anzahl der Abhängigkeiten. Je mehr Dienste synchronisiert werden müssen, desto höher ist die Wahrscheinlichkeit, dass einer davon nicht synchron ist. Ein Team, das dies gut handhabt, kartiert vor einem Deployment alle Abhängigkeiten, bestätigt die korrekte Reihenfolge und bereitet einen Rollback-Plan für den Fall vor, dass etwas nicht passt. Sie verwenden auch Techniken wie Rückwärtskompatibilität – die neue Version funktioniert noch mit der alten Version – um das Risiko von Breaking Changes zu reduzieren.

Breaking Changes sind nicht immer offensichtlich

Ein Breaking Change muss nicht dramatisch sein. Er kann subtil sein. Zum Beispiel könnte eine neue Version deiner Anwendung beginnen, ein zusätzliches Feld in einer Anfrage an eine interne API zu senden. Der empfangende Dienst ignoriert unbekannte Felder, also scheint alles in Ordnung. Aber ein paar Wochen später wird dieser empfangende Dienst aktualisiert und erwartet nun, dass dieses zusätzliche Feld vorhanden ist. Die alte Version deiner Anwendung, die in einigen Umgebungen noch läuft, funktioniert nicht mehr. Der Bruch ist zeitlich verzögert und die Ursache schwer nachvollziehbar.

Deshalb ist das Kartieren von Abhängigkeiten keine einmalige Aktivität. Es muss aktualisiert werden, wenn sich das System weiterentwickelt. Jedes Mal, wenn eine neue Abhängigkeit hinzugefügt wird oder eine bestehende sich ändert, ändern sich auch die Deployment-Reihenfolge und das Risikoprofil.

Wie man das Abhängigkeitsrisiko reduziert

Es gibt praktische Schritte, die du unternehmen kannst, um Deployments sicherer zu machen, wenn Abhängigkeiten im Spiel sind.

Dokumentiere zunächst deine Abhängigkeiten. Das bedeutet nicht, ein langes Dokument zu schreiben, das niemand liest. Es bedeutet, eine klare, maschinenlesbare Aufzeichnung darüber zu haben, wovon deine Anwendung abhängt und was von ihr abhängt. Tools wie Abhängigkeitsgraphen, Servicekataloge oder sogar eine einfache README in deinem Repository können helfen.

Zweitens: Teste die Integration, nicht nur die Einheit. Unit-Tests, die jeden externen Dienst mocken, werden Probleme, die durch das tatsächliche Verhalten von Abhängigkeiten verursacht werden, nicht aufdecken. Integrationstests, die gegen echte Datenbanken, APIs oder Message Queues laufen, werden Probleme aufdecken, bevor sie in die Produktion gelangen.

Drittens: Verwende Feature Flags oder versionierte APIs, um Rückwärtskompatibilität zu gewährleisten. Wenn deine neue Version immer noch Anfragen von alten Clients bedienen kann, hast du mehr Flexibilität bei der Deployment-Reihenfolge. Du kannst die neue Version zuerst deployen, prüfen, ob sie funktioniert, und dann die abhängigen Dienste aktualisieren.

Viertens: Übe deinen Rollback. Wisse genau, was passieren muss, wenn ein Deployment aufgrund einer Abhängigkeitsinkompatibilität fehlschlägt. Kannst du das Datenbankschema-Update rückgängig machen? Kannst du die Anwendung wieder auf die alte API-Version verweisen? Ein getesteter Rollback-Plan reduziert den Druck während eines Deployments.

Ein einfaches sequenzielles Deployment-Script erzwingt beispielsweise die korrekte Reihenfolge und stoppt bei einem Fehler:

#!/bin/bash
# deploy.sh - Erzwingt die korrekte Deployment-Reihenfolge

set -e  # Beende bei jedem Fehler

echo "Schritt 1: Datenbank-Schema deployen"
./deploy_database.sh || { echo "Datenbank-Deployment fehlgeschlagen. Abbruch."; exit 1; }

echo "Schritt 2: Backend-API deployen"
./deploy_api.sh || { echo "API-Deployment fehlgeschlagen. Führe Datenbank-Rollback durch..."; ./rollback_database.sh; exit 1; }

echo "Schritt 3: Frontend deployen"
./deploy_frontend.sh || { echo "Frontend-Deployment fehlgeschlagen. Führe Rollback von API und Datenbank durch..."; ./rollback_api.sh; ./rollback_database.sh; exit 1; }

echo "Alle Deployments erfolgreich abgeschlossen."

Eine praktische Checkliste vor deinem nächsten Deployment

Bevor du deployst, gehe diese kurze Checkliste durch:

  • Hast du jede Abhängigkeit deiner Anwendung aufgelistet (Datenbank, API, Bibliothek)?
  • Kennst du die korrekte Deployment-Reihenfolge für jede Abhängigkeit?
  • Hast du die neue Version gegen die tatsächlichen Versionen dieser Abhängigkeiten getestet?
  • Gibt es einen Rollback-Plan für jede Abhängigkeit, die brechen könnte?
  • Hast du die Deployment-Reihenfolge mit jedem Team kommuniziert, das eine Abhängigkeit besitzt?

Diese Checkliste ist nicht vollständig, deckt aber die häufigsten Fehlerquellen ab. Wenn du alle fünf Fragen mit Ja beantworten kannst, bist du in einer viel besseren Position als die meisten Teams.

Das Fazit

Deployment bedeutet nicht nur, Code zu pushen. Es geht darum, die Beziehungen zwischen deiner Anwendung und allem, womit sie interagiert, zu managen. Abhängigkeiten bestimmen die Reihenfolge des Deployments, das Risiko eines Fehlschlags und die Schwierigkeit der Wiederherstellung. Ein Team, das seine Abhängigkeiten versteht und für sie plant, wird weniger Vorfälle, schnellere Wiederherstellungen und mehr Vertrauen in jedes Release haben. Die Pipeline ist wichtig, aber die Abhängigkeitslandkarte ist das, was verhindert, dass die Pipeline zu einer Feuerwehrübung wird.