Kapitel 4. Zeitplanungsprogramm und Werkzeuge

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

Der Scheduling-Teil des CKA konzentriert sich auf die Auswirkungen der Definition von Ressourcengrenzen bei der Auswertung durch das Zeitplannungsprogramm von Kubernetes. Das Standard-Laufzeitverhalten des Zeitplannungsprogramms kann auch durch die Definition von Node-Affinitätsregeln sowie von Taints und Toleranzen verändert werden. Von diesen Konzepten wird nur erwartet, dass du die Feinheiten der Ressourcengrenzen und ihre Auswirkungen auf das Zeitplannungsprogramm in verschiedenen Szenarien verstehst. Schließlich werden in diesem Bereich des Lehrplans Kenntnisse über die Verwaltung von Manifesten und Templating-Tools auf hohem Niveau vorausgesetzt.

In diesem Kapitel werden die folgenden Konzepte behandelt:

  • Ressourcengrenzen für Pods

  • Imperative und deklarative Manifestverwaltung

  • Gängige Templating-Tools wie Kustomize, yq, und Helm

Wie sich Ressourcenbeschränkungen auf diePod-Planung auswirken

Ein Kubernetes-Cluster kann aus mehreren Knotenpunkten bestehen. Das Zeitplannungsprogramm von Kubernetes entscheidet anhand verschiedener Regeln (z.B. Node-Selektoren, Node-Affinität, Taints und Toleranzen), welcher Knoten für die Ausführung der Arbeitslast ausgewählt wird. In der CKA-Prüfung wird nicht verlangt, dass du die oben genannten Scheduling-Konzepte verstehst, aber es wäre hilfreich, wenn du eine grobe Vorstellung davon hättest, wie sie auf hohem Niveau funktionieren.

Eine Metrik, die bei der Workload-Planung eine Rolle spielt, ist die von den Containern eines Pods definierte Ressourcenanforderung. Übliche Ressourcen, die angegeben werden können, sind CPU und Speicher. Das Zeitplannungsprogramm stellt sicher, dass die Ressourcenkapazität des Knotens die Ressourcenanforderungen des Pods erfüllen kann. Genauer gesagt,ermittelt das Zeitplannungsprogramm die Summe der Ressourcenanforderungen pro Typ für alle im Pod definierten Container und vergleicht sie mit den verfügbaren Ressourcen des Knotens. Abbildung 4-1 veranschaulicht den Zeitplanungsprozess auf der Grundlage der Ressourcenanforderungen.

ckas 0401
Abbildung 4-1. Pod-Planung auf der Grundlage von Ressourcenanforderungen

Definieren von Container-Ressourcenanfragen

Jeder Container eines Pods kann seine eigenen Ressourcenanforderungen definieren. Tabelle 4-1 beschreibt die verfügbaren Optionen sowie einen Beispielwert.

Tabelle 4-1. Optionen für Ressourcenanfragen
YAML-Attribut Beschreibung Beispielwert

spec.containers[].resources.requests.cpu

CPU-Ressourcen-Typ

500m (fünfhundert millicpu)

spec.containers[].resources.requests.memory

Typ der Speicherressource

64Mi (2^26 Bytes)

spec.containers[].resources.requests.hugepages-<size>

Riesige Seite Ressourcentyp

hugepages-2Mi: 60Mi

spec.containers[].resources.requests.ephemeral-storage

Typ der flüchtigen Speicherung von Ressourcen

4Gi

Kubernetes verwendet Ressourceneinheiten für Ressourcentypen, die von den Standard-Ressourceneinheiten wie Megabyte und Gigabyte abweichen. Alle Feinheiten dieser Einheiten zu erklären, würde den Rahmen dieses Buches sprengen, aber du kannst die Details in der Dokumentation nachlesen.

Um die Verwendung dieser Ressourcenanforderungen transparent zu machen, sehen wir uns eine Beispieldefinition an. Das in Beispiel 4-1 gezeigte Pod-YAML-Manifest definiert zwei Container, die jeweils eigene Ressourcenanforderungen haben. Jeder Knoten, auf dem der Pod ausgeführt werden darf, muss mindestens eine Speicherkapazität von 320Mi und 1250m CPU unterstützen, also die Summe der Ressourcen für beide Container.

Beispiel 4-1. Einstellen von Container-Ressourcenanforderungen
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "1"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"

In diesem Szenario haben wir es mit einem Minikube-Kubernetes-Cluster zu tun, der aus drei Knoten besteht, einem Control-Plane-Knoten und zwei Worker-Knoten. Der folgende Befehl listet alle Knoten auf:

$ kubectl get nodes
NAME           STATUS   ROLES                  AGE   VERSION
minikube       Ready    control-plane,master   12d   v1.21.2
minikube-m02   Ready    <none>                 42m   v1.21.2
minikube-m03   Ready    <none>                 41m   v1.21.2

Im nächsten Schritt erstellen wir den Pod aus dem YAML-Manifest. Das Zeitplannungsprogramm platziert den Pod auf dem Knoten namens minikube-m03:

$ kubectl create -f rate-limiter-pod.yaml
pod/rate-limiter created
$ kubectl get pod rate-limiter -o yaml | grep nodeName:
  nodeName: minikube-m03

Wenn du dir den Knoten genauer ansiehst, kannst du seine maximale Kapazität, wie viel davon zuweisbar ist und die Speicheranforderungen der Pods, die auf dem Knoten geplant sind, einsehen. Der folgende Befehl listet die Informationen auf und komprimiert die Ausgabe auf die relevanten Teile:

$ kubectl describe node minikube-m03
...
Capacity:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
Allocatable:
  cpu:                2
  ephemeral-storage:  17784752Ki
  hugepages-2Mi:      0
  memory:             2186612Ki
  pods:               110
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
  Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   0 (0%)      \
  320Mi (14%)      0 (0%)         9m
...

Es ist durchaus möglich, dass ein Pod nicht geplant werden kann, weil auf den Knoten nicht genügend Ressourcen zur Verfügung stehen. In diesen Fällen wird das Ereignisprotokoll des Pods diese Situation mit den Gründen PodExceedsFreeCPU oder PodExceedsFreeMemory anzeigen. Weitere Informationen zur Fehlerbehebung und Lösung dieser Situation findest du im entsprechenden Abschnitt der Dokumentation.

Festlegen von Container-Ressourcenlimits

Eine weitere Kennzahl, die du für einen Container festlegen kannst, sind seine Ressourcenlimits. Ressourcenlimits stellen sicher, dass der Container nicht mehr als die zugewiesenen Ressourcen verbrauchen kann. Du könntest zum Beispiel festlegen, dass die Anwendung, die im Container läuft, nicht mehr als 1000m CPU und 512Mi Arbeitsspeicher verbrauchen darf.

Je nach der vom Cluster verwendeten Container-Laufzeitumgebung führt das Überschreiten einer der zulässigen Ressourcengrenzen zum Abbruch des im Container laufenden Anwendungsprozesses oder dazu, dass das System die Zuweisung von Ressourcen über die Grenzen hinaus gänzlich verhindert. Wie dieContainer-Laufzeitumgebung Docker mit Ressourcengrenzen umgeht, wird in der Dokumentation ausführlich beschrieben.

Tabelle 4-2 beschreibt die verfügbaren Optionen und gibt einen Beispielwert an.

Tabelle 4-2. Optionen für Ressourcengrenzen
YAML-Attribut Beschreibung Beispielwert

spec.containers[].resources.limits.cpu

CPU-Ressourcen-Typ

500m (500 millicpu)

spec.containers[].resources.limits.memory

Typ der Speicherressource

64Mi (2^26 Bytes)

spec.containers[].resources.limits.hugepages-<size>

Riesige Seite Ressourcentyp

hugepages-2Mi: 60Mi

spec.containers[].resources.limits.ephemeral-storage

Typ der flüchtigen Speicherung von Ressourcen

4Gi

Beispiel 4-2 zeigt die Definition von Grenzen in Aktion. Hier kann der Container business-app nicht mehr als 512Mi Arbeitsspeicher und 2000m CPU verwenden. Der Container mit dem Namen ambassador definiert ein Limit von 128Mi Arbeitsspeicher und 500m CPU.

Beispiel 4-2. Container-Ressourcenlimits festlegen
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      limits:
        memory: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      limits:
        memory: "128Mi"
        cpu: "500m"

Nehmen wir an, dass der Pod auf dem Knoten minikube-m03 geplant wurde. Wenn du die Details des Knotens beschreibst, siehst du, dass die CPU- und Speicherbegrenzungen wirksam wurden. Aber das ist noch nicht alles. Kubernetes weist automatisch die gleiche Menge an Ressourcen für die Requests zu, wenn du nur die Limits definierst:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits  \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------  \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   1250m (62%) \
  320Mi (14%)      320Mi (14%)    11s
...

Festlegen von Requests und Limits für Container-Ressourcen

Es wird empfohlen, dass du für jeden Container Ressourcenanforderungen und Limits festlegst. Es ist nicht immer einfach, diese Ressourcenanforderungen zu bestimmen, insbesondere bei Anwendungen, die noch nicht in einer Produktionsumgebung getestet wurden. Lasttests der Anwendung zu einem frühen Zeitpunkt während des Entwicklungszyklus können bei der Analyse des Ressourcenbedarfs helfen. Weitere Anpassungen können vorgenommen werden, indem der Ressourcenverbrauch der Anwendung nach der Bereitstellung im Cluster überwacht wird. Beispiel 4-3 fasst Ressourcenanforderungen und Limits in einem einzigen YAML-Manifest zusammen.

Beispiel 4-3. Einstellen von Requests und Limits für Containerressourcen
apiVersion: v1
kind: Pod
metadata:
  name: rate-limiter
spec:
  containers:
  - name: business-app
    image: bmuschko/nodejs-business-app:1.0.0
    ports:
    - containerPort: 8080
    resources:
      requests:
        memory: "256Mi"
        cpu: "1"
      limits:
        memory: "512Mi"
        cpu: "2"
  - name: ambassador
    image: bmuschko/nodejs-ambassador:1.0.0
    ports:
    - containerPort: 8081
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

Als Ergebnis kannst du die verschiedenen Einstellungen für Ressourcenanforderungen und Limits sehen:

$ kubectl describe node minikube-m03
...
Non-terminated Pods:          (3 in total)
  Namespace                   Name                CPU Requests  CPU Limits   \
   Memory Requests  Memory Limits  AGE
  ---------                   ----                ------------  ----------   \
  ---------------  -------------  ---
  default                     rate-limiter        1250m (62%)   2500m (125%) \
  320Mi (14%)      640Mi (29%)    3s
...

Objekte verwalten

Kubernetes-Objekte können mithilfe von imperativen Befehlen ( kubectl ) erstellt, geändert und gelöscht werden oder indem ein kubectl Befehl gegen eine Konfigurationsdatei ausgeführt wird, die den gewünschten Zustand eines Objekts, ein sogenanntes Manifest, definiert. Die primäre Definitionssprache für ein Manifest ist YAML, du kannst dich aber auch für JSON entscheiden, das in der Kubernetes-Gemeinschaft weniger verbreitet ist. Es wird empfohlen, dass Entwicklungsteams diese Konfigurationsdateien in Versionskontroll-Repositories übertragen, um Änderungen im Laufe der Zeit zu verfolgen und zu überprüfen.

Die Modellierung einer Anwendung in Kubernetes erfordert oft eine Reihe von unterstützenden Objekten, von denen jedes sein eigenes Manifest haben kann. Du möchtest zum Beispiel ein Deployment erstellen, das die Anwendung auf fünf Pods ausführt, eine ConfigMap, um Konfigurationsdaten als Umgebungsvariablen zu injizieren, und einen Service für den Netzwerkzugang.

Dieser Abschnitt konzentriert sich in erster Linie auf die deklarative Objektverwaltung mit Hilfe von Manifesten. Eine ausführlichere Diskussion über die imperative Unterstützung findest du in den entsprechenden Abschnitten der Dokumentation. Außerdem gehen wir auf Tools wie Kustomize und Helm ein, um dir einen Eindruck von ihren Vorteilen, Fähigkeiten und Arbeitsabläufen zu vermitteln.

Deklarative Objektverwaltung mit Konfigurationsdateien

Die deklarative Objektverwaltung erfordert eine oder mehrere Konfigurationsdateien im Format YAML oder JSON, die den gewünschten Zustand eines Objekts beschreiben. Mit diesem Ansatz kannst du Objekte erstellen, aktualisieren und löschen.

Objekte erstellen

Um neue Objekte zu erstellen, führst du den Befehl apply aus, indem du mit der Option -f auf eine Datei, ein Dateiverzeichnis oder eine Datei zeigst, auf die eine HTTP(S)-URL verweist. Wenn eines oder mehrere der Objekte bereits existieren, synchronisiert der Befehl die an der Konfiguration vorgenommenen Änderungen mit dem Live-Objekt.

Um die Funktionalität zu demonstrieren, gehen wir von den folgenden Verzeichnissen und Konfigurationsdateien aus. Die folgenden Befehle erstellen Objekte aus einer einzelnen Datei, aus allen Dateien innerhalb eines Verzeichnisses und aus allen Dateien in einem Verzeichnis rekursiv:

.
├── app-stack
│   ├── mysql-pod.yaml
│   ├── mysql-service.yaml
│   ├── web-app-pod.yaml
│   └── web-app-service.yaml
├── nginx-deployment.yaml
└── web-app
    ├── config
    │   ├── db-configmap.yaml
    │   └── db-secret.yaml
    └── web-app-pod.yaml

Erstellen eines Objekts aus einer einzelnen Datei:

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

Objekte aus mehreren Dateien innerhalb eines Verzeichnisses erstellen:

$ kubectl apply -f app-stack/
pod/mysql-db created
service/mysql-service created
pod/web-app created
service/web-app-service created

Erstellen von Objekten aus einem rekursiven Verzeichnisbaum mit Dateien:

$ kubectl apply -f web-app/ -R
configmap/db-config configured
secret/db-creds created
pod/web-app created

Erstellen von Objekten aus einer Datei, auf die eine HTTP(S)-URL verweist:

$ kubectl apply -f https://raw.githubusercontent.com/bmuschko/cka-study-guide/ \
  master/ch04/object-management/nginx-deployment.yaml
deployment.apps/nginx-deployment created

Der Befehl apply hält die Änderungen fest, indem er die Anmerkung mit dem Schlüssel kubectl.kubernetes.io/last-applied-configuration hinzufügt oder ändert. Ein Beispiel für die Annotation in der Ausgabe des Befehls get pod findest du hier:

$ kubectl get pod web-app -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{}, \
      "labels":{"app":"web-app"},"name":"web-app","namespace":"default"}, \
      "spec":{"containers":[{"envFrom":[{"configMapRef":{"name":"db-config"}}, \
      {"secretRef":{"name":"db-creds"}}],"image":"bmuschko/web-app:1.0.1", \
      "name":"web-app","ports":[{"containerPort":3000,"protocol":"TCP"}]}], \
      "restartPolicy":"Always"}}
...

Objekte aktualisieren

Das Aktualisieren eines bestehenden Objekts erfolgt mit demselben Befehl apply. Du musst nur die Konfigurationsdatei ändern und dann den Befehl gegen sie ausführen. Beispiel 4-4 ändert die bestehende Konfiguration eines Deployments in der Datei nginx-deployment.yaml. Wir haben ein neues Label mit dem Schlüssel team hinzugefügt und die Anzahl der Replikate von 3 auf 5 geändert.

Beispiel 4-4. Geänderte Konfigurationsdatei für ein Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
    team: red
spec:
  replicas: 5
...

Der folgende Befehl wendet die geänderte Konfigurationsdatei an. Die Anzahl der Pods, die von dem zugrunde liegenden ReplicaSet kontrolliert werden, beträgt nun 5. Der Verwendungsnachweis kubectl.kubernetes.io/last-applied-configuration spiegelt die letzte Änderung der Konfiguration wider:

$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment configured
$ kubectl get deployments,pods
NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx-deployment   5/5     5            5           10m

NAME                                    READY   STATUS    RESTARTS   AGE
pod/nginx-deployment-66b6c48dd5-79j6t   1/1     Running   0          35s
pod/nginx-deployment-66b6c48dd5-bkkgb   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-d26c8   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-fcqrs   1/1     Running   0          10m
pod/nginx-deployment-66b6c48dd5-whfnn   1/1     Running   0          35s
$ kubectl get deployment nginx-deployment -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{}, \
      "labels":{"app":"nginx","team":"red"},"name":"nginx-deployment", \
      "namespace":"default"},"spec":{"replicas":5,"selector":{"matchLabels": \
      {"app":"nginx"}},"template":{"metadata":{"labels":{"app":"nginx"}}, \
      "spec":{"containers":[{"image":"nginx:1.14.2","name":"nginx", \
      "ports":[{"containerPort":80}]}]}}}}
...

Löschen von Objekten

Es gibt zwar eine Möglichkeit, Objekte mit dem Befehl apply zu löschen, indem du die Optionen --prune -l <labels> angibst, aber es wird empfohlen, ein Objekt mit dem Befehl delete zu löschen und ihn auf die Konfigurationsdatei zu verweisen. Der folgende Befehl löscht ein Deployment und die von ihm kontrollierten Objekte (ReplicaSet und Pods):

$ kubectl delete -f nginx-deployment.yaml
deployment.apps "nginx-deployment" deleted
$ kubectl get deployments,replicasets,pods
No resources found in default namespace.

Deklarative Objektverwaltung mit Kustomize

Kustomize ist ein Tool, das mit Kubernetes 1.14 eingeführt wurde und die Verwaltung von Manifesten vereinfachen soll. Es unterstützt drei verschiedene Anwendungsfälle:

  • Erzeugen von Manifesten aus anderen Quellen. Zum Beispiel, indem du eine ConfigMap erstellst und ihre Schlüssel-Wert-Paare aus einer Eigenschaftsdatei ausfüllst.

  • Hinzufügen einer gemeinsamen Konfiguration für mehrere Manifeste. Zum Beispiel kannst du einen Namensraum und eine Reihe von Bezeichnungen für eine Bereitstellung und einen Dienst hinzufügen.

  • Zusammenstellen und Anpassen einer Sammlung von Manifesten. Zum Beispiel das Festlegen von Ressourcengrenzen für mehrere Bereitstellungen.

Die zentrale Datei, die benötigt wird, damit Kustomize funktioniert, ist die Kustomisierungsdatei. Der standardisierte Name für die Datei ist kustomization.yaml und kann nicht geändert werden. Eine Kustomization-Datei definiert die Verarbeitungsregeln, nach denen Kustomize arbeitet.

Kustomize ist vollständig in kubectl integriert und kann in zwei Modi ausgeführt werden: Rendering der Verarbeitungsausgabe auf der Konsole oder Erstellen der Objekte. Beide Modi können mit einem Verzeichnis, einem Tarball, einem Git-Archiv oder einer URL arbeiten, solange sie die Kustomize-Datei und die referenzierten Ressourcendateien enthalten:

Rendering der produzierten Ausgabe

Im ersten Modus wird der Unterbefehl kustomize verwendet, um das Ergebnis auf der Konsole darzustellen, aber die Objekte werden nicht erstellt. Dieser Befehl funktioniert ähnlich wie die Option "Trockenlauf", die du vielleicht von dem Befehl run kennst:

$ kubectl kustomize <target>
Erstellen der Objekte

Der zweite Modus verwendet den Befehl apply in Verbindung mit der Befehlszeilenoption -k, um die von Kustomize verarbeiteten Ressourcen anzuwenden, wie im vorherigen Abschnitt erklärt:

$ kubectl apply -k <target>

In den folgenden Abschnitten wird jeder der Anwendungsfälle anhand eines Beispiels erläutert. Eine vollständige Übersicht über alle möglichen Szenarien findest du in der Dokumentation oder im Kustomize GitHub Repository.

Manifeste zusammenstellen

Eine der Kernfunktionen von Kustomize ist es, ein zusammengesetztes Manifest aus anderen Manifesten zu erstellen. Das Kombinieren mehrerer Manifeste zu einem einzigen mag für sich genommen nicht sehr nützlich erscheinen, aber viele der anderen Funktionen, die später beschrieben werden, bauen auf dieser Fähigkeit auf. Angenommen, du möchtest ein Manifest aus einer Verteilungs- und einer Service-Ressourcendatei zusammenstellen. Alles, was du tun musst, ist, die Ressourcendateien in denselben Ordner zu legen wie die Anpassungsdatei:

.
├── kustomization.yaml
├── web-app-deployment.yaml
└── web-app-service.yaml

Die kustomization-Datei listet die Ressourcen im Abschnitt resources auf, wie in Beispiel 4-5 gezeigt.

Beispiel 4-5. Eine Anpassungsdatei, die zwei Manifeste kombiniert
resources:
- web-app-deployment.yaml
- web-app-service.yaml

Der Unterbefehl kustomize zeigt das kombinierte Manifest an, das alle Ressourcen enthält, die durch drei Bindestriche (---) getrennt sind, um die verschiedenen Objektdefinitionen zu kennzeichnen:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
  name: web-app-service
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
  name: web-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

Erzeugen von Manifesten aus anderen Quellen

Weiter oben in diesem Kapitel haben wir gelernt, dass ConfigMaps und Secrets erstellt werden können, indem sie auf eine Datei verweisen, die die eigentlichen Konfigurationsdaten für sie enthält. Kustomize kann bei diesem Prozess helfen, indem es die Beziehung zwischen dem YAML-Manifest dieser Konfigurationsobjekte und ihren Daten abbildet. Außerdem wollen wir die erstellte ConfigMap und Secret als Umgebungsvariablen in einen Pod einfügen. In diesem Abschnitt erfährst du, wie du dies mit Hilfe von Kustomize erreichen kannst.

Die folgende Datei- und Verzeichnisstruktur enthält die Manifestdatei für den Pod und die Konfigurationsdateien, die wir für die ConfigMap und Secret benötigen. Die obligatorische Anpassungsdatei befindet sich auf der Stammebene des Verzeichnisbaums:

.
├── config
│   ├── db-config.properties
│   └── db-secret.properties
├── kustomization.yaml
└── web-app-pod.yaml

In kustomization.yaml kannst du festlegen, dass die ConfigMap und das Secret-Objekt mit dem angegebenen Namen erstellt werden sollen. Der Name der ConfigMap soll db-config lauten und der Name des Secret wird db-creds sein. Die beiden Generatorattribute configMapGenerator und secretGenerator verweisen auf eine Eingabedatei, in die die Konfigurationsdaten eingegeben werden. Alle zusätzlichen Ressourcen können mit dem Attribut resources angegeben werden. Beispiel 4-6 zeigt den Inhalt der Anpassungsdatei.

Beispiel 4-6. Eine Anpassungsdatei mit einer ConfigMap und einem Secret-Generator
configMapGenerator:
- name: db-config
  files:
  - config/db-config.properties
secretGenerator:
- name: db-creds
  files:
  - config/db-secret.properties
resources:
- web-app-pod.yaml

Kustomize erzeugt ConfigMaps und Secrets, indem es ein Suffix an den Namen anhängt. Du kannst dieses Verhalten sehen, wenn du die Objekte mit dem Befehl apply erstellst. Die ConfigMap und das Secret können im Pod-Manifest mit ihrem Namen referenziert werden:

$ kubectl apply -k ./
configmap/db-config-t4c79h4mtt unchanged
secret/db-creds-4t9dmgtf9h unchanged
pod/web-app created

Diese Benennungsstrategie kann mit dem Attribut generatorOptions in der kustomization-Datei konfiguriert werden. WeitereInformationen findest du in der Dokumentation.

Probieren wir auch den Unterbefehl kustomize aus. Anstatt die Objekte zu erstellen, gibt der Befehl die verarbeitete Ausgabe auf der Konsole aus:

$ kubectl kustomize ./
apiVersion: v1
data:
  db-config.properties: |-
    DB_HOST: mysql-service
    DB_USER: root
kind: ConfigMap
metadata:
  name: db-config-t4c79h4mtt
---
apiVersion: v1
data:
  db-secret.properties: REJfUEFTU1dPUkQ6IGNHRnpjM2R2Y21RPQ==
kind: Secret
metadata:
  name: db-creds-4t9dmgtf9h
type: Opaque
---
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - envFrom:
    - configMapRef:
        name: db-config-t4c79h4mtt
    - secretRef:
        name: db-creds-4t9dmgtf9h
    image: bmuschko/web-app:1.0.1
    name: web-app
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Hinzufügen einer gemeinsamen Konfiguration für mehrere Manifeste

Anwendungsentwickler arbeiten normalerweise an einem Anwendungsstack, der aus mehreren Manifesten besteht. Ein Anwendungsstapel kann zum Beispiel aus einem Frontend-Microservice, einem Backend-Microservice und einer Datenbank bestehen. Es ist üblich, für alle Manifeste die gleiche, übergreifende Konfiguration zu verwenden. Kustomize bietet eine Reihe von unterstützten Feldern (z. B. Namensräume, Labels oder Anmerkungen). In der Dokumentation findest du Informationen zu allen unterstützten Feldern.

Für das nächste Beispiel gehen wir davon aus, dass ein Einsatz und ein Dienst im selben Namensraum leben und einen gemeinsamen Satz von Bezeichnungen verwenden. Der Namensraum heißt persistence und das Label ist das Schlüssel-Wert-Paar team: helix. Beispiel 4-7 zeigt, wie man diese gemeinsamen Felder in der Anpassungsdatei einstellt.

Beispiel 4-7. Eine Anpassungsdatei mit einem gemeinsamen Feld
namespace: persistence
commonLabels:
  team: helix
resources:
- web-app-deployment.yaml
- web-app-service.yaml

Um die referenzierten Objekte in der Anpassungsdatei zu erstellen, führe den Befehl apply aus. Achte darauf, dass du vorher den Namensraum persistence erstellst:

$ kubectl create namespace persistence
namespace/persistence created
$ kubectl apply -k ./
service/web-app-service created
deployment.apps/web-app-deployment created

Die YAML-Darstellung der verarbeiteten Dateien sieht wie folgt aus:

$ kubectl kustomize ./
apiVersion: v1
kind: Service
metadata:
  labels:
    app: web-app-service
    team: helix
  name: web-app-service
  namespace: persistence
spec:
  ports:
  - name: web-app-port
    port: 3000
    protocol: TCP
    targetPort: 3000
  selector:
    app: web-app
    team: helix
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web-app-deployment
    team: helix
  name: web-app-deployment
  namespace: persistence
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
      team: helix
  template:
    metadata:
      labels:
        app: web-app
        team: helix
    spec:
      containers:
      - env:
        - name: DB_HOST
          value: mysql-service
        - name: DB_USER
          value: root
        - name: DB_PASSWORD
          value: password
        image: bmuschko/web-app:1.0.1
        name: web-app
        ports:
        - containerPort: 3000

Anpassen einer Sammlung von Manifesten

Kustomize kann den Inhalt eines YAML-Manifests mit einem Codeschnipsel aus einem anderen YAML-Manifest zusammenführen. Typische Anwendungsfälle sind das Hinzufügen einer Sicherheitskontextkonfiguration zu einer Pod-Definition oder das Festlegen von Ressourcengrenzen für ein Deployment. In der kustomization-Datei können verschiedene Patch-Strategien angegeben werden wiepatchesStrategicMerge und patchesJson6902. Eine ausführlichere Diskussion über die Unterschiede zwischen den Patch-Strategien findest du in der Dokumentation.

Beispiel 4-8 zeigt den Inhalt einer Anpassungsdatei, die eine Einsatzdefinition in der Datei nginx-deployment.yaml mit dem Inhalt der Datei security-context.yaml patcht.

Beispiel 4-8. Eine Anpassungsdatei, die einen Patch definiert
resources:
- nginx-deployment.yaml
patchesStrategicMerge:
- security-context.yaml

Die in Beispiel 4-9 gezeigte Patch-Datei definiert einen Sicherheitskontext auf Containerebene für die Pod-Vorlage des Deployments. Zur Laufzeit versucht die Patch-Strategie, den Container mit dem Namen nginx zu finden und die zusätzliche Konfiguration zu erweitern.

Beispiel 4-9. Das YAML-Manifest des Patches
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  template:
    spec:
      containers:
      - name: nginx
        securityContext:
          runAsUser: 1000
          runAsGroup: 3000
          fsGroup: 2000

Das Ergebnis ist eine gepatchte Einsatzdefinition, wie sie in der Ausgabe deskustomize Unterbefehls (siehe unten). Der Patch-Mechanismus kann auf andere Dateien angewendet werden, die eine einheitliche Sicherheitskontextdefinition erfordern:

$ kubectl kustomize ./
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.14.2
        name: nginx
        ports:
        - containerPort: 80
        securityContext:
          fsGroup: 2000
          runAsGroup: 3000
          runAsUser: 1000

Gemeinsame Templating Tools

Wie im vorherigen Abschnitt gezeigt, bietet Kustomize Templating-Funktionen. Das Kubernetes-Ökosystem bietet noch weitere Lösungen für dieses Problem, die wir hier besprechen werden. Wir werden auf den YAML-Prozessor yq und die Templating-Engine Helm eingehen.

Den YAML-Prozessor verwenden yq

Das Tool yq wird verwendet, um den Inhalt einer YAML-Datei zu lesen, zu ändern und zu erweitern. In diesem Abschnitt werden alle drei Anwendungsfälle demonstriert. Eine detaillierte Liste der Anwendungsbeispiele findest du im GitHub-Repository. In der CKA-Prüfung wirst du möglicherweise aufgefordert, diese Techniken anzuwenden, obwohl von dir nicht erwartet wird, dass du alle Feinheiten der jeweiligen Tools kennst. Die Version von yq, die für die Beschreibung der folgenden Funktionen verwendet wird, ist 4.2.1.

Werte lesen

Das Auslesen von Werten aus einer bestehenden YAML-Datei erfordert die Verwendung eines YAML-Pfadausdrucks. Ein Pfadausdruck ermöglicht es dir, tief in der YAML-Struktur zu navigieren und den Wert eines gesuchten Attributs zu extrahieren. Beispiel 4-10 zeigt das YAML-Manifest eines Pods, der zwei Umgebungsvariablen definiert.

Beispiel 4-10. Das YAML-Manifest eines Pods
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'

Um einen Wert zu lesen, verwendest du den Befehl eval oder die Kurzform e, gibst den YAML-Pfadausdruck an und verweist auf die Quelldatei. Die folgenden beiden Befehle lesen den Namen des Pods und den Wert der zweiten Umgebungsvariablen, die von einem einzelnen Container definiert wird. Beachte, dass der Pfadausdruck mit einem obligatorischen Punkt (.) beginnen muss, um den Wurzelknoten der YAML-Struktur zu kennzeichnen:

$ yq e .metadata.name pod.yaml
spring-boot-app
$ yq e .spec.containers[0].env[1].value pod.yaml
1.5.3

Werte ändern

Das Ändern eines bestehenden Wertes ist so einfach wie die Verwendung desselben Befehls und das Hinzufügen des Flags -i. Die Zuweisung des neuen Wertes zu einem Attribut erfolgt durch die Zuweisung zum Pfadausdruck. Der folgende Befehl ändert die zweite Umgebungsvariable der YAML-Datei des Pods auf den Wert 1.6.0:

$ yq e -i .spec.containers[0].env[1].value = "1.6.0" pod.yaml
$ cat pod.yaml
...
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: 1.6.0

Zusammenführen von YAML-Dateien

Ähnlich wie Kustomize kann auch yq mehrere YAML-Dateien zusammenführen. Kustomize ist auf jeden Fall leistungsfähiger und komfortabler, aber yq kann für kleinere Projekte sehr nützlich sein. Angenommen, du möchtest die in Beispiel 4-11 gezeigte Definition des Sidecar-Containers in die YAML-Datei des Pods einbinden.

Beispiel 4-11. Das YAML-Manifest einer Containerdefinition
spec:
  containers:
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

Der Befehl, mit dem dies erreicht wird, lautet eval-all. Angesichts der vielen Konfigurationsoptionen für diesen Befehl werden wir nicht ins Detail gehen. Einen tieferen Einblick in die Operation "Multiplizieren (Zusammenführen)" findest du im yq Benutzerhandbuch. Der folgende Befehl fügt den Sidecar-Container an das bestehende Container-Array des Pod-Manifests an:

$ yq eval-all 'select(fileIndex == 0) *+ select(fileIndex == 1)' pod.yaml \
  sidecar.yaml
apiVersion: v1
kind: Pod
metadata:
  name: spring-boot-app
spec:
  containers:
  - image: bmuschko/spring-boot-app:1.5.3
    name: spring-boot-app
    env:
    - name: SPRING_PROFILES_ACTIVE
      value: prod
    - name: VERSION
      value: '1.5.3'
  - image: envoyproxy/envoy:v1.19.1
    name: proxy-container
    ports:
    - containerPort: 80

Helm benutzen

Helm ist eine Templating-Engine und ein Paketmanager für eine Reihe von Kubernetes-Manifesten. Zur Laufzeit ersetzt es Platzhalter in YAML-Vorlagendateien durch tatsächliche, vom Endnutzer definierte Werte. Das Artefakt, das von Helm erzeugt wird, ist eine sogenannte Chart-Datei, die die Manifeste bündelt, die die API-Ressourcen einer Anwendung umfassen. Diese Diagrammdatei kann in einen Paketmanager hochgeladen werden, um sie während des Bereitstellungsprozesses zu verwenden. Das Helm-Ökosystem bietet eine Vielzahl von wiederverwendbaren Charts für gängige Anwendungsfälle in einem zentralen Chart-Repository (z. B. für Grafana oder PostgreSQL).

Aufgrund der Fülle an Funktionen, die Helm bietet, werden wir nur die Grundlagen besprechen. Bei der CKA-Prüfung wird nicht erwartet, dass du ein Helm-Experte bist, sondern dass du mit den Vorteilen und Konzepten vertraut bist. Ausführlichere Informationen zu Helm findest du in der Benutzerdokumentation. Die Version von Helm, die für die Beschreibung der Funktionen hier verwendet wird, ist 3.7.0.

Standard Chart Struktur

Ein Diagramm muss einer vordefinierten Verzeichnisstruktur folgen. Du kannst einen beliebigen Namen für das Stammverzeichnis wählen. Innerhalb dieses Verzeichnisses müssen zwei Dateien existieren: Chart.yaml und values.yaml. Die Datei Chart.yaml beschreibt die Metainformationen des Diagramms (z. B. Name und Version). Die Datei values.yaml enthält die Schlüssel-Wert-Paare, die zur Laufzeit verwendet werden, um die Platzhalter in den YAML-Manifesten zu ersetzen. Alle Templating-Dateien, die in die Archivdatei der Tabelle gepackt werden sollen, müssen im Verzeichnis templates abgelegt werden. Dateien, die sich im Verzeichnis template befinden, müssen keinen Namenskonventionen folgen.

Die folgende Verzeichnisstruktur zeigt ein Beispieldiagramm. Das Verzeichnis templates enthält eine Datei für einen Pod und einen Service:

$ tree
.
├── Chart.yaml
├── templates
│   ├── web-app-pod-template.yaml
│   └── web-app-service-template.yaml
└── values.yaml

Die Kartendatei

Die Datei Chart.yaml beschreibt das Diagramm auf einer hohen Ebene. Zu den obligatorischen Attributen gehören die API-Version des Diagramms, der Name und die Version. Zusätzlich können optionale Attribute angegeben werden. Eine vollständige Liste der Attribute findest du in der entsprechenden Dokumentation. Beispiel 4-12 zeigt das absolute Minimum einer Diagrammdatei.

Beispiel 4-12. Eine grundlegende Helm-Diagrammdatei
apiVersion: 1.0.0
name: web-app
version: 2.5.4

Die Wertedatei

In der Datei values.yaml werden Schlüssel-Wert-Paare definiert, die die Platzhalter in den YAML-Vorlagendateien ersetzen sollen. Beispiel 4-13 gibt vier Schlüssel-Wert-Paare an. Beachte, dass die Datei leer sein kann, wenn du keine Werte zur Laufzeit ersetzen willst.

Beispiel 4-13. Eine Helm-Werte-Datei
db_host: mysql-service
db_user: root
db_password: password
service_port: 3000

Die Templating-Dateien

Die Templating-Dateien müssen sich im Verzeichnis templates befinden. Eine Vorlagendatei ist ein reguläres YAML-Manifest, das (optional) Platzhalter mit Hilfe von doppelten geschweiften Klammern ({{ }}) definieren kann. Um einen Wert aus der Datei values.yaml zu referenzieren, verwendest du den Ausdruck {{ .Values.<key> }}. Um zum Beispiel den Wert des Schlüssels db_host zur Laufzeit zu ersetzen, verwendest du den Ausdruck {{ .Values.db_host }}. Beispiel 4-14 definiert einen Pod als Template und legt gleichzeitig drei Platzhalter fest, die auf Werte aus values.yaml verweisen.

Beispiel 4-14. Das YAML-Templating-Manifest eines Pods
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: {{ .Values.db_host }}
    - name: DB_USER
      value: {{ .Values.db_user }}
    - name: DB_PASSWORD
      value: {{ .Values.db_password }}
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Ausführen von Helm-Befehlen

Das ausführbare Programm Helm verfügt über eine Vielzahl von Befehlen. Wir wollen einige davon demonstrieren. Der Befehl template rendert die Diagrammvorlagen lokal und zeigt die Ergebnisse auf der Konsole an. In der folgenden Ausgabe kannst du den Vorgang in Aktion sehen. Alle Platzhalter wurden durch ihre tatsächlichen Werte ersetzt, die aus der Datei values.yaml stammen:

$ helm template .
---
# Source: Web Application/templates/web-app-service-template.yaml
...
---
# Source: Web Application/templates/web-app-pod-template.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: web-app
  name: web-app
spec:
  containers:
  - image: bmuschko/web-app:1.0.1
    name: web-app
    env:
    - name: DB_HOST
      value: mysql-service
    - name: DB_USER
      value: root
    - name: DB_PASSWORD
      value: password
    ports:
    - containerPort: 3000
      protocol: TCP
  restartPolicy: Always

Wenn du mit dem Ergebnis zufrieden bist, solltest du die Vorlagendateien in einer Chart-Archivdatei bündeln. Die Diagrammarchivdatei ist eine komprimierte TAR-Datei mit der Dateiendung .tgz. Der Befehl package wertet die Metadaten von Chart.yaml aus, um den Dateinamen des Diagrammarchivs zu ermitteln:

$ helm package .
Successfully packaged chart and saved it to: /Users/bmuschko/dev/projects/ \
cka-study-guide/ch04/templating-tools/helm/web-app-2.5.4.tgz

Eine vollständige Liste der Befehle und typischen Arbeitsabläufe findest du auf der Dokumentationsseite von Helm.

Zusammenfassung

Ressourcengrenzen sind einer der vielen Faktoren, die der Algorithmus des Zeitplannungsprogramms kube berücksichtigt, wenn er entscheidet, auf welchem Knoten ein Pod eingeplant werden kann. Ein Container kann Ressourcenanforderungen und Limits angeben. Das Zeitplannungsprogramm wählt einen Knotenpunkt auf der Grundlage seiner verfügbaren Hardwarekapazität aus.

Die deklarative Verwaltung von Manifesten ist die bevorzugte Methode zum Erstellen, Ändern und Löschen von Objekten in realen Cloud-nativen Projekten. Das zugrundeliegende YAML-Manifest soll in die Versionskontrolle eingecheckt werden und verfolgt automatisch die Änderungen an einem Objekt einschließlich seines Zeitstempels für einen entsprechenden Commit-Hash. Mit den Befehlen kubectl apply und delete können diese Vorgänge für ein oder mehrere YAML-Manifeste durchgeführt werden.

Es entstanden zusätzliche Werkzeuge für eine bequemere Manifestverwaltung. Kustomize ist vollständig in die Werkzeugkette von kubectl integriert. Es hilft bei der Erstellung, Zusammenstellung und Anpassung von Manifesten. Tools mit Templating-Funktionen wie yq und Helm können die verschiedenen Arbeitsabläufe zur Verwaltung von Anwendungsstapeln, die durch eine Reihe von Manifesten repräsentiert werden, weiter vereinfachen.

Prüfungsgrundlagen

Verstehe die Auswirkungen von Ressourcengrenzen auf die Planung

Ein Container, der durch einen Pod definiert ist, kann Requests und Limits für Ressourcen festlegen. Arbeite Szenarien durch, in denen du diese Grenzen einzeln und gemeinsam für Single- und Multi-Container-Pods definierst. Nach der Erstellung des Pods solltest du die Auswirkungen auf die Planung des Objekts auf einem Node sehen können. Übe außerdem, wie du die verfügbare Ressourcenkapazität eines Knotens ermitteln kannst.

Verwalte Objekte mit dem imperativen und deklarativen Ansatz

YAML-Manifeste sind wichtig, um den gewünschten Zustand eines Objekts auszudrücken. Du musst wissen, wie du mit dem Befehl kubectl apply Objekte erstellen, aktualisieren und löschen kannst. Der Befehl kann auf eine einzelne Manifestdatei oder auf ein Verzeichnis verweisen, das mehrere Manifestdateien enthält.

Ein umfassendes Verständnis der gängigen Templating-Tools

Kustomize, yg und Helm sind etablierte Tools für die Verwaltung von YAML-Manifesten. Ihre Templating-Funktionen unterstützen komplexe Szenarien wie das Zusammenstellen und Zusammenführen mehrerer Manifeste. Für die Prüfung solltest du einen praktischen Blick auf die Tools, ihre Funktionen und die Probleme werfen, die sie lösen.

Beispiel-Übungen

Die Lösungen zu diesen Übungen findest du im Anhang.

  1. Schreibe ein Manifest für einen neuen Pod namens ingress-controller mit einem einzelnen Container, der das Bild bitnami/nginx-ingress-controller:1.0.0 verwendet. Setze für den Container die Ressourcenanforderung auf 256Mi für den Speicher und 1 CPU. Setze die Ressourcengrenzen auf 1024Mi Arbeitsspeicher und 2,5 CPUs.

  2. Verwende das Manifest, um den Pod in einem Cluster mit drei Knoten einzuplanen. Sobald er erstellt ist, identifiziere den Knoten, auf dem der Pod läuft. Schreibe den Namen des Knotens in die Datei node.txt.

  3. Erstelle das Verzeichnis mit dem Namen manifests. Erstelle in diesem Verzeichnis zwei Dateien: pod.yaml und configmap.yaml. Die Datei pod.yaml sollte einen Pod namens nginx mit dem Bild nginx:1.21.1 definieren. Die Datei configmap.yaml definiert eine ConfigMap namens logs-config mit dem Schlüssel-Wert-Paar dir=/etc/logs/traffic.log. Erstelle beide Objekte mit einem einzigen, deklarativen Befehl.

  4. Ändere das ConfigMap-Manifest, indem du den Wert des Schlüssels dir in /etc/logs/traffic-log.txt änderst. Wende die Änderungen an. Lösche beide Objekte mit einem einzigen deklarativen Befehl.

  5. Verwende Kustomize, um einen gemeinsamen Namensraum t012 für die Ressourcendatei pod.yaml festzulegen. Die Datei pod.yaml definiert den Pod mit dem Namen nginx mit dem Bild nginx:1.21.1 ohne einen Namespace. Führe den Kustomize-Befehl aus, der das umgewandelte Manifest auf der Konsole ausgibt.

Get Zertifizierter Kubernetes Administrator (CKA) Studienführer 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.