Anwendungen auf Kubernetes bereitstellen

KubernetesBeginner
Jetzt üben

Einführung

Im ersten Abschnitt des Kurses haben Sie einen Kubernetes-Cluster untersucht, ohne Änderungen daran vorzunehmen. Sie haben gelernt, dass kubectl Anfragen an den API-Server sendet und dass Kubernetes-Controller den gewünschten Zustand fortlaufend mit dem tatsächlichen Zustand vergleichen.

Nun stellen Sie Ihre ersten Anwendungsanfragen. Anstatt Kubernetes jede einzelne Aktion auf niedriger Ebene vorzugeben, beschreiben Sie in YAML-Dateien, den sogenannten Manifesten, das gewünschte Ergebnis. Kubernetes speichert diese Objektdefinitionen und sorgt dafür, dass sie umgesetzt werden.

Sie beginnen mit einem einzelnen Pod, damit die grundlegende Struktur eines Manifests leicht erkennbar ist. Anschließend definieren Sie ein Deployment, das zwei Pods verwaltet. Der Vergleich zwischen dem eigenständigen Pod und den von einem Deployment verwalteten Pods zeigt, warum für Anwendungen normalerweise übergeordnete Controller bevorzugt werden.

Dieses Lab konzentriert sich bewusst auf das Erstellen von Workloads. Wie Sie Anwendungen über Services erreichbar machen, lernen Sie später, sobald Sie mit Pods, Labels und Deployments vertraut sind.

Deklarative Kubernetes-Objekte verstehen

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 verknüpfen Sie das Konzept des gewünschten Zustands aus dem vorherigen Lab mit Kubernetes-Manifesten und richten einen Arbeitsbereich für Ihre ersten Anwendungsdefinitionen ein.

Von Befehlen zum gewünschten Zustand

Kubernetes unterstützt zwei grundlegende Verwaltungsarten:

  • Mit einem imperativen Befehl fordern Sie direkt eine Aktion an, zum Beispiel „Erstelle einen Pod namens first-nginx“.
  • Mit einem deklarativen Manifest speichern Sie die gewünschte Objektkonfiguration in einer Datei und weisen Kubernetes an, den Cluster daran anzupassen.

Deklarative Dateien sind besonders nützlich, weil Sie sie vor einer Änderung lesen, wiederholt anwenden, Unterschiede überprüfen und in einer Versionsverwaltung speichern können. In diesem Kurs liegt der Schwerpunkt auf dem deklarativen Ansatz.

Jedes von der API zurückgegebene Kubernetes-Objekt enthält wichtige Felder auf oberster Ebene:

  • apiVersion wählt die Kubernetes-API-Gruppe und -Version aus.
  • kind bezeichnet den Objekttyp, zum Beispiel Pod oder Deployment.
  • metadata beschreibt die Identität des Objekts, einschließlich Name und Labels.
  • spec beschreibt den gewünschten Zustand des Objekts.
  • status enthält den beobachteten Zustand und wird normalerweise nach der Erstellung von Kubernetes ausgefüllt, nicht in Ihrem Manifest definiert.

Daraus ergibt sich ein wichtiger Kreislauf:

manifest spec -> API server stores desired state -> controllers act -> object status reports actual state

Manifestverzeichnis vorbereiten

Wechseln Sie in das für dieses Lab vorbereitete Verzeichnis:

cd /home/labex/project/k8s-manifests

Überprüfen Sie Ihren aktuellen Speicherort mit pwd, was für print working directory steht:

pwd
/home/labex/project/k8s-manifests

Erstellen Sie eine kurze Notizdatei mit den vier Manifestfeldern, die Sie verwenden werden. Der Befehl printf schreibt jede Zeichenkette in Anführungszeichen als separate Zeile, und > leitet diese Ausgabe in eine Datei um. Eine bereits vorhandene Datei wird dabei ersetzt.

printf '%s\n' apiVersion kind metadata spec > manifest-fields.txt

Geben Sie die Datei aus, um sie zu überprüfen:

cat manifest-fields.txt
apiVersion
kind
metadata
spec

Die Notizdatei dient als kleiner Lernmeilenstein: Diese vier Felder werden in beiden Manifesten vorkommen, die Sie als Nächstes erstellen.

Ein Pod-Manifest schreiben und validieren

In diesem Schritt schreiben Sie ein YAML-Manifest für einen Pod und überprüfen dessen Struktur, bevor Sie es an den Cluster senden.

Den Pod kennenlernen

Ein Pod ist das kleinste bereitstellbare Kubernetes-Objekt. Ein Pod stellt einem oder mehreren eng miteinander verbundenen Containern eine gemeinsame Netzwerkidentität und einen gemeinsamen Speicherkontext zur Verfügung. In den meisten Einsteigerbeispielen wird pro Pod ein Container verwendet.

Der Pod in diesem Lab führt NGINX aus, einen kleinen Webserver. Das Image ist auf nginx:1.27-alpine festgelegt. Durch die feste Versionsangabe ist das Ergebnis reproduzierbarer als bei Verwendung des veränderlichen Tags latest. Das Image wurde bereits im Cluster zwischengespeichert, sodass die Arbeit nicht von einem Internet-Download abhängt.

Die YAML-Datei erstellen

Stellen Sie sicher, dass Sie sich im Manifestverzeichnis befinden:

cd /home/labex/project/k8s-manifests

Sie verwenden ein Here-Dokument, um die Datei zu erstellen. Die Shell leitet jede Zeile zwischen <<'EOF' und dem abschließenden EOF in first-pod.yaml um. Die Anführungszeichen beim ersten EOF verhindern, dass die Shell Sonderzeichen innerhalb des YAML-Inhalts interpretiert.

cat <<'EOF' > first-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: first-nginx
  namespace: default
  labels:
    app: first-nginx
spec:
  containers:
    - name: nginx
      image: nginx:1.27-alpine
      imagePullPolicy: IfNotPresent
      ports:
        - name: http
          containerPort: 80
          protocol: TCP
EOF

YAML bildet Hierarchien durch Einrückungen ab. Verwenden Sie konsequent Leerzeichen; Tabulatoren können YAML ungültig machen. Ein Bindestrich wie in - name: nginx leitet einen Listeneintrag ein.

Lesen Sie das Objekt von oben nach unten:

  • apiVersion: v1 wählt die für Pods verwendete zentrale API aus.
  • kind: Pod legt den Ressourcentyp fest.
  • metadata.name gibt dem Pod den stabilen Namen first-nginx.
  • metadata.namespace: default ordnet ihn dem normalen Anwendungs-Namespace des Kurses und nicht einem System-Namespace zu.
  • metadata.labels weist das Label app=first-nginx zu, über das der Pod später ausgewählt werden kann.
  • spec.containers ist eine Liste der Container, die der Pod ausführen soll.
  • imagePullPolicy: IfNotPresent verwendet das zwischengespeicherte Image, sofern es verfügbar ist.
  • Der benannte Port http verwendet containerPort: 80 und protocol: TCP. Er dokumentiert, an welcher Stelle NGINX innerhalb des Containers lauscht; der Pod wird dadurch nicht außerhalb des Clusters erreichbar.

Vor der Erstellung validieren

Verwenden Sie einen clientseitigen Probelauf, um die Datei zu analysieren, ohne den Pod zu erstellen. Die Option -f steht für file, und --dry-run=client hält die Anfrage lokal:

kubectl apply --dry-run=client -f first-pod.yaml
pod/first-nginx created (dry run)

Die Wörter dry run sind entscheidend: Die Syntax ist gültig, aber der Cluster wurde noch nicht geändert.

Lassen Sie kubectl das normalisierte Objekt als YAML ausgeben:

Die Ausgabeoption -o steht für output format. Mit yaml weisen Sie kubectl an, das analysierte Objekt als YAML darzustellen, anstatt nur eine einzeilige Zusammenfassung auszugeben:

kubectl apply --dry-run=client -f first-pod.yaml -o yaml

Sie sehen Ihre Felder sowie vom Client ergänzte Standardwerte. So lassen sich Fehler bei Einrückungen, Feldnamen und Datentypen vor einer tatsächlichen Anwendung erkennen.

Den ersten Pod erstellen und untersuchen

In diesem Schritt wenden Sie das validierte Manifest an, beobachten, wie Kubernetes den Pod in Richtung des gewünschten Zustands bewegt, und untersuchen anschließend das erstellte Objekt.

Das Manifest anwenden

Wechseln Sie bei Bedarf in das Manifestverzeichnis:

cd /home/labex/project/k8s-manifests

Wenden Sie die Datei ohne die Probelaufoption an:

kubectl apply -f first-pod.yaml
pod/first-nginx created

kubectl apply sendet das Objekt an den API-Server. Der API-Server speichert die gewünschte Pod-Spezifikation, während Scheduler und Kubelet zusammenarbeiten, um den Pod auf dem Knoten auszuführen.

Auf die Bereitschaft warten

Die Erstellung eines Pods erfolgt asynchron: kubectl apply kann zurückkehren, bevor der Container bereit ist. Verwenden Sie kubectl wait, um auf die Bedingung Ready des Pods zu warten. Der Befehl wird erfolgreich beendet, sobald die Bedingung erfüllt ist, oder schlägt nach 60 Sekunden fehl:

kubectl wait --for=condition=Ready pod/first-nginx --timeout=60s
pod/first-nginx condition met

Listen Sie nun den Pod auf. Dieses Lab verwendet erneut -o wide, weil jedes Lab eigenständig nutzbar sein soll: -o wählt ein Ausgabeformat, und wide ergänzt Felder wie Pod-IP und Knotennamen:

kubectl get pod first-nginx -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP           NODE
first-nginx   1/1     Running   ...        ...   ...          labex-v135

READY=1/1 bedeutet, dass der eine Container bereit ist, während STATUS=Running die Phase des Pods angibt. Die erweiterte Ansicht zeigt außerdem die Pod-IP und den zugewiesenen Knoten. Pod-IPs und Altersangaben werden dynamisch erzeugt und können daher abweichen.

Labels und Besitzverhältnisse untersuchen

Geben Sie die Labels des Pods aus:

kubectl get pod first-nginx --show-labels

Suchen Sie nach app=first-nginx. Labels werden zusammen mit dem Objekt gespeichert und werden wichtig, sobald Deployments und Services Pods auswählen.

Fragen Sie Kubernetes, ob ein anderes Objekt diesen Pod steuert. -o jsonpath='...' extrahiert ausgewählte Felder, anstatt das gesamte Objekt auszugeben. Der Ausdruck folgt metadata.ownerReferences; {"\n"} fügt einen abschließenden Zeilenumbruch ein, damit die Shell-Eingabeaufforderung in der nächsten Zeile erscheint:

kubectl get pod first-nginx -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}{"\n"}'
Owner:

Das Besitzerfeld ist leer, weil Sie diesen eigenständigen Pod direkt erstellt haben. Wenn er gelöscht wird, weiß kein übergeordneter Controller, dass er ersetzt werden sollte. In den nächsten Schritten vergleichen Sie dieses Verhalten mit verwalteten Pods.

Meilenstein

Sie haben eine lokale Datei mit dem gewünschten Zustand in ein laufendes Kubernetes-Objekt überführt. Die API hat den Pod akzeptiert, der Scheduler hat ihn zugewiesen und das Kubelet hat seinen Container bereitgestellt. Der Pod existiert, aber kein Controller verwaltet seinen Lebenszyklus.

Ein Deployment definieren

In diesem Schritt definieren Sie ein Deployment, das Kubernetes anweist, zwei Kopien eines NGINX-Pods aufrechtzuerhalten.

Warum ein Deployment verwenden?

Ein eigenständiger Pod eignet sich gut zum Lernen, Anwendungen benötigen jedoch normalerweise einen Controller. Ein Deployment definiert die gewünschte Anzahl von Replikaten sowie eine Pod-Vorlage. Es erstellt ein ReplicaSet, das die angeforderten Pods verwaltet.

Die Besitzkette lautet:

Deployment -> ReplicaSet -> Pods -> containers

Wenn ein verwalteter Pod verschwindet, erkennt das ReplicaSet, dass die tatsächliche Anzahl der Replikate unter der gewünschten Anzahl liegt, und erstellt einen Ersatz. In späteren Labs verwenden Sie Deployments zum Skalieren und für fortlaufende Aktualisierungen.

Das Deployment-Manifest erstellen

Wechseln Sie zurück in das Manifestverzeichnis:

cd /home/labex/project/k8s-manifests

Erstellen Sie course-web-deployment.yaml mit einem Here-Dokument:

cat <<'EOF' > course-web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: course-web
  labels:
    app: course-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: course-web
  template:
    metadata:
      labels:
        app: course-web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27-alpine
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 80
EOF

Das Deployment verwendet apps/v1, die stabile API für Deployments. Seine spec führt drei wichtige Felder ein:

  • replicas: 2 gibt die gewünschte Anzahl von Pods an.
  • selector.matchLabels identifiziert die Pods, die das Deployment verwaltet.
  • template ist die Vorlage, aus der jeder Pod erstellt wird.

Der Selektor und template.metadata.labels verwenden beide app: course-web. Sie müssen übereinstimmen, damit das Deployment die aus seiner eigenen Vorlage erstellten Pods identifizieren kann.

Das Deployment validieren

Analysieren Sie das Manifest, ohne den Cluster zu ändern:

kubectl apply --dry-run=client -f course-web-deployment.yaml
deployment.apps/course-web created (dry run)

Verwenden Sie kubectl diff, um das Manifest mit dem aktuellen Zustand zu vergleichen. Ein Exit-Code ungleich null bedeutet lediglich, dass sich das Objekt ändern würde; || true verhindert, dass die Shell diesen erwarteten Unterschied als Fehler behandelt:

kubectl diff -f course-web-deployment.yaml || true

Da course-web noch nicht existiert, zeigt die Ausgabe das vollständige Objekt als Hinzufügung. Die entsprechenden Zeilen beginnen mit +. Im Gegensatz zu apply nimmt diff keine Änderungen am Cluster vor.

Die verwaltete Anwendung bereitstellen und untersuchen

In diesem Schritt wenden Sie das Deployment an, warten auf seine beiden Replikate und untersuchen die zugehörigen Ressourcen anhand ihres gemeinsamen Labels.

Deployment anwenden und warten

Wechseln Sie zunächst in das Verzeichnis mit dem Manifest. Wenden Sie anschließend den gespeicherten gewünschten Zustand an; -f weist kubectl an, die Datei zu lesen:

cd /home/labex/project/k8s-manifests
kubectl apply -f course-web-deployment.yaml
deployment.apps/course-web created

Warten Sie, bis der Rollout des Deployments abgeschlossen ist. Ein Rollout bezeichnet den Vorgang, bei dem die Pods des Deployments an die gewünschte Vorlage und Replikatanzahl angepasst werden:

kubectl rollout status deployment/course-web --timeout=60s
deployment "course-web" successfully rolled out

Listen Sie das Deployment auf:

kubectl get deployment course-web
NAME         READY   UP-TO-DATE   AVAILABLE   AGE
course-web   2/2     2            2           ...

READY=2/2 bedeutet, dass beide gewünschten Replikate bereit sind. UP-TO-DATE=2 bedeutet, dass beide die aktuelle Pod-Vorlage verwenden, und AVAILABLE=2 bedeutet, dass beide verfügbar sind.

Zugehörige Ressourcen untersuchen

Verwenden Sie den Label-Selektor -l app=course-web, um zugehörige Ressourcen aufzulisten:

kubectl get deployment,replicaset,pods -l app=course-web

Die Ausgabe enthält ein Deployment, ein ReplicaSet und zwei Pods. Die automatisch erzeugten Suffixe von ReplicaSet und Pods können abweichen:

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/course-web   2/2     2            2           ...

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/course-web-...          2         2         2       ...

NAME                              READY   STATUS    RESTARTS   AGE
pod/course-web-...-...            1/1     Running   ...        ...
pod/course-web-...-...            1/1     Running   ...        ...

Diese Ansicht bestätigt, dass aus den gewünschten zwei Replikaten des Deployments zwei bereite Pods geworden sind. Im nächsten Schritt verfolgen Sie die Besitzverbindungen zwischen diesen Ressourcen.

Meilenstein

Sie haben ein Deployment-Manifest angewendet und auf den gewünschten Zustand gewartet. Kubernetes hat ein ReplicaSet und zwei Pods erstellt. Das gemeinsame Label app=course-web ermöglichte es Ihnen, sie als eine zusammengehörige Anwendungsgruppe aufzulisten.

Besitzverhältnisse zwischen Controllern verfolgen

In diesem Schritt folgen Sie den Owner-Referenzen von einem verwalteten Pod zu seinem ReplicaSet und anschließend zum Deployment. Außerdem wenden Sie das Manifest erneut an, um die deklarative Idempotenz zu beobachten.

Besitzer eines Pods untersuchen

Speichern Sie den Namen eines automatisch erzeugten Pods in einer Shell-Variablen. Die Syntax NAME=$(command) ist eine Befehlsersetzung: Die Shell führt den Befehl aus und speichert dessen Ausgabe in NAME. Hier wählt -l app=course-web passende Pods aus, während JSONPath den automatisch erzeugten Namen des ersten Pods extrahiert:

POD_NAME=$(kubectl get pods -l app=course-web -o jsonpath='{.items[0].metadata.name}')

Geben Sie den Namen aus, damit Sie wissen, welcher Pod ausgewählt wurde:

echo "$POD_NAME"

Untersuchen Sie nun seinen direkten Besitzer. Durch die Anführungszeichen um "$POD_NAME" wird der gespeicherte Name sicher als ein einziges Befehlsargument übergeben:

kubectl get pod "$POD_NAME" -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
Owner: ReplicaSet/course-web-...

Anders als der eigenständige Pod first-nginx besitzt ein von einem Deployment verwalteter Pod ein ReplicaSet als Besitzer. Das ReplicaSet selbst gehört dem Deployment.

Geben Sie den Besitzer des ReplicaSets aus. Der erste Befehl verwendet erneut dasselbe Muster der Befehlsersetzung, speichert diesmal jedoch den Namen eines ReplicaSets in RS_NAME:

RS_NAME=$(kubectl get replicaset -l app=course-web -o jsonpath='{.items[0].metadata.name}')
kubectl get replicaset "$RS_NAME" -o jsonpath='Owner: {.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
Owner: Deployment/course-web

Den gewünschten Zustand erneut anwenden

Wenden Sie dasselbe Manifest erneut an:

kubectl apply -f course-web-deployment.yaml
deployment.apps/course-web unchanged

unchanged veranschaulicht eine wichtige Eigenschaft des deklarativen Ansatzes: Es ist sicher, denselben gewünschten Zustand wiederholt anzuwenden. Kubernetes muss nur handeln, wenn sich der gewünschte und der tatsächliche Zustand unterscheiden.

Meilenstein

Sie verfügen nun sowohl über einen eigenständigen Pod als auch über eine von einem Deployment verwaltete Anwendung. Beide führen Container aus, doch das Deployment ergänzt eine Controller-Hierarchie, die zwei Replikate aufrechterhält und eine Grundlage für künftige Skalierungen und fortlaufende Aktualisierungen bildet.

Zusammenfassung

Sie sind von der schreibgeschützten Erkundung des Clusters zur deklarativen Anwendungsverwaltung übergegangen. Sie haben die Aufgaben von apiVersion, kind, metadata und spec kennengelernt, Manifeste mit clientseitigen Probeläufen validiert, einen eigenständigen Pod erstellt und untersucht sowie zwei verwaltete Replikate mit einem Deployment bereitgestellt.

Besonders wichtig war die Beobachtung der Controller-Besitzkette vom Deployment über das ReplicaSet bis zu den Pods. Diese Grundlage des gewünschten Zustands bereitet Sie darauf vor, in späteren Kursabschnitten Anwendungen zu analysieren, sie über Services bereitzustellen, Replikate zu skalieren und fortlaufende Aktualisierungen durchzuführen.