Nicht alle Backend-Dienste werden gleich deployed

Ein Team bereitet sich darauf vor, ein neues Feature auszuliefern. Das API-Service-Update läuft reibungslos – ein paar Sekunden Rolling Replacement, null Ausfallzeit. Dann kommt das Worker-Update. Jemand drückt auf Deploy, und plötzlich verschwinden alle in der Warteschlange befindlichen Bildverarbeitungsjobs. Das Team starrt auf die Logs, verwirrt. „Beim API hat es funktioniert. Warum ist der Worker kaputtgegangen?“

Diese Situation erlebt man in Engineering-Teams jede Woche. Die Ursache ist fast nie ein Bug im Deployment-Tool. Es ist ein Missverständnis darüber, um welche Art von Backend-Dienst es sich handelt.

Backend-Dienste sehen an der Oberfläche ähnlich aus. Sie laufen alle auf Servern, sie haben alle Code, sie brauchen alle Updates. Aber wie sie Arbeit erhalten, wie sie Zustand halten und wie sie mit Unterbrechungen umgehen, unterscheidet sich grundlegend. Eine CI/CD-Pipeline, die für einen Typ perfekt funktioniert, kann für einen anderen still und leise Daten zerstören.

API-Services: Immer zuhörend, immer verfügbar

Der bekannteste Backend-Typ ist der API-Service. Er horcht auf einem Port, wartet auf eingehende Anfragen von mobilen Apps, Websites oder anderen Diensten und gibt eine Antwort zurück. Benutzer merken es sofort, wenn ein API-Service ausfällt – die App lädt nicht mehr, die Seite zeigt einen Fehler an, die Transaktion schlägt fehl.

Das folgende Flussdiagramm klassifiziert Backend-Dienste danach, wie sie Arbeit erhalten, und ordnet jedem Typ die empfohlene Deployment-Strategie zu.

flowchart TD A[Wie erhält der Dienst Arbeit?] --> B[Anfrage] A --> C[Warteschlange] A --> D[Zeitplan] A --> E[Stream] B --> F[API-Service] F --> G[Rolling / Blue-Green / Canary] C --> H[Worker] H --> I[Graceful Shutdown nach Job-Abschluss] D --> J[Scheduler] J --> K[Überlappung verhindern, verteilte Locks verwenden] E --> L[Consumer] L --> M[Offset bei Graceful Shutdown committen]

API-Services müssen immer bereit sein. Sie müssen Verkehrsspitzen bewältigen, indem sie Instanzen hoch- und herunterskalieren. Sie müssen neue Verbindungen akzeptieren, während alte noch verarbeitet werden. Und vor allem müssen sie aktualisiert werden, ohne laufende Anfragen fallenzulassen.

Für CI/CD bedeutet das, dass die Deployment-Strategie entscheidend ist. Ein einfaches Stop-and-Start-Deployment würde aktive Benutzer abschneiden. Rolling Updates, Blue-Green-Deployments oder Canary-Releases werden notwendig. Die Pipeline muss überprüfen, ob neue Instanzen Health Checks bestehen, bevor alte entfernt werden.

Worker: Jobs verarbeiten, keine Anfragen

Worker kommunizieren nicht direkt mit Benutzern. Sie holen Aufgaben aus einer Warteschlange, verarbeiten sie eine nach der anderen und speichern die Ergebnisse. Bildskalierung, E-Mail-Versand, Berichtserstellung – das sind Worker-Jobs. Der Benutzer lädt ein Foto hoch und erhält sofort eine Antwort, aber die Skalierung passiert im Hintergrund.

Da Worker keine Live-Anfragen bedienen, können sie sanfter aktualisiert werden. Das Hauptanliegen ist, aktive Jobs nicht fallenzulassen. Eine gute Worker-Pipeline wartet, bis der aktuelle Job abgeschlossen ist, bevor sie den alten Prozess herunterfährt. Wenn das Warteschlangensystem dies unterstützt, kann der Worker signalisieren, dass er anhält, seine aktuelle Aufgabe beenden und dann beenden. Neue Instanzen starten, holen die nächsten Jobs aus der Warteschlange, und das System läuft weiter, ohne Arbeit zu verlieren.

Der Fehler, den Teams machen, ist, Worker wie API-Services zu behandeln. Sie töten den Prozess während des Deployments sofort, und alle laufenden Jobs gehen verloren. Die Warteschlange weiß nicht, dass der Job auf halbem Weg fehlgeschlagen ist – sie sieht nur nie ein Abschlusssignal. Der Job verschwindet in einem schwarzen Loch.

Ein Kubernetes Deployment erwartet, für immer zu laufen, und verwendet Readiness-Probes, um den Datenverkehr zu steuern. Ein Job hingegen läuft bis zum Ende und verwendet restartPolicy: Never:

# API-Service - läuft für immer, braucht Readiness Probe
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
      - name: api
        image: myapp/api:latest
        ports:
        - containerPort: 8080
        readinessProbe:
          httpGet:
            path: /health
            port: 8080
---
# Worker - führt einen einzelnen Job aus und beendet sich dann
apiVersion: batch/v1
kind: Job
metadata:
  name: image-resize-job
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: worker
        image: myapp/worker:latest
        command: ["process-queue"]

Scheduler: Timing ist alles

Scheduler führen Aufgaben nach einem Zeitplan aus. Alte Daten um Mitternacht bereinigen. Stündlich mit einem externen System synchronisieren. Tägliche Berichte um 6 Uhr morgens erstellen.

Die Herausforderung bei Schedulern besteht darin, doppelte Ausführungen zu vermeiden. Wenn ein Scheduler einen Bereinigungsjob ausführt und das Deployment den Scheduler genau dann neu startet, wenn der Job beginnt, besteht die Gefahr, dass die neue Instanz denselben Job ebenfalls auslöst. Jetzt läuft die Bereinigung zweimal, was zu Konflikten oder Datenproblemen führen kann.

Eine gute Scheduler-Pipeline stellt sicher, dass der Scheduler keine neue Instanz startet, während die alte noch eine geplante Aufgabe ausführt. Sie benötigt auch einen Mechanismus, um überlappende Läufe zu erkennen und zu überspringen. Einige Teams verwenden verteilte Locks oder Datenbankmarkierungen, um doppelte Ausführungen zu verhindern.

Consumer: Mit dem Stream Schritt halten

Consumer ähneln Workern, verarbeiten aber einen kontinuierlichen Datenstrom. Sie lesen von Message Brokern wie Kafka oder RabbitMQ, verarbeiten Nachrichten in Echtzeit und müssen oft ihre Position im Stream beibehalten.

Der entscheidende Unterschied ist Geschwindigkeit und Offset-Tracking. Wenn ein Consumer zurückfällt, muss er aufholen, ohne das System zu überlasten. Wenn ein Consumer abstürzt, muss er dort weitermachen, wo er aufgehört hat, nicht von Anfang an.

Für CI/CD bedeutet dies, dass das Deployment sorgfältig mit Offset-Commits umgehen muss. Das Herunterfahren eines Consumers, ohne den aktuellen Offset zu committen, kann zur erneuten Verarbeitung bereits behandelter Nachrichten führen. Das Neustarten vom falschen Offset kann Nachrichten überspringen oder zweimal verarbeiten. Die Pipeline muss mit dem Message Broker koordinieren, um einen Graceful Shutdown und Wiederaufnahme zu gewährleisten.

Interne Dienste: Die versteckten Abhängigkeiten

Interne Dienste werden nicht von Frontend-Anwendungen angesprochen. Sie bedienen andere Dienste im System – Authentifizierung, Konfiguration, Logging, Feature Flags. Mehrere Dienste sind von ihnen abhängig, oft synchron.

Die Gefahr bei internen Diensten sind kaskadierende Ausfälle. Eine kleine Änderung in einem Authentifizierungsdienst kann jeden Dienst zerstören, der ihn aufruft. Eine langsame Antwort von einem Konfigurationsdienst kann Timeouts im gesamten System verursachen.

Diese Dienste benötigen strengere Tests und ein vorsichtigeres Deployment. Die Pipeline sollte Integrationstests ausführen, die das tatsächliche Aufruferverhalten simulieren. Das Deployment sollte schrittweise erfolgen, mit Überwachung der abhängigen Dienste. Ein Rollback muss schnell und zuverlässig sein, da der Schaden groß sein kann.

Eine praktische Checkliste für Backend-Dienst-Pipelines

Bevor du eine Pipeline für einen Backend-Dienst entwirfst, stelle diese Fragen:

  • Wie erhält dieser Dienst Arbeit? (Anfrage, Warteschlange, Zeitplan, Stream)
  • Kann er mitten in einer Aufgabe sicher gestoppt werden? Wenn ja, was passiert mit der Aufgabe?
  • Hält er einen In-Memory-Zustand, der nicht wiederhergestellt werden kann?
  • Was passiert, wenn zwei Instanzen gleichzeitig dieselbe Aufgabe ausführen?
  • Welche anderen Dienste brechen, wenn dieser Dienst ausfällt?
  • Wie lange kann dieser Dienst nicht verfügbar sein, bevor Benutzer oder Systeme es bemerken?

Die Antworten zeigen dir, ob du Rolling Updates, Graceful Shutdown Hooks, Queue-Koordination oder dependency-aware Deployment-Reihenfolge benötigst.

Das Fazit

Eine CI/CD-Pipeline ist kein Einheits-Skript. Sie ist ein Abbild davon, wie dein Dienst tatsächlich funktioniert. API-Services brauchen Verbindungsbewusstsein. Worker brauchen Job-Abschlussgarantien. Scheduler brauchen Überlappungsschutz. Consumer brauchen Offset-Management. Interne Dienste brauchen Abhängigkeitskoordination.

Wenn du verstehst, um welche Art von Backend-Dienst es sich handelt, hörst du auf, Pipeline-Vorlagen aus Blogbeiträgen zu kopieren, und beginnst, Pipelines zu bauen, die deine Daten, deine Benutzer und den Schlaf deines Teams schützen.