Anwendungen aktualisieren und zurücksetzen

KubernetesBeginner
Jetzt üben

Einführung

Sie können inzwischen eine Anwendung bereitstellen, öffentlich zugänglich machen und skalieren. Die nächste betriebliche Frage lautet: Wie lässt sich die Anwendungsversion austauschen, ohne den gesamten Dienst außer Betrieb zu nehmen?

Ein Kubernetes-Deployment löst dieses Problem mit einem Rolling Update. Sobald sich sein Pod-Template ändert, erstellt das Deployment ein neues ReplicaSet, startet nach und nach neue Pods und entfernt alte Pods erst, wenn der Ersatz verfügbar ist. Der Service behält während des gesamten Vorgangs dieselbe stabile Adresse.

In diesem Lab migrieren Sie eine NGINX-Anwendung von einem festgelegten Image zu einem anderen, stellen den Zusammenhang zwischen Deployment-Revisionen, ReplicaSets und Pods her, simulieren absichtlich eine fehlerhafte Veröffentlichung und setzen anschließend auf die letzte fehlerfreie Revision zurück. Außerdem halten Sie das gespeicherte Manifest konsistent mit dem wiederhergestellten Live-Zustand – eine wichtige Gewohnheit, die verhindert, dass ein späteres kubectl apply den Fehler erneut einführt.

Die Ausgangsumgebung prüfen

Start der Umgebung: Dieses Lab startet einen vollständigen Kubernetes-Cluster für Sie. Die Konfiguration der Steuerungsebene, des Knotens und der Netzwerkkomponenten dauert normalerweise 2–3 Minuten. Bitte warten Sie geduldig, bis die Umgebung vollständig geladen ist, bevor Sie beginnen.

In diesem Schritt überprüfen Sie die Clusterverbindung und richten einen HTTP-Client innerhalb des Clusters ein.

In dieser Kursumgebung ist bereits ein laufender Kubernetes-Cluster vorhanden, daher müssen Sie keinen eigenen erstellen. Überprüfen Sie zunächst, wohin kubectl seine Befehle sendet. config current-context nennt die aktive Verbindung, get node liest den Zustand des Knotens aus und version gibt die Versionen des lokalen Clients sowie des erreichbaren API-Servers aus:

kubectl config current-context
kubectl get node
kubectl version

Der Kontext und der Knoten heißen labex-v135, der Knoten befindet sich im Zustand Ready und der Server meldet Kubernetes v1.35.x. Ein Kontext ist die in kubeconfig ausgewählte Verbindung, über die kubectl mit einem bestimmten Cluster, Benutzer und Standard-Namespace verbunden wird.

Erstellen Sie nun einen kleinen Client-Pod. Später verwenden Sie ihn, um die Anwendung über ihren Service zu erreichen. kubectl run erstellt einen Pod, --image wählt BusyBox aus, --restart=Never belässt ihn als eigenständigen Pod und der Trenner -- leitet den lang laufenden Befehl sleep 3600 ein. kubectl wait wartet anschließend höchstens 30 Sekunden auf den Zustand Ready:

kubectl run release-client --image=busybox:1.36 --image-pull-policy=IfNotPresent \
  --restart=Never -- sleep 3600
kubectl wait --for=condition=Ready pod/release-client --timeout=30s

Damit ist der aufrufende Client von den Web-Pods getrennt – ähnlich wie bei einem Workload, der innerhalb eines Clusters einen anderen Workload aufruft.

Die stabile Ausgangsversion bereitstellen

In diesem Schritt richten Sie eine bekannte fehlerfreie Anwendungsversion ein, bevor Sie Änderungen vornehmen.

Bevor Sie ein Update üben, legen Sie eine fehlerfreie Ausgangsversion fest. Erstellen Sie ein Manifest mit einem Deployment aus drei Replikaten und einem stabilen ClusterIP-Service.

Mit cd wechseln Sie in den vorbereiteten Arbeitsbereich. Die Here-Document-Syntax cat <<'EOF' > release-web.yaml schreibt alles bis zum abschließenden EOF in die Datei. Die Zeile --- trennt zwei Kubernetes-Objekte innerhalb einer YAML-Datei:

cd /home/labex/project/update-lab
cat <<'EOF' > release-web.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: release-web
  annotations:
    kubernetes.io/change-cause: "Initial release: nginx 1.26"
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
  selector:
    matchLabels:
      app: release-web
  template:
    metadata:
      labels:
        app: release-web
    spec:
      containers:
        - name: nginx
          image: nginx:1.26-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: release-web
spec:
  selector:
    app: release-web
  ports:
    - name: http
      port: 80
      targetPort: http
EOF
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment,service,pods -l app=release-web

Das Deployment verwaltet die Pods, während der Service sie unabhängig davon über ihr Label auswählt. Eine Aktualisierung des Deployments ändert daher nicht die Adresse des Service.

Überprüfen Sie die Ausgangsversion vom Client aus. Bei kubectl exec POD -- COMMAND trennt -- die Argumente von kubectl vom Befehl, der im Container ausgeführt wird. wget -qO- ruft die Antwort ohne zusätzliche Ausgaben ab und schreibt sie auf die Standardausgabe. Die Pipe übergibt die Antwort an head, sodass nur deren Anfang angezeigt wird:

kubectl exec release-client -- wget -qO- http://release-web | head

Ein neues Image deklarativ veröffentlichen

In diesem Schritt ändern Sie das gespeicherte Pod-Template und lassen das Deployment ein Rolling Update durchführen.

Ein Deployment startet einen Rollout, sobald sich sein Pod-Template ändert. Das Image befindet sich innerhalb dieses Templates. Eine Änderung erzeugt daher eine neue Revision und ein neues ReplicaSet.

Aktualisieren Sie sowohl das Image als auch den lesbaren Änderungsgrund im gespeicherten Manifest. Jede Ersetzung mit sed -i 's/old/new/' file bearbeitet die Datei direkt. grep -nE zeigt anschließend die Zeilennummern für einen der beiden Begriffe des erweiterten regulären Ausdrucks (change-cause oder image:), sodass Sie beide Änderungen vor der Anwendung prüfen können:

cd /home/labex/project/update-lab
sed -i 's/Initial release: nginx 1.26/Release nginx 1.27/' release-web.yaml
sed -i 's/nginx:1.26-alpine/nginx:1.27-alpine/' release-web.yaml
grep -nE 'change-cause|image:' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s

kubectl apply ändert den gewünschten Zustand. Der Deployment-Controller arbeitet anschließend asynchron weiter, bis alle drei aktualisierten Replikate verfügbar sind. Für eine übersichtliche Pod-Tabelle wählt -l das Anwendungslabel aus, während -o custom-columns Überschriften den Feldpfaden der Objekte zuordnet:

kubectl get deployment release-web
kubectl get pods -l app=release-web \
  -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,READY:.status.containerStatuses[0].ready'

Alle drei aktuellen Replikate verwenden das neue Image. Nach einem erfolgreichen Rollout kann kurzzeitig noch ein Pod mit dem alten Image im Zustand Terminating erscheinen. Dieser Pod ist kein gewünschtes Replikat mehr; Kubernetes beendet ihn lediglich noch kontrolliert im Hintergrund.

Revisionen, ReplicaSets und Pods zusammenführen

In diesem Schritt untersuchen Sie die Objekte hinter dem erfolgreichen Rollout und bestätigen, dass der Service verfügbar geblieben ist.

Der Rollout ist abgeschlossen. Für den Betrieb ist es jedoch hilfreicher zu verstehen, was sich geändert hat, als lediglich „Erfolg“ zu sehen. kubectl rollout history liest die gespeicherten Deployment-Revisionen aus. Der folgende Befehl get replicasets verwendet -l, um zugehörige Objekte auszuwählen, und custom-columns, um ihre Replikazahlen und Images zu vergleichen:

kubectl rollout history deployment/release-web
kubectl get replicasets -l app=release-web \
  -o custom-columns='NAME:.metadata.name,DESIRED:.spec.replicas,CURRENT:.status.replicas,READY:.status.readyReplicas,IMAGE:.spec.template.spec.containers[0].image'

Sie sollten zwei ReplicaSets sehen. Das neue verwaltet drei Pods; das alte bleibt bei null Replikaten, damit sein Pod-Template für ein Zurücksetzen verfügbar bleibt. Der Name eines ReplicaSets enthält einen aus dem Pod-Template abgeleiteten Hash. Deshalb haben sich die Pod-Namen geändert, als das Image geändert wurde.

Prüfen Sie das aktive Image und die Anzahl der Backends des Service. Beide JSONPath-Ausdrücke verwenden range, um eine Ausgabevorlage für jedes Element einer Liste zu wiederholen. Leerzeichen und {"\n"} sorgen für eine gut lesbare Zeile pro Pod beziehungsweise Endpunkt:

kubectl get pods -l app=release-web \
  -o jsonpath='{range .items[*]}{.metadata.name}{"  "}{.spec.containers[0].image}{"\n"}{end}'
kubectl get endpointslices -l kubernetes.io/service-name=release-web \
  -o jsonpath='{range .items[*].endpoints[*]}{.addresses[0]}{" ready="}{.conditions.ready}{"\n"}{end}'
kubectl exec release-client -- wget -qO- http://release-web | head

Die Revision hat sich geändert, aber der Service verfügt weiterhin über drei bereite Backends und denselben stabilen Namen.

Eine fehlerhafte Veröffentlichung diagnostizieren

In diesem Schritt führen Sie einen kontrollierten Image-Fehler ein und verwenden Pod-Ereignisse, um zu ermitteln, warum der Rollout nicht abgeschlossen werden kann.

Simulieren Sie nun einen häufigen Fehler bei Veröffentlichungen: ein Container-Image mit einem nicht vorhandenen Tag. Verwenden Sie weiterhin dasselbe Deployment, damit der Rollout-Mechanismus eine wichtige Sicherheitsfunktion demonstrieren kann. Die beiden Befehle mit sed -i bearbeiten die Annotation und den Image-Text direkt. Der erwartete Timeout des Rollouts würde normalerweise einen Fehlerstatus zurückgeben; || true sorgt absichtlich dafür, dass die Lektion fortgesetzt werden kann:

cd /home/labex/project/update-lab
sed -i 's/Release nginx 1.27/Broken release: missing image/' release-web.yaml
sed -i 's/nginx:1.27-alpine/nginx:does-not-exist-course/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=20s || true
kubectl get deployment release-web
kubectl get pods -l app=release-web

Der Rollout wird nicht abgeschlossen, weil ein neuer Pod sein Image nicht abrufen kann. || true ermöglicht, dass die Lektion nach dem erwarteten Timeout fortgesetzt wird.

Ermitteln Sie den fehlerhaften Pod und untersuchen Sie seine Ereignisse. $(...) speichert die Befehlsausgabe in BAD_POD. Die erste Pipe übergibt die Kubernetes-JSON-Ausgabe an jq; select(...) behält den Pod mit dem fehlerhaften Image, und -r gibt dessen Namen als reinen Text aus. Eine zweite Pipe zu head -n1 behält nur einen Namen. Schließlich gibt sed -n '/Events:/,$p' die Ausgabe von describe ab Events: bis zum Ende aus:

BAD_POD=$(kubectl get pods -l app=release-web \
  -o json | jq -r '.items[] | select(.spec.containers[0].image == "nginx:does-not-exist-course") | .metadata.name' | head -n1)
echo "$BAD_POD"
kubectl describe pod "$BAD_POD" | sed -n '/Events:/,$p'
kubectl get replicasets -l app=release-web

Achten Sie auf ErrImagePull oder ImagePullBackOff. Beachten Sie, dass das vorherige fehlerfreie ReplicaSet weiterhin verfügbare Pods besitzt und der Service daher weiterhin antworten kann:

kubectl exec release-client -- wget -qO- http://release-web | head

Das zeigt die Aufrechterhaltung der Verfügbarkeit während eines fehlgeschlagenen Rollouts – nicht, dass die neue Veröffentlichung funktioniert.

Zurücksetzen und Manifest abgleichen

In diesem Schritt stellen Sie die vorherige fehlerfreie Revision wieder her und reparieren anschließend das gespeicherte Manifest.

Das aktive Deployment verfügt über eine Revisionshistorie, sodass Kubernetes das vorherige Pod-Template wiederherstellen kann. rollout undo weist den Deployment-Controller an, die vorherige Revision erneut zu verwenden; rollout status wartet auf die Wiederherstellung, und custom-columns gibt das wiederhergestellte Image sowie die Zahl der bereiten Replikate aus:

kubectl rollout history deployment/release-web
kubectl rollout undo deployment/release-web
kubectl rollout status deployment/release-web --timeout=60s
kubectl get deployment release-web \
  -o custom-columns='NAME:.metadata.name,IMAGE:.spec.template.spec.containers[0].image,READY:.status.readyReplicas'

Das Image ist wieder fehlerfrei und drei Replikate sind bereit. rollout undo erstellt aus einem älteren Pod-Template eine neue Revision; der Revisionszähler wird dabei nicht zurückgesetzt.

Es bleibt noch eine Aufgabe. Die YAML-Datei enthält weiterhin das fehlerhafte Image, sodass ein späteres kubectl apply das Deployment erneut beschädigen würde. Gleichen Sie den gespeicherten gewünschten Zustand mit dem wiederhergestellten Live-Zustand ab. Die Ersetzungen reparieren die Datei, apply synchronisiert sie, und der abschließende Historienbefehl bestätigt den resultierenden Revisionsverlauf:

cd /home/labex/project/update-lab
sed -i 's/Broken release: missing image/Rollback to nginx 1.27/' release-web.yaml
sed -i 's/nginx:does-not-exist-course/nginx:1.27-alpine/' release-web.yaml
kubectl apply -f release-web.yaml
kubectl rollout status deployment/release-web --timeout=60s
grep -nE 'change-cause|image:' release-web.yaml
kubectl rollout history deployment/release-web

Nun beschreiben sowohl der Cluster als auch die Datei dieselbe fehlerfreie Veröffentlichung.

Ein sichereres Update-Budget festlegen

In diesem Schritt machen Sie die Kapazitäts- und Verfügbarkeitsgrenzen des Deployments während eines Rollouts explizit.

Die Einstellungen der Deployment-Strategie legen fest, welche temporäre Kapazität bei künftigen Updates zulässig ist:

  • maxUnavailable gibt an, wie viele der gewünschten Replikate während eines Rollouts nicht verfügbar sein dürfen.
  • maxSurge gibt an, wie viele zusätzliche Pods vorübergehend über die gewünschte Replikazahl hinaus existieren dürfen.

Für diese kleine Anwendung mit drei Replikaten sollen alle drei gewünschten Replikate verfügbar bleiben und ein zusätzlicher Pod erlaubt werden.

Der Befehl sed verwendet eine Adresse, /type: RollingUpdate/, um die Strategiezeile zu finden. Die Aktion a\ fügt das folgende, durch Zeilenumbrüche getrennte YAML mit der erforderlichen Einrückung ein. Nach der Anwendung extrahiert JSONPath beide Strategiewerte, sodass Sie die Änderung prüfen können, ohne das vollständige Objekt durchsuchen zu müssen:

cd /home/labex/project/update-lab
sed -i '/type: RollingUpdate/a\    rollingUpdate:\n      maxUnavailable: 0\n      maxSurge: 1' release-web.yaml
kubectl apply -f release-web.yaml
kubectl get deployment release-web \
  -o jsonpath='maxUnavailable={.spec.strategy.rollingUpdate.maxUnavailable}{"\n"}maxSurge={.spec.strategy.rollingUpdate.maxSurge}{"\n"}'
kubectl get deployment release-web

Die Änderung ausschließlich der Strategiefelder ersetzt keine Pods, da sich das Pod-Template nicht geändert hat. Bei einem künftigen Image-Update kann Kubernetes einen zusätzlichen Pod erstellen und sollte die Zahl der verfügbaren Replikate nicht absichtlich unter drei senken.

Diese Einstellung bevorzugt die Verfügbarkeit, erfordert jedoch freie Clusterkapazität. Einen allgemein besten Wert gibt es nicht: Größere Anwendungen verwenden häufig Prozentangaben, und die konkrete Wahl hängt von der Kapazität, der Startzeit und der akzeptablen Unterbrechung ab.

Zusammenfassung

Sie haben eine Anwendungsveröffentlichung über ihren vollständigen betrieblichen Lebenszyklus begleitet:

  • eine fehlerfreie Deployment- und stabile Service-Ausgangsbasis eingerichtet;
  • ein festgelegtes Image deklarativ geändert und den Rollout abgewartet;
  • Deployment-Revisionen mit alten und neuen ReplicaSets verknüpft;
  • einen Fehler beim Abrufen des Images diagnostiziert, während die vorherige Veröffentlichung weiterhin Anfragen bediente;
  • auf das letzte fehlerfreie Pod-Template zurückgesetzt;
  • das Manifest abgeglichen, sodass ein späteres Anwenden das fehlerhafte Image nicht erneut einführt; und
  • ein explizites Budget für Verfügbarkeit und zusätzliche Pods bei künftigen Updates konfiguriert.

Der zentrale Gedanke ist, dass ein Deployment den gewünschten Zustand über die Zeit hinweg verwaltet. Ein Rollout ist nicht lediglich eine Image-Änderung, sondern ein kontrollierter Übergang zwischen ReplicaSets, den Sie beobachten, überprüfen und bei Bedarf zurücknehmen können.