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.
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.
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.
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.
| 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.
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.
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.
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:
#!/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:
when: manual streichen könnt.| 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 |
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