10 — Container und Orchestrierung
Teil D — Wie man betreibt. Zurück zur Übersicht.
Lernziel
Nach diesem Kapitel kannst du erklären, warum Container das natürliche Artefakt einer Auslieferungskette sind, kennst die für DevOps wesentlichen Zwölf-Faktoren-Regeln und kannst begründen, wann Orchestrierung nötig wird und wann nicht. Der KFZ-Rechner läuft als Container unter systemd.
10.1 Warum Container hier auftauchen
Die technischen Grundlagen — Namespaces, cgroups, Image-Schichten, Podman gegen Docker — behandelt die Container-Unterlage dieses Wikis ausführlich. Dieses Kapitel beschränkt sich auf die Frage: Was leisten Container für die Auslieferungskette?
Die Antwort besteht aus vier Punkten, die alle an früher Gesagtes anknüpfen:
| Anforderung | Kapitel | Was der Container beiträgt |
|---|---|---|
| Einmal bauen, überall ausrollen | 6 | Das Image ist das Artefakt — bitgleich in jeder Umgebung |
| Umgebungsparität | 6 | Laufzeitumgebung steckt im Image, nicht auf dem Server |
| Schnelles Rollback | 8 | Altes Image starten, Sekunden statt Minuten |
| Reproduzierbarkeit | 9 | Containerfile ist Code, das Image ist unveränderlich |
Der Satz, auf den es ankommt: Ohne Container ist das Artefakt einer Pipeline ein Archiv, das auf einem passend vorbereiteten Server ausgepackt werden muss — und die Frage, ob dieser Server passend vorbereitet ist, bleibt offen.
Mit Container ist das Artefakt selbst der vorbereitete Server. Das ist der eigentliche Grund, warum Container und Continuous Delivery gemeinsam populär wurden: Das eine löst ein Problem, das das andere aufwirft.
10.2 Die Zwölf Faktoren — die relevanten sechs
Die Twelve-Factor App ist eine Sammlung von Regeln für Anwendungen, die in solchen Umgebungen betrieben werden sollen. Sechs davon sind für diese Unterlage unmittelbar relevant.
| Faktor | Regel | Warum |
|---|---|---|
| III Konfiguration | Konfiguration in Umgebungsvariablen, nicht im Code | ein Image für alle Umgebungen |
| VI Prozesse | zustandslos, nichts im lokalen Dateisystem behalten | Container jederzeit ersetzbar |
| IX Einweg | schneller Start, sauberes Herunterfahren auf SIGTERM | Rolling Update ohne Fehler |
| XI Logs | nach stdout schreiben, nicht in Dateien | die Umgebung sammelt ein |
| X Parität | Entwicklung, Test und Produktion ähnlich halten | weniger Überraschungen |
| XII Verwaltungsaufgaben | Migrationen als einmalige Prozesse | reproduzierbar, nachvollziehbar |
# Faktor III und XI in der Praxis import logging import os import sys logging.basicConfig( stream=sys.stdout, # XI: nach stdout, nicht in Dateien format='{"zeit":"%(asctime)s","stufe":"%(levelname)s","text":"%(message)s"}', ) DB_HOST = os.environ["DB_HOST"] # III: aus der Umgebung DB_PASS = os.environ["DB_PASSWORD"] # kein Standardwert bei Geheimnissen LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
Faktor IX wird am häufigsten übersehen. Beim Rolling Update sendet die Umgebung SIGTERM und wartet einige Sekunden. Eine Anwendung, die dieses Signal ignoriert, wird anschließend hart abgeschossen — mitten in laufenden Anfragen.
Praktisch heißt sauberes Herunterfahren: keine neuen Anfragen mehr annehmen, laufende zu Ende bearbeiten, Verbindungen schließen, beenden. Das sind zwanzig Zeilen Code und der Unterschied zwischen einem unauffälligen Update und sporadischen Fehlern, die niemand reproduzieren kann.
10.3 Wann Orchestrierung nötig wird
Die ehrliche Antwort lautet: seltener, als angenommen wird.
| Stufe | Werkzeug | Taugt für | Grenze |
|---|---|---|---|
| 1 | podman run | Ausprobieren | überlebt keinen Neustart |
| 2 | Quadlet / systemd | 1–30 Dienste auf einem Server | ein Host |
| 3 | Compose | Entwicklungsumgebungen | nicht für Produktion gedacht |
| 4 | k3s | kleiner Cluster, Ausfallsicherheit | Betriebsaufwand steigt |
| 5 | Kubernetes / OpenShift | Unternehmensumgebungen | halbe bis ganze Stelle für die Plattform |
Der Sprung von Stufe 2 auf Stufe 4 lohnt sich, wenn einer dieser Punkte zutrifft:
- Der Ausfall eines einzelnen Servers ist nicht hinnehmbar.
- Die Last schwankt so stark, dass automatisch skaliert werden muss.
- Mehrere Teams brauchen abgestufte Rechte auf derselben Plattform.
- Die Anzahl der Dienste übersteigt das, was eine Person überblicken kann.
Trifft nichts davon zu, ist Stufe 2 die wirtschaftlichere Wahl. Ein einzelner Server mit Quadlet-Units, Git-Anbindung und einem Deployment-Skript liefert Reproduzierbarkeit, Versionierung, Rollback und saubere Protokollierung — mit Werkzeugen, die jeder Linux-Administrator bereits beherrscht.
10.4 Kurzüberblick Kubernetes und OpenShift
Nur so viel, wie zur Einordnung nötig ist.
| Objekt | Aufgabe |
|---|---|
| Pod | kleinste Einheit: ein oder mehrere Container mit gemeinsamer IP |
| Deployment | sorgt für die gewünschte Anzahl Pods, ermöglicht Rolling Update |
| Service | stabile interne Adresse und Lastverteilung |
| Ingress / Route | Zugang von außen |
| ConfigMap / Secret | Konfiguration und Geheimnisse getrennt vom Image |
Das Grundprinzip ist eine Regelschleife: Man beschreibt den Sollzustand, ein Controller vergleicht laufend mit dem Ist-Zustand und korrigiert. Genau dasselbe Denkmuster wie bei Ansible aus Kapitel 9 — nur dauerhaft statt einmalig.
OpenShift ist Kubernetes mit integrierter Registry, Weboberfläche, Pipelines, Monitoring und deutlich strengeren Sicherheitsvorgaben. Der wichtigste praktische Unterschied: Container laufen dort mit einer zufällig zugewiesenen Benutzerkennung und niemals als Root. Ein Image, das unter OpenShift läuft, läuft überall — umgekehrt gilt das nicht.
10.5 Lab: Der KFZ-Rechner wird zum Dienst
- Containerfile
# ---------- Stufe 1: Abhängigkeiten ---------- FROM registry.access.redhat.com/ubi9/python-312:latest AS builder WORKDIR /build COPY requirements.txt . RUN pip install --no-cache-dir --target=/build/deps -r requirements.txt # ---------- Stufe 2: Laufzeit ---------- FROM registry.access.redhat.com/ubi9/python-312-minimal:latest LABEL org.opencontainers.image.title="KFZ-Rechner" \ org.opencontainers.image.vendor="Beispiel AG" WORKDIR /app COPY --from=builder /build/deps /app/deps COPY src/ ./src/ ENV PYTHONPATH=/app/deps \ PYTHONUNBUFFERED=1 \ LOG_LEVEL=INFO # Gruppe 0 statt fester UID -> läuft auch unter OpenShift RUN chgrp -R 0 /app && chmod -R g=u /app USER 1001 EXPOSE 8080 HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ CMD python -c "import urllib.request,sys; \ sys.exit(0 if urllib.request.urlopen('http://localhost:8080/health').status==200 else 1)" CMD ["python", "-m", "src.server"]
- ~/.config/containers/systemd/kfz-rechner.container
[Unit] Description=KFZ-Prämienrechner After=network-online.target [Container] Image=registry.intern/kfz-rechner:2.5.0 ContainerName=kfz-rechner PublishPort=127.0.0.1:8081:8080 Environment=UMGEBUNG=produktion Environment=LOG_LEVEL=INFO Secret=kfz_db_passwort,type=env,target=DB_PASSWORD Volume=/srv/kfz/daten:/app/daten:Z ReadOnly=true Tmpfs=/tmp:rw,size=32m NoNewPrivileges=true DropCapability=ALL Memory=512m AutoUpdate=registry [Service] Restart=always TimeoutStartSec=90 [Install] WantedBy=default.target
systemctl --user daemon-reload systemctl --user start kfz-rechner.service systemctl --user status kfz-rechner.service journalctl --user -u kfz-rechner.service -f
Aufgaben:
- Bring den Dienst zum Laufen und prüfe nach einem Neustart des Servers, dass er von selbst wiederkommt.
- Ergänze sauberes Herunterfahren auf
SIGTERMim Python-Code und beobachte den Unterschied beisystemctl restart. - Vergleiche die Größe des Images mit und ohne Multi-Stage-Build.
- Erzeuge mit
podman kube generate kfz-rechnereine Kubernetes-Beschreibung und sieh sie dir an. Was fehlt darin für den Produktivbetrieb?
10.6 Anti-Pattern
| Anti-Pattern | Folge | Abhilfe |
|---|---|---|
| Mehrere Dienste in einem Container | schlecht wartbare VM im Container-Gewand | ein Prozess je Container |
| SSH-Server im Container | Änderungen von Hand, die beim Neustart verschwinden | podman exec zur Diagnose, sonst Image ändern |
| Als Root laufen lassen | unnötige Angriffsfläche, läuft nicht unter OpenShift | USER 1001, Gruppe 0 |
| Konfiguration im Image | ein Image je Umgebung, Prinzip verletzt | Umgebungsvariablen |
| Logs in Dateien im Container | verschwinden mit dem Container | nach stdout schreiben |
| Daten im Container ablegen | Datenverlust beim Austausch | Volumes |
latest in Produktion | unklar, was läuft | feste Version, besser Digest |
SIGTERM ignoriert | Fehler bei jedem Update | sauberes Herunterfahren umsetzen |
| Kubernetes für drei Container | Aufwand ohne Gegenwert | Quadlet auf einem Server |
10.7 Reflexionsfrage
Wie viele Dienste betreibt ihr, und wie oft ist im letzten Jahr ein Server so ausgefallen, dass ein Cluster geholfen hätte?
Die zweite Zahl ist meist kleiner, als die Diskussion über Kubernetes vermuten lässt.
Weiter mit Kapitel 11 — GitOps
