Wenn Ihr Container-Image bereit ist – wo läuft es eigentlich?
Sie haben das Image gebaut. Sie haben es auf Schwachstellen gescannt. Sie haben es in eine Registry gepusht. Jetzt kommt der Moment, der eine funktionierende Pipeline von einem echten Deployment trennt: den Container tatsächlich dort auszuführen, wo Nutzer ihn erreichen können.
Die Art und Weise, wie Sie den Container ausführen, hängt vollständig davon ab, wo er landen soll. Ein einzelner Server und ein Kubernetes-Cluster sehen auf dem Papier ähnlich aus – beide führen Container aus – aber die betriebliche Erfahrung ist völlig unterschiedlich. Die Wahl beeinflusst, wie Sie aktualisieren, wie Sie von Fehlern zurückkommen und wie viel manuelle Koordination Ihr Team bei jeder neuen Version benötigt.
Container auf einem einzelnen Server ausführen
Ein Deployment auf einem einzelnen Server sieht einfach aus. Sie verbinden sich per SSH mit der Maschine, führen docker run mit dem Image-Tag aus, den Sie gerade promoted haben, und die Anwendung startet. In einer Demo-Umgebung ist das die ganze Geschichte.
In der Praxis läuft auf einem einzelnen Server selten nur ein Container. Meistens haben Sie einen Anwendungscontainer, einen Datenbankcontainer, einen Cache, vielleicht einen Queue-Worker. Diese Container müssen in der richtigen Reihenfolge starten, über das richtige Netzwerk miteinander kommunizieren und den Fall abdecken, dass einer von ihnen abstürzt. Hier wird docker-compose nützlich. Sie definieren alle Dienste, ihre Abhängigkeiten, ihre Ports und ihre Neustartrichtlinien in einer einzigen Datei. Ein Befehl bringt alles in der richtigen Reihenfolge hoch.
Die eigentliche Herausforderung zeigt sich, wenn Sie die Anwendungsversion aktualisieren müssen. Auf einem einzelnen Server stoppen Sie den alten Container und starten den neuen. In diesem Fenster kann die Anwendung keine Anfragen bedienen. Wenn die Anwendung von echten Menschen genutzt wird, zählt diese Ausfallzeit.
Der einfachste Weg, Ausfallzeiten zu reduzieren, besteht darin, zwei Container parallel laufen zu lassen. Lassen Sie die alte Version laufen, während die neue Version startet. Sobald der neue Container bereit ist, Verbindungen anzunehmen, leiten Sie den Datenverkehr auf ihn um und stoppen dann den alten Container. Dies ist ein Rolling Update in seiner einfachsten Form. Sie können es manuell mit einem Skript oder mit einem Reverse-Proxy wie Nginx oder Traefik erledigen, der den Datenverkehr umschaltet.
Aber selbst mit einem Rolling-Update-Muster hat ein einzelner Server eine harte Grenze. Wenn der Server selbst ausfällt, fällt die Anwendung aus. Wenn Sie einen Sicherheitspatch auf das Host-Betriebssystem anwenden müssen, müssen Sie Ausfallzeiten einplanen. Für interne Tools, die von einem kleinen Team genutzt werden, ist dieser Kompromiss oft akzeptabel. Für kundenorientierte Anwendungen ist er das in der Regel nicht.
Container auf Kubernetes ausführen
Kubernetes behandelt die Probleme von Single-Server-Deployments als gelöste Probleme und baut darauf auf. Sie verwalten Container nicht direkt. Sie definieren ein Deployment-Objekt, das den gewünschten Zustand beschreibt: welches Image ausgeführt werden soll, wie viele Replikate, welche Health Checks verwendet werden sollen und wie Updates durchgeführt werden sollen.
Wenn Sie den Image-Tag in einem Deployment aktualisieren, stoppt Kubernetes nicht alles und startet neu. Es erstellt neue Pods mit dem neuen Image, wartet darauf, dass sie ihre Health Checks bestehen, und beendet dann nach und nach die alten Pods. Während des gesamten Prozesses ist immer mindestens ein Pod aktiv, der Datenverkehr bedient. Benutzer sehen keine Dienstunterbrechung.
Ein Pod ist die kleinste Einheit in Kubernetes. Er kann einen oder mehrere Container ausführen, aber die Kernidee ist, dass ein Pod ephemeral ist. Kubernetes erstellt Pods, zerstört sie und verschiebt sie bei Bedarf auf verschiedene Nodes. Sie denken nie darüber nach, auf welchem bestimmten Server ein Pod läuft. Der Cluster kümmert sich darum.
Der Unterschied zwischen einem einzelnen Server und Kubernetes liegt nicht nur in der Skalierung auf mehr Datenverkehr. Es geht darum, wer die Koordination übernimmt. Auf einem einzelnen Server legen Sie die Startreihenfolge, die Neustartrichtlinie und die Fehlerbehandlung fest. Sie schreiben Skripte oder verwenden docker-compose, um diese Entscheidungen durchzusetzen. Auf Kubernetes übernimmt der Orchestrator diese Koordination. Er überprüft regelmäßig die Pod-Gesundheit, startet fehlgeschlagene Pods neu und verteilt Pods auf gesunde Nodes, wenn ein Node ausfällt.
Diese Verschiebung der Verantwortung ändert die Arbeitsweise Ihres Teams. Sie hören auf, Skripte zu schreiben, die den Container-Lebenszyklus verwalten. Sie beginnen, Deployment-Manifeste zu schreiben, die den gewünschten Zustand beschreiben, und lassen den Cluster herausfinden, wie dieser Zustand erreicht werden kann.
So sieht ein minimales Deployment-Manifest in der Praxis aus:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-registry/my-app:v1.2.3
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
Dieses Manifest weist Kubernetes an, drei Replikate auszuführen, diese nacheinander zu aktualisieren und Datenverkehr nur dann an einen Pod zu leiten, nachdem sein /health-Endpunkt erfolgreich geantwortet hat.
Wie Sie zwischen den beiden wählen
Die Wahl zwischen einem einzelnen Server und Kubernetes ist kein Test der technischen Reinheit. Es ist eine Entscheidung, die auf betrieblichen Anforderungen basiert.
Das folgende Flussdiagramm kann Ihnen helfen zu entscheiden, welcher Weg zu Ihrer Situation passt:
Verwenden Sie einen einzelnen Server mit docker-compose, wenn:
- Die Anwendung von einem kleinen internen Team genutzt wird.
- Ausfallzeiten für Updates oder Wartung akzeptabel sind.
- Sie ein oder zwei Dienste verwalten.
- Sie nicht horizontal skalieren müssen.
- Ihr Team klein ist und Sie minimale Infrastrukturkomplexität wünschen.
Verwenden Sie Kubernetes, wenn:
- Die Anwendung auch während Updates verfügbar sein muss.
- Sie Dienste basierend auf dem Datenverkehr unabhängig skalieren müssen.
- Sie mehrere Dienste betreiben, die separat bereitgestellt und aktualisiert werden müssen.
- Sie eine automatisierte Wiederherstellung nach Node-Ausfällen wünschen.
- Ihr Team die operative Reife hat, einen Cluster zu verwalten.
Es gibt einen Mittelweg. Einige Teams betreiben einen kleinen Kubernetes-Cluster mit einem einzelnen Node unter Verwendung von Tools wie K3s oder MicroK8s. Dies bietet Ihnen die Rolling-Update- und Health-Check-Funktionen von Kubernetes ohne die volle Komplexität eines Multi-Node-Clusters. Es ist eine Überlegung wert, wenn Sie die Deployment-Muster wünschen, aber noch nicht den Maßstab benötigen.
Die eine Regel, die sich nie ändert
Unabhängig davon, wo Sie deployen, bleibt eine Regel bestehen: Das Image, das in der Produktion läuft, muss genau dasselbe Image sein, das alle Tests und Scans in der Pipeline bestanden hat.
Bauen Sie das Image niemals auf dem Server neu. Ziehen Sie niemals einen anderen Tag, weil "es dasselbe sein sollte". Lassen Sie niemals zu, dass jemand per SSH auf den Server geht und einen Container mit einem lokal modifizierten Image ausführt. Wenn das Image in der Registry nicht das Image ist, das läuft, haben Sie die Fähigkeit verloren, zu reproduzieren, zu auditieren und zurückzurollen.
Deshalb sind Image-Tagging und Promotion wichtig. Wenn Sie ein Image vom Staging in die Produktion promoten, bauen Sie nichts neu. Sie ändern lediglich, welche Umgebung diesen bestimmten Tag ziehen darf. Die Bytes sind identisch.
Praktische Checkliste für das Container-Deployment
Bevor Sie einen Container in einer Umgebung deployen, bestätigen Sie diese Punkte:
- Der Image-Tag im Deployment stimmt mit dem Tag überein, der die Pipeline bestanden hat.
- Der Container hat einen Health-Check-Endpunkt, der dem Orchestrator mitteilt, wann er bereit ist.
- Die Umgebungsvariablen und Secrets sind korrekt für die Zielumgebung gesetzt.
- Die Update-Strategie ist definiert: Rolling Update für Null-Ausfallzeit, Recreate für einfache Fälle.
- Sie haben eine Möglichkeit zu sehen, welche Version des Images gerade läuft.
- Sie haben einen Rollback-Plan: entweder ein vorheriger Image-Tag oder ein vorheriges Deployment-Manifest.
Was als Nächstes kommt
Den Container auszuführen ist nur die halbe Arbeit. Sobald er läuft, müssen Sie wissen, welche Version tatsächlich Datenverkehr bedient, ob sie gesund ist und was zu tun ist, wenn die neue Version ein Problem hat. Hier kommen Image-Version-Tracking und Rollback ins Spiel. Das sind die Themen für den nächsten Teil dieser Diskussion.
Für jetzt ist das Wichtigste, das Deployment-Ziel zu wählen, das Ihrer betrieblichen Realität entspricht, und sicherzustellen, dass das Image, das Sie ausführen, das Image ist, das Sie getestet haben. Alles andere ergibt sich daraus.