Kapitel 4. Erstellen und Ändern grundlegender Workloads

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

In diesem Kapitel stellen wir dir Rezepte vor, die dir zeigen, wie du die grundlegenden Kubernetes-Workload-Typen verwalten kannst: Pods und Deployments. Wir zeigen dir, wie du Deployments und Pods über CLI-Befehle und ein YAML-Manifest erstellst und wie du ein Deployment skalieren und aktualisieren kannst.

4.1 Erstellen eines Pods mit kubectl run

Problem

Du willst eine lang laufende Anwendung wie einen Webserver schnell starten.

Lösung

Verwende den Befehl kubectl run, einen Generator, der einen Pod im Handumdrehen erstellt. Um zum Beispiel mit einen Pod zu erstellen, der den NGINX Reverse Proxy ausführt, gehst du wie folgt vor:

$ kubectl run nginx --image=nginx

$ kubectl get pod/nginx
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          3m55s

Diskussion

Der Befehl kubectl run kann eine Reihe von Argumenten annehmen, um zusätzliche Parameter der Pods zu konfigurieren. Du kannst zum Beispiel Folgendes tun:

  • Setze Umgebungsvariablen mit --env.

  • Definiere Containerhäfen mit --port.

  • Definiere einen Befehl, der mit --command ausgeführt werden soll.

  • Erstelle automatisch einen zugehörigen Dienst mit --expose.

  • Teste einen Lauf, ohne etwas mit --dry-run=client auszuführen.

Typische Anwendungsfälle sind die folgenden. Um NGINX auf Port 2368 zu starten und einen Dienst zu erstellen, gibst du Folgendes ein:

$ kubectl run nginx --image=nginx --port=2368 --expose

Um MySQL mit dem root-Passwort zu starten, gibst du Folgendes ein:

$ kubectl run mysql --image=mysql --env=MYSQL_ROOT_PASSWORD=root

Um einen busybox Container zu starten und beim Start den Befehl sleep 3600 auszuführen, gibst du Folgendes ein:

$ kubectl run myshell --image=busybox:1.36 --command -- sh -c "sleep 3600"

Siehe auch kubectl run --help für weitere Informationen über die verfügbaren Argumente.

4.2 Erstellen einer Bereitstellung mit kubectl create

Problem

Du willst eine lang laufende Anwendung wie ein ContentManagement System schnell starten.

Lösung

Verwende kubectl create deployment, um ein Bereitstellungsmanifest im Handumdrehen zu erstellen. Um zum Beispiel eine Bereitstellung zu erstellen, auf der das WordPress Content ManagementSystem läuft, gehe wie folgt vor:

$ kubectl create deployment wordpress --image wordpress:6.3.1

$ kubectl get deployments.apps/wordpress
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
wordpress   1/1     1            1           90s

Diskussion

Der Befehl kubectl create deployment kann eine Reihe von Argumenten annehmen, um zusätzliche Parameter für die Einsätze zu konfigurieren. Du kannst zum Beispiel Folgendes tun:

  • Definiere Containerhäfen mit --port.

  • Definiere die Anzahl der Replikate mit --replicas.

  • Teste einen Lauf, ohne etwas mit --dry-run=client auszuführen.

  • Stelle das erstellte Manifest mit --output yaml zur Verfügung.

Siehe auch kubectl create deployment --help für weitere Informationen über die verfügbaren Argumente.

4.3 Objekte aus Dateimanifesten erstellen

Problem

Anstatt ein Objekt über einen Generator wie kubectl run zu erstellen, möchtest du seine Eigenschaften explizit angeben und es dann erstellen.

Lösung

Verwende kubectl apply wie folgt:

$ kubectl apply -f <manifest>

In Rezept 7.3 siehst du, wie du einen Namensraum mit einem YAML-Manifest erstellst. Dies ist eines der einfachsten Beispiele, da das Manifest sehr kurz ist. Es kann in YAML oder JSON geschrieben werden - zum Beispiel mit einer YAML-Manifestdatei myns.yaml wie folgt:

apiVersion: v1
kind: Namespace
metadata:
  name: myns

Du kannst dieses Objekt damit erstellen:

$ kubectl apply -f myns.yaml

Überprüfe, ob der Namensraum mit diesem erstellt wurde:

$ kubectl get namespaces

Diskussion

Du kannst kubectl apply auf eine URL statt auf einen Dateinamen in deinem lokalen Dateisystem verweisen. Um zum Beispiel das Frontend für die kanonische Gästebuch-Anwendung zu erstellen, holst du dir die URL der Roh-YAML, die die Anwendung in einem einzigen Manifest definiert, und gibst diese ein:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/examples/
    master/guestbook/all-in-one/guestbook-all-in-one.yaml

Überprüfe die Ressourcen, die durch diesen Vorgang erstellt wurden, zum Beispiel mit diesem:

$ kubectl get all

4.4 Ein Pod-Manifest von Grund auf neu schreiben

Problem

Du willst ein Pod-Manifest von Grund auf neu schreiben und es deklarativ anwenden, im Gegensatz zu einem Befehl wie kubectl run, der zwingend notwendig ist und keine manuelle Bearbeitung eines Manifests erfordert.

Lösung

Ein Pod ist ein /api/v1 Objekt, und wie jedes andere Kubernetes-Objekt enthält seine Manifestdatei die folgenden Felder:

  • apiVersion, die die API-Version angibt

  • kind, die den Typ des Objekts angibt

  • metadatadie einige Metadaten über das Objekt liefert

  • spec, die die Objektspezifikation liefert

Das Pod-Manifest enthält eine Reihe von Containern und eine optionale Reihe von Volumes (siehe Kapitel 8). In seiner einfachsten Form, mit einem einzigen Container und keinem Volume, sieht es etwa so aus:

apiVersion: v1
kind: Pod
metadata:
  name: oreilly
spec:
  containers:
  - name: oreilly
    image: nginx:1.25.2

Speichere dieses YAML-Manifest in einer Datei namens oreilly.yaml und verwende dann kubectl, um es zu erstellen:

$ kubectl apply -f oreilly.yaml

Überprüfe die Ressourcen, die durch diesen Vorgang erstellt wurden, zum Beispiel mit diesem:

$ kubectl get all

Diskussion

Die API-Spezifikation eines Pods ist viel umfangreicher als das, was in der Lösung gezeigt wird, die den grundlegendsten funktionierenden Pod darstellt. Ein Pod kann zum Beispiel mehrere Container enthalten, wie hier gezeigt:

apiVersion: v1
kind: Pod
metadata:
  name: oreilly
spec:
  containers:
  - name: oreilly
    image: nginx:1.25.2
  - name: safari
    image: redis:7.2.0

Ein Pod kann auch Volume-Definitionen enthalten, um Daten in die Container zu laden (siehe Rezept 8.1), sowie Sonden, um den Zustand der containerisierten Anwendung zu überprüfen (siehe Rezepte 11.2 und 11.3).

Eine Beschreibung der Überlegungen, die hinter vielen der Spezifikationsfelder stehen, und ein Link zur vollständigen API-Objektspezifikation sind in der Dokumentation zu finden.

Hinweis

Erstelle niemals einen Pod allein, es sei denn, es gibt ganz besondere Gründe dafür. Verwende ein Deployment Objekt (siehe Rezept 4.5), um Pods zu überwachen - es überwacht die Pods über ein anderes Objekt namens ReplicaSet.

4.5 Starten eines Einsatzes mit Hilfe eines Manifests

Problem

Du willst die volle Kontrolle darüber haben, wie eine (lang laufende) App gestartet undüberwacht wird.

Lösung

Schreibe ein Bereitstellungsmanifest. Für die Grundlagen, siehe auch Rezept 4.4.

Nehmen wir an, du hast eine Manifestdatei namens fancyapp.yaml mit dem folgenden Inhalt:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fancyapp
spec:
  replicas: 5
  selector:
    matchLabels:
      app: fancy
  template:
    metadata:
      labels:
        app: fancy
        env: development
    spec:
      containers:
      - name: sise
        image: gcr.io/google-samples/hello-app:2.0
        ports:
        - containerPort: 8080
        env:
        - name: SIMPLE_SERVICE_VERSION
          value: "2.0"

Wie du siehst, gibt es ein paar Dinge, die du beim Starten der App explizit tun solltest:

  • Lege die Anzahl der Pods (replicas), also der identischen Kopien, fest, die gestartet und überwacht werden sollen.

  • Beschrifte sie, z.B. mit env=development (siehe auch Rezepte 7.5 und 7.6).

  • Setze Umgebungsvariablen, wie z.B. SIMPLE_SERVICE_VERSION.

Schauen wir uns nun an, was der Einsatz mit sich bringt:

$ kubectl apply -f fancyapp.yaml
deployment.apps/fancyapp created

$ kubectl get deployments
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
fancyapp   5/5     5            5           57s

$ kubectl get replicasets
NAME                  DESIRED   CURRENT   READY     AGE
fancyapp-1223770997   5         5         0         59s

$ kubectl get pods -l app=fancy
NAME                                 READY   STATUS    RESTARTS      AGE
fancyapp-74c6f7cfd7-98d97            1/1     Running   0             115s
fancyapp-74c6f7cfd7-9gm2l            1/1     Running   0             115s
fancyapp-74c6f7cfd7-kggsx            1/1     Running   0             115s
fancyapp-74c6f7cfd7-xfs6v            1/1     Running   0             115s
fancyapp-74c6f7cfd7-xntk2            1/1     Running   0             115s
Warnung

Wenn du ein Deployment und damit auch die Replikat-Sets und Pods, die es überwacht, loswerden willst, führst du einen Befehl wie kubectl delete deploy/fancyapp aus. Versuche nicht, einzelne Pods zu löschen, denn sie werden von dem Einsatz neu erstellt. Das ist etwas, das Anfänger oft verwirrt.

Mit Deployments kannst du die App skalieren (siehe Rezept 9.1), eine neue Version ausrollen oder eine ReplicaSet auf eine frühere Version zurücksetzen. Sie eignen sich im Allgemeinen gut für zustandslose Anwendungen, die Pods mit identischen Eigenschaften benötigen.

Diskussion

Ein Deployment ist ein Supervisor für Pods und Replikat-Sets (RSs), mit dem du genau steuern kannst, wie und wann eine neue Pod-Version ausgerollt oder auf einen früheren Zustand zurückgesetzt wird. Die RS und Pods, die ein Deployment überwacht, sind in der Regel für dich uninteressant, es sei denn, du musst zum Beispiel einen Pod debuggen (siehe Rezept 12.5). Abbildung 4-1 veranschaulicht, wie du zwischen den Revisionen eines Deployments hin und her wechseln kannst.

Deployment Revisions
Abbildung 4-1. Revisionen des Einsatzes

Um das Manifest für eine Bereitstellung zu erstellen, kannst du den Befehl kubectl create und die Option --dry-run=client verwenden. Damit kannst du das Manifest im YAML- oder JSON-Format erstellen und zur späteren Verwendung speichern. Um zum Beispiel das Manifest eines Einsatzes namens fancy-app mit dem Container-Image nginx zu erstellen, gibst du den folgenden Befehl ein:

$ kubectl create deployment fancyapp --image nginx:1.25.2 -o yaml \
    --dry-run=client
kind: Deployment
apiVersion: apps/v1
metadata:
  name: fancyapp
  creationTimestamp:
  labels:
    app: fancyapp
...

Siehe auch

4.6 Aktualisieren eines Einsatzes

Problem

Du hast ein Deployment und willst eine neue Version deiner App ausrollen.

Lösung

Aktualisiere dein Deployment und lass die Standard-Update-Strategie RollingUpdate den Rollout automatisch durchführen.

Wenn du z.B. ein neues Container-Image erstellst und das darauf basierende Deployment aktualisieren möchtest, kannst du wie folgt vorgehen:

$ kubectl create deployment myapp --image=gcr.io/google-samples/hello-app:1.0
deployment.apps/myapp created

$ kubectl set image deployment/myapp \
    hello-app=gcr.io/google-samples/hello-app:2.0
deployment.apps/myapp image updated

$ kubectl rollout status deployment myapp
deployment "myapp" successfully rolled out

$ kubectl rollout history deployment myapp
deployment.apps/myapp
REVISION        CHANGE-CAUSE
1               <none>
2               <none>

Du hast jetzt erfolgreich eine neue Version deines Einsatzes ausgerollt, bei der sich nur das verwendete Container-Image geändert hat. Alle anderen Eigenschaften der Bereitstellung, wie z.B. die Anzahl der Replikate, bleiben unverändert. Was aber, wenn du andere Aspekte des Einsatzes aktualisieren willst, wie z.B. die Umgebungsvariablen ändern? Du kannst eine Reihe von kubectl Befehlen verwenden, um die Bereitstellung zu aktualisieren. Um zum Beispiel eine Portdefinition zur aktuellen Bereitstellung hinzuzufügen, kannst du kubectl edit verwenden:

$ kubectl edit deploy myapp

Dieser Befehl öffnet das aktuelle Deployment in deinem Standard-Editor oder, wenn er gesetzt und exportiert wurde, in dem durch die Umgebungsvariable KUBE_EDITOR festgelegten Editor .

Angenommen, du möchtest die folgende Portdefinition hinzufügen (siehe Abbildung 4-2 für die vollständige Datei):

...
  ports:
  - containerPort: 9876
...

Das Ergebnis des Bearbeitungsvorgangs (in diesem Fall mit KUBE_EDITOR auf vi) ist in Abbildung 4-2 dargestellt.

Sobald du den Editor speicherst und beendest, startet Kubernetes ein neues Deployment, jetzt mit dem definierten Port. Lass uns das überprüfen:

$ kubectl rollout history deployment myapp
deployments "sise"
REVISION        CHANGE-CAUSE
1               <none>
2               <none>
3               <none>

In der Tat sehen wir, dass Revision 3 mit den Änderungen, die wir mit kubectl edit eingeführt haben, ausgerollt wurde. Die Spalte CHANGE-CAUSE ist jedoch leer. Du kannst eine Änderungsursache für eine Revision angeben, indem du einen speziellen Vermerk verwendest. Im Folgenden findest du ein Beispiel für die Angabe einer Änderungsursache für die letzte Revision:

$ kubectl annotate deployment/myapp \
    kubernetes.io/change-cause="Added port definition."
deployment.apps/myapp annotate
Editing Deployment
Abbildung 4-2. Bearbeiten einer Bereitstellung

Wie bereits erwähnt, gibt es weitere kubectl Befehle, die du verwenden kannst, um deine Bereitstellung zu aktualisieren:

  • Mit kubectl apply kannst du ein Deployment aus einer Manifestdatei aktualisieren (oder erstellen, wenn es noch nicht existiert) - zum Beispiel kubectl apply -f simpleservice.yaml.

  • Verwende kubectl replace, um eine Bereitstellung aus einer Manifestdatei zu ersetzen - zum Beispiel kubectl replace -f simpleservice.yaml. Beachte, dass im Gegensatz zu apply die Bereitstellung bereits existieren muss, um replace zu verwenden.

  • Verwende kubectl patch, um einen bestimmten Schlüssel zu aktualisieren - zum Beispiel:

    kubectl patch deployment myapp -p '{"spec": {"template":
    {"spec": {"containers":
    [{"name": "sise", "image": "gcr.io/google-samples/hello-app:2.0"}]}}}}'

Was ist, wenn du einen Fehler machst oder Probleme mit der neuen Version des Deployments auftreten? Zum Glück ist es in Kubernetes ganz einfach, mit dem Befehl kubectl rollout undo zu einem bekannten guten Zustand zurückzukehren. Angenommen, die letzte Bearbeitung war ein Fehler und du willst auf Revision 2 zurückgehen. Das kannst du mit dem folgenden Befehl tun:

$ kubectl rollout undo deployment myapp ‐‐to‐revision 2

Du kannst dann mit kubectl get deploy/myapp -o yaml überprüfen, ob die Portdefinition entfernt wurde.

Hinweis

Der Rollout eines Deployments wird nur dann ausgelöst, wenn Teile der Pod-Vorlage (d.h. Schlüssel unter .spec.template) geändert werden, z.B. Umgebungsvariablen, Ports oder das Container-Image. Änderungen an Aspekten des Deployments, wie z. B. der Anzahl der Replikate, lösen kein neues Deployment aus.

4.7 Einen Batch-Auftrag ausführen

Problem

Du möchtest einen Prozess ausführen, der eine bestimmte Zeit bis zum Abschluss läuft, z.B. eine Batch-Konvertierung, einen Sicherungsvorgang oder ein Datenbankschema-Upgrade.

Lösung

Verwende eine Kubernetes Job um den/die Pods zu starten und zu überwachen, die den Batch-Prozess durchführen.

Definiere zunächst das Kubernetes-Manifest für den Auftrag in einer Datei namens counter-batch-job.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: counter
spec:
  template:
    metadata:
      name: counter
    spec:
      containers:
      - name: counter
        image: busybox:1.36
        command:
         - "sh"
         - "-c"
         - "for i in 1 2 3 ; do echo $i ; done"
      restartPolicy: Never

Dann starte den Auftrag und wirf einen Blick auf seinen Status: :

$ kubectl apply -f counter-batch-job.yaml
job.batch/counter created

$ kubectl get jobs
NAME      COMPLETIONS   DURATION   AGE
counter   1/1           7s         12s

$ kubectl describe jobs/counter
Name:             counter
Namespace:        default
Selector:         controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
Labels:           controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
                  job-name=counter
Annotations:      batch.kubernetes.io/job-tracking:
Parallelism:      1
Completions:      1
Completion Mode:  NonIndexed
Start Time:       Mon, 03 Apr 2023 18:19:13 +0530
Completed At:     Mon, 03 Apr 2023 18:19:20 +0530
Duration:         7s
Pods Statuses:    0 Active (0 Ready) / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=2d21031e-7263-4ff1-becd-48406393edd5
           job-name=counter
  Containers:
   counter:
    Image:      busybox:1.36
    Port:       <none>
    Host Port:  <none>
    Command:
      sh
      -c
      for i in 1 2 3 ; do echo $i ; done
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  30s   job-controller  Created pod: counter-5c8s5
  Normal  Completed         23s   job-controller  Job completed

Zum Schluss willst du überprüfen, ob er die Aufgabe tatsächlich ausgeführt hat (Zählen von 1 bis 3):

$ kubectl logs jobs/counter
1
2
3

Wie du sehen kannst, hat der counter Job tatsächlich wie erwartet gezählt.

Diskussion

Nachdem ein Auftrag erfolgreich ausgeführt wurde, befindet sich der Pod, der durch den Auftrag erstellt wurde, im Status Abgeschlossen. Du kannst den Auftrag löschen, wenn du ihn nicht mehr brauchst. Dadurch werden die Pods, die er erstellt hat, aufgeräumt:

$ kubectl delete jobs/counter 

Du kannst die Ausführung eines Auftrags auch vorübergehend aussetzen und später wieder aufnehmen. Wenn du einen Auftrag unterbrichst, werden auch die Pods aufgeräumt, die er erstellt hat:

$ kubectl patch jobs/counter --type=strategic --patch '{"spec":{"suspend":true}}'

Um den Auftrag fortzusetzen, drehst du einfach die Flagge suspend um:

$ kubectl patch jobs/counter --type=strategic \
    --patch '{"spec":{"suspend":false}}'

4.8 Einen Task auf einem Zeitplan innerhalb eines Pods ausführen

Problem

Du möchtest eine Aufgabe nach einem bestimmten Zeitplan innerhalb eines von Kubernetes verwalteten Pods ausführen.

Lösung

Verwende die CronJob Objekte von Kubernetes. Das CronJob Objekt ist eine Ableitung des allgemeineren Job Objekts (siehe Rezept 4.7).

Du kannst einen Auftrag regelmäßig planen, indem du ein Manifest schreibst, das dem hier gezeigten ähnelt. Im spec siehst du einen schedule Abschnitt, der dem crontab Format folgt. Du kannst auch einige Makros verwenden, wie z.B. @hourly, @daily, @weekly, @monthly, und @yearly. Der Abschnitt template beschreibt den Pod, der ausgeführt wird, und den Befehl, der ausgeführt wird (dieser gibt jede Stunde das aktuelle Datum und die Uhrzeit an stdout aus):

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hourly-date
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: date
            image: busybox:1.36
            command:
              - "sh"
              - "-c"
              - "date"
          restartPolicy: OnFailure

Diskussion

Genau wie ein Auftrag kann auch ein Cron-Job ausgesetzt und wieder aufgenommen werden, indem du das Flag suspend umdrehst. Zum Beispiel:

$ kubectl patch cronjob.batch/hourly-date --type=strategic \
    --patch '{"spec":{"suspend":true}}'

Wenn du den Cron-Job nicht mehr brauchst, lösche ihn, um die Pods, die er erstellt hat, aufzuräumen:

$ kubectl delete cronjob.batch/hourly-date

4.9 Ausführen von Infrastruktur-Daemons pro Knoten

Problem

Du willst einen Infrastruktur-Daemon starten, z.B. einen Log Collector oder einen Monitoring Agent, und sicherstellen, dass genau ein Pod pro Knoten läuft.

Lösung

Verwende eine DaemonSet, um den Daemon-Prozess zu starten und zu überwachen. Um zum Beispiel einen Fluentd-Agenten auf jedem Knoten in deinem Cluster zu starten, erstelle eine Datei namens fluentd-daemonset.yaml mit folgendem Inhalt:

kind: DaemonSet
apiVersion: apps/v1
metadata:
  name: fluentd
spec:
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
      name: fluentd
    spec:
      containers:
      - name: fluentd
        image: gcr.io/google_containers/fluentd-elasticsearch:1.3
        env:
         - name: FLUENTD_ARGS
           value: -qq
        volumeMounts:
         - name: varlog
           mountPath: /varlog
         - name: containers
           mountPath: /var/lib/docker/containers
      volumes:
         - hostPath:
             path: /var/log
           name: varlog
         - hostPath:
             path: /var/lib/docker/containers
           name: containers

Starte nun die DaemonSet, etwa so:

$ kubectl apply -f fluentd-daemonset.yaml
daemonset.apps/fluentd created

$ kubectl get ds
NAME     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR   AGE
fluentd  1         1         1       1            1           <none>          60s

$ kubectl describe ds/fluentd
Name:           fluentd
Selector:       app=fluentd
Node-Selector:  <none>
Labels:         <none>
Annotations:    deprecated.daemonset.template.generation: 1
Desired Number of Nodes Scheduled: 1
Current Number of Nodes Scheduled: 1
Number of Nodes Scheduled with Up-to-date Pods: 1
Number of Nodes Scheduled with Available Pods: 1
Number of Nodes Misscheduled: 0
Pods Status:  1 Running / 0 Waiting / 0 Succeeded / 0 Failed
...

Diskussion

Da der Befehl auf Minikube ausgeführt wird, siehst du in der vorangegangenen Ausgabe nur einen laufenden Pod, da es nur einen Knoten in diesem Setup gibt. Hättest du 15 Knoten in deinem Cluster, würdest du insgesamt 15 Pods haben und 1 Pod pro Knoten laufen lassen. Du kannst den Daemon auch auf bestimmte Knoten beschränken, indem du den Abschnitt nodeSelector im spec des Manifests DaemonSet verwendest.

Get Kubernetes Kochbuch, 2. Auflage now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.