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:

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:

  1. Bring den Dienst zum Laufen und prüfe nach einem Neustart des Servers, dass er von selbst wiederkommt.
  2. Ergänze sauberes Herunterfahren auf SIGTERM im Python-Code und beobachte den Unterschied bei systemctl restart.
  3. Vergleiche die Größe des Images mit und ohne Multi-Stage-Build.
  4. Ergänze den Bau des Images in der Pipeline aus Kapitel 7 und rolle das Ergebnis über das Blue/Green-Skript aus Kapitel 8 aus.
  5. Erzeuge mit podman kube generate kfz-rechner eine 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