06 — Continuous Delivery und Deployment

Teil C — Wie man ausliefert. Zurück zur Übersicht.

Lernziel

Nach diesem Kapitel kannst du CI, Continuous Delivery und Continuous Deployment sauber gegeneinander abgrenzen und erklären, warum der Unterschied zwischen den beiden letzten eine Geschäftsentscheidung ist. Du kennst das Prinzip einmal bauen, überall ausrollen, verstehst die Trennung von Deployment und Release und weißt, warum Datenbankmigrationen das eigentliche Hindernis sind.

6.1 Die drei Begriffe

Continuous Integration
  Code wird laufend zusammengeführt, gebaut und getestet.
  Ergebnis: ein geprüftes Artefakt.
         │
         ▼
Continuous Delivery
  Das Artefakt ist jederzeit auslieferbar.
  Der Weg in die Produktion ist automatisiert und erprobt.
  Ausgelöst wird er durch einen Menschen.
         │
         ▼
Continuous Deployment
  Jede Änderung, die alle Prüfungen besteht, geht automatisch
  in Produktion. Ohne Knopfdruck.
CI Continuous Delivery Continuous Deployment
Gebaut und getestet automatisch automatisch automatisch
In Testumgebungen teilweise automatisch automatisch
In Produktion nein Knopfdruck automatisch
Entscheidung über den Zeitpunkt Mensch Pipeline

Der Unterschied zwischen Delivery und Deployment ist technisch minimal — es fehlt genau ein manueller Schritt. Es ist deshalb keine technische, sondern eine geschäftliche und regulatorische Entscheidung.

Für einen internen Prämienrechner kann Continuous Deployment vollkommen angemessen sein. Für ein System, dessen Änderungen der Fachbereich freigeben muss oder bei dem eine Aufsichtsbehörde die Genehmigung sehen will, ist Continuous Delivery das Ziel — der Knopf existiert, jemand muss ihn drücken, und dieses Drücken ist protokolliert. Siehe Kapitel 17.

Wichtig ist die Reihenfolge: Continuous Delivery ist die anspruchsvolle Leistung. Wer sie beherrscht, kann jederzeit entscheiden, den letzten Schritt zu automatisieren. Wer sie nicht beherrscht, gewinnt durch das Weglassen des Knopfes gar nichts.

6.2 Einmal bauen, überall ausrollen

Das vielleicht wichtigste technische Prinzip dieses Kapitels.

   FALSCH                              RICHTIG

   Code ──► Build ──► Test-Umgebung    Code ──► Build ──► Artefakt
   Code ──► Build ──► Abnahme                             │
   Code ──► Build ──► Produktion                          ├──► Test
                                                          ├──► Abnahme
   Drei verschiedene Ergebnisse.                          └──► Produktion
   Getestet wurde nie das,
   was produktiv läuft.                 Ein Artefakt. Überall dasselbe.

Wird für jede Umgebung neu gebaut, hat man drei verschiedene Artefakte. Zwischen dem Bauen für die Abnahme und dem Bauen für die Produktion kann sich eine Abhängigkeit aktualisiert haben, ein Basis-Image geändert, ein Zeitstempel unterschieden. Getestet wurde dann streng genommen etwas anderes als das, was in Produktion läuft — und genau dieser Unterschied ist die Sorte Fehler, die man am Freitagabend sucht.

Die Konsequenz: Die Konfiguration darf nicht im Artefakt stecken. Sie kommt von außen.

# Dasselbe Image, drei Umgebungen
podman run -e DB_HOST=db-test.intern    kfz-rechner:2.4.0
podman run -e DB_HOST=db-abnahme.intern kfz-rechner:2.4.0
podman run -e DB_HOST=db-prod.intern    kfz-rechner:2.4.0

Dieses Prinzip stammt aus der Zwölf-Faktoren-Methodik, deren dritter Punkt genau das fordert: Konfiguration gehört in die Umgebung, nicht in den Code.

6.3 Deployment ist nicht Release

Zwei Begriffe, die im Alltag synonym gebraucht werden, obwohl ihre Trennung eine der wirkungsvollsten Techniken überhaupt ist.

Begriff Bedeutung Entscheidet
Deployment neue Version läuft auf den Servern Technik
Release Anwender können die Funktion nutzen Fachbereich

Solange beides zusammenfällt, ist jede Auslieferung ein Ereignis mit Publikum. Entkoppelt man sie, wird das Deployment zum Nichtereignis:

# Die Funktion ist ausgerollt, aber abgeschaltet
if feature_aktiv("wenigfahrer_rabatt"):
    beitrag = wende_wenigfahrer_rabatt_an(beitrag)

Der Code geht am Dienstag in Produktion, ohne dass ihn jemand bemerkt. Am Donnerstag schaltet der Fachbereich die Funktion frei — für zunächst fünf Prozent der Anfragen. Fällt etwas auf, wird sie ausgeschaltet. Das dauert Sekunden und braucht kein Deployment, kein Wartungsfenster und keine Nachtschicht.

Mehr zu Feature Flags in Kapitel 8.

6.4 Umgebungen und ihre Tücken

Umgebung Zweck Datenbestand
Entwicklung lokales Arbeiten synthetisch, klein
Integration/Test automatisierte Tests synthetisch, reproduzierbar
Abnahme fachliche Prüfung, Schulung synthetisch, realistisch im Umfang
Produktion Echtbetrieb echt

Schneeflockenumgebungen sind das häufigste Hindernis auf dem Weg zu Continuous Delivery.

Eine Schneeflocke ist ein System, das über Jahre von Hand gepflegt wurde und das niemand mehr identisch nachbauen kann. Typisches Symptom: „Auf der Abnahme läuft es, in Produktion nicht„ — und niemand kann sagen, worin sich die beiden unterscheiden.

Die Gegenmaßnahme heißt Infrastructure as Code (Kapitel 9) und Containerisierung (Kapitel 10). Der Prüfstein ist unbequem, aber eindeutig: Könnt ihr eure Abnahmeumgebung löschen und binnen einer Stunde identisch neu erzeugen?

Vollständige Umgebungsparität ist dabei eine Illusion — die Produktion hat mehr Last, mehr Daten, echte Netzwerke und echte Anwender. Ziel ist nicht Gleichheit, sondern kontrollierte, bekannte Unterschiede. Ein Unterschied, den man kennt und dokumentiert hat, ist beherrschbar. Einer, den man erst im Störungsfall entdeckt, nicht.

6.5 Datenbankmigrationen — das eigentliche Problem

Anwendungscode lässt sich in Sekunden austauschen und ebenso schnell zurückrollen. Bei Datenbanken gilt das nicht: Eine gelöschte Spalte ist gelöscht, und die Daten darin sind es auch.

Das ist der Punkt, an dem Continuous Delivery in der Praxis am häufigsten scheitert.

Das Expand-Contract-Verfahren

Die Lösung besteht darin, Schemaänderungen in mehrere rückwärtskompatible Schritte zu zerlegen.

Aufgabenstellung: Die Spalte sf_klasse soll in schadenfreiheitsklasse umbenannt werden.

Schritt 1 — Erweitern (expand)
  Neue Spalte ergänzen. Die alte bleibt.
  ALTER TABLE vertrag ADD COLUMN schadenfreiheitsklasse INT;
  → Alte und neue Anwendungsversion funktionieren beide.

Schritt 2 — Doppelt schreiben
  Anwendung schreibt in beide Spalten, liest aus der alten.
  → Ausrollbar und zurückrollbar.

Schritt 3 — Daten übertragen
  UPDATE vertrag SET schadenfreiheitsklasse = sf_klasse
   WHERE schadenfreiheitsklasse IS NULL;

Schritt 4 — Umschalten
  Anwendung liest aus der neuen Spalte, schreibt weiter beide.
  → Immer noch zurückrollbar.

Schritt 5 — Zusammenziehen (contract)
  Erst wenn keine alte Version mehr läuft:
  ALTER TABLE vertrag DROP COLUMN sf_klasse;

Fünf Auslieferungen statt einer. Dafür ist jede einzelne davon gefahrlos und jederzeit zurücknehmbar.

Die Grundregel für Datenbankänderungen bei laufender Auslieferung:

Jede Migration muss mit der vorherigen Anwendungsversion verträglich sein. Sonst ist ein Rollback unmöglich — und ohne Rollback-Möglichkeit ist jede Auslieferung wieder ein Risiko, egal wie gut die Pipeline ist.

Praktisch heißt das: keine Spalten löschen, keine Spalten umbenennen, keine Typen verengen, solange noch eine ältere Version laufen könnte. Alles davon lässt sich als Expand-Contract-Folge abbilden.

Migrationen gehören dabei genauso in die Versionsverwaltung wie der Code — mit Werkzeugen wie Flyway, Liquibase oder Alembic, die durchnummerierte, einmalig ausgeführte Skripte verwalten.

6.6 Lab: Die Pipeline bekommt einen Ausrollschritt

.gitlab-ci.yml
stages:
  - pruefen
  - testen
  - bauen
  - ausrollen-test
  - ausrollen-prod

variables:
  IMAGE: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
 
# ... Stufen aus Kapitel 5 ...

ausrollen-test:
  stage: ausrollen-test
  environment:
    name: test
    url: https://kfz-test.intern
  script:
    - ./scripts/deploy.sh test "$IMAGE"
    - ./scripts/smoketest.sh https://kfz-test.intern
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

ausrollen-prod:
  stage: ausrollen-prod
  environment:
    name: produktion
    url: https://kfz.intern
  script:
    - ./scripts/deploy.sh produktion "$IMAGE"
    - ./scripts/smoketest.sh https://kfz.intern
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual            # <- genau hier liegt der Unterschied
  allow_failure: false

Beachte die Zeile when: manual. Sie ist der gesamte Unterschied zwischen Continuous Delivery und Continuous Deployment. Nimmt man sie heraus, rollt jede geprüfte Änderung automatisch aus.

Der Rauchtest danach ist keine Formalie, sondern die Zusage, dass die neue Version tatsächlich antwortet:

scripts/smoketest.sh
#!/usr/bin/env bash
set -euo pipefail
 
URL="$1"
VERSUCHE=30
 
for i in $(seq 1 "$VERSUCHE"); do
    if curl -fsS --max-time 5 "${URL}/health" > /dev/null; then
        echo "Dienst antwortet nach ${i} Versuch(en)."
 
        # Fachliche Stichprobe: bekannter Eingabewert, bekanntes Ergebnis
        ergebnis=$(curl -fsS "${URL}/api/praemie?sf=5&alter=3" | jq -r '.betrag')
        if [[ "$ergebnis" != "288.00" ]]; then
            echo "FEHLER: Erwartet 288.00, erhalten ${ergebnis}"
            exit 1
        fi
        echo "Fachliche Prüfung bestanden."
        exit 0
    fi
    sleep 2
done
 
echo "FEHLER: Dienst antwortet nach ${VERSUCHE} Versuchen nicht."
exit 1

Aufgaben:

  1. Ergänze eine Abnahmeumgebung zwischen Test und Produktion.
  2. Sorge dafür, dass in allen Umgebungen dasselbe Image mit unterschiedlicher Konfiguration läuft.
  3. Lass den Rauchtest absichtlich fehlschlagen und prüfe, dass die Pipeline rot wird.
  4. Formuliere in einem Satz, was passieren muss, damit ihr when: manual streichen könnt.

6.7 Anti-Pattern

Anti-Pattern Folge Abhilfe
Pro Umgebung neu bauen getestet wurde nie das Produktivartefakt einmal bauen, überall ausrollen
Konfiguration im Artefakt für jede Umgebung ein eigenes Image Konfiguration von außen
Schneeflockenumgebungen „läuft auf der Abnahme“ Infrastructure as Code
Migration ohne Rückwärtskompatibilität Rollback unmöglich Expand-Contract
Manuelle Schritte in der Anleitung wird vergessen, wenn es eilt vollständig automatisieren
Kein Rauchtest nach dem Ausrollen Störung fällt erst Anwendern auf automatische Prüfung
Freigabe als reine Formalie Genehmigung ohne Kenntnis des Inhalts Änderungsliste automatisch beilegen

6.8 Reflexionsfrage

Wenn ihr euer Produktivsystem heute Abend verlieren würdet — wie lange bräuchtet ihr, um es aus dem Repository heraus neu aufzubauen? Und wisst ihr das aus Erfahrung oder aus Vermutung?


Weiter mit Kapitel 07 — Pipeline in der Praxis