Kubernetes-Architektur Teil 4 - Workload-Objekte

In diesem Blogbeitrag geht es um die abstrakten Komponenten der Kubernetes-Architektur, insbesondere um die verschiedenen Workload-Objekte. Im vorherigen Beiträgen konnten Sie mehr über Kubernetes-Cluster und deren Komponenten erfahren:

Workload- und Ressourcenobjekte sind wichtige Bestandteile der Kubernetes-Architektur. Diese Objekte beschreiben, wie ein Container oder eine Gruppe von Containern in Kubernetes bereitgestellt und ausgeführt wird. Oft werden die Begriffe „Kubernetes Workload Objekte“ und „Kubernetes Controller“ als absolute Synonyme verwendet, obwohl dies nicht ganz korrekt ist.

Workload-Objekte sind API-Objekte für die Bereitstellung und Verwaltung von Anwendungen und Services in Kubernetes. Ein Workload-Objekt definiert einen bestimmten gewünschten Zustand und Kubernetes sorgt dafür, dass dieser Zustand aufrechterhalten wird.

Die Controller überwachen den aktuellen Zustand des Clusters und nehmen gegebenenfalls Änderungen vor, um den Cluster in den gewünschten Zustand (Desired State) zu bringen.

Zu den gebräuchlichsten Workload-Objekten und Controllern gehören:

  • ReplicaSet
  • Deployment
  • DaemonSet
  • StatefulSet
  • Job
  • CronJob

 

ReplicaSet

Ein ReplicaSet (auch ReplicaSet Controller genannt) sorgt dafür, dass eine bestimmte Anzahl von Pods (Repliken eines Pods) in einem Cluster ausgeführt werden.

Die Begriffe ReplicaSet und ReplicaSet Controller werden oft "synonym" verwendet. Technisch gesehen handelt es sich um unterschiedliche Konzepte:

  • Ein ReplicaSet ist ein Kubernetes-Objekt (YAML-Manifest). Es definiert, wie viele Kopien eines bestimmten Pods ausgeführt werden sollen.
  • Der ReplicaSet Controller ist der Teil der Kubernetes-Steuerungsebene und dafür zuständig, dass die gewünschte Anzahl von Pods eines ReplicaSet aufrechtzuerhalten.

Die Aufgabe des ReplicaSet Controllers ist es zu erkennen, dass z.B. ein Pod beendet wurde (aus welchem Grund auch immer) und der Cluster vom gewünschten Zustand abweicht. In diesem Fall sollte der ReplicaSet-Controller den fehlgeschlagenen Pod löschen und eine Create-Anforderung an den API-Server senden, um einen neuen Pod im Cluster zu erstellen und so den gewünschten Zustand wiederherzustellen.

In den seltensten Fällen werden ReplicaSets direkt erstellt. In der Regel werden sie durch Deployments erzeugt.

Die erforderlichen Elemente eines ReplicaSets sind: Pod Template, Replicas, Selector.

Hier ist Beispiel für eine ReplicaSet-Konfiguration:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: mein-replicaset
spec:
  replicas: 3
  selector:
    matchLabels:
      app: mein-app
    matchExpressions: 
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        app: mein-app
        tier: frontend
    spec:
      containers:
      - name: mein-super-container
        image: mein-image

 

Erklärung:

  • template - hier werden die Eigenschaften der Pods definiert.
  • replicas - Anzahl der gleichzeitig laufenden Pods.
  • selector - enthält die matchLabels und/oder matchExpressions
    • matchLabels - ist ein Schlüssel-Wert-Selektor (wurde im vorherigen Beitrag ausführlich beschrieben). Alle von diesem ReplicaSet verwalteten Pods haben das Label app und den Wert mein-app.
    • matchExpressions - können zusätzlich oder anstelle von matchLabels verwendet werden und ermöglichen eine komplexere Logik bei der Auswahl von Pods.

Die matchExpressions-Komponente besteht immer aus drei Teilen: key, operator und values.

  • key: Name des Labels, für das die Bedingung gilt.
  • operator: Der Operator, der die Bedingung definiert. Die gültigen Operatoren sind: In, NotIn, Exists und DoesNotExist
  • values: Ist eine Liste von Werten, die mit dem Label (key) verglichen werden. Dies ist nur für die Operatoren In und NotIn relevant
    • In - Dieser Operator überprüft, ob der gegebene Key in der Liste der angegebenen Werte enthalten ist.
    • NotIn - Dieser Operator ist das Gegenteil von In und überprüft, ob der gegebene Key nicht in der Liste der Werte enthalten ist.
    • Exists - überprüft, ob ein bestimmtes Label unabhängig von seinem Wert vorhanden ist.
    • DoesNotExist - Dies ist das Gegenteil von Exists

 

Deployment

Wie bereits erwähnt, basiert Deployment auf ReplicaSets. Deployments ermöglichen die Aktualisierung oder Änderung von Anwendungen durch die Verwaltung der zugrunde liegenden ReplicaSets. Dies geschieht durch die Erstellung neuer ReplicaSets und die Anpassung ihrer Größe bei gleichzeitiger Reduzierung der Größe bestehender ReplicaSets. Dieser Prozess ermöglicht so genannte "Rolling Updates". "Rolling Updates" ist das schrittweise Hinzufügen neuer Objekte und das Entfernen alter Objekte, ohne dass es zu Ausfallzeiten kommt. Eine weitere wichtige Eigenschaft von Deployment ist die Möglichkeit, die vorgenommenen Änderungen durch Rollback wieder rückgängig zu machen.

Die Aktualisierung oder bessergesagt "Rolling Update" von containerbasieren Applikation funktioniert wie folgt:

  • Im ersten Schritt wird ein neues ReplicaSet für die neue Version der Applikation (neues Container-Image) bereitgestellt. Dieses neue ReplicaSet enthält zunächst keine Pods.

Kubernetes Deployment 01

  • Danach startet das Deployment neue Pods im neuen ReplicaSet und stoppt gleichzeitig die alten Pods im alten ReplicaSet. Dies geschieht schrittweise, um sicherzustellen, dass die Anwendung weiterhin verfügbar ist und die Lastverteilung sichergestellt ist.

Kubernetes Deployment 02

  • Dieser Prozess wird fortgesetzt, bis alle alten Pods gestoppt und durch neue Pods ersetzt wurden. Am Ende dieses Prozesses wird das alte ReplicaSet nicht gelöscht und bleibt mit 0 Pods bestehen. Dies ist notwendig, falls während der Aktualisierung Probleme auftreten und der Rollback-Prozess durchgeführt werden soll.

Kubernetes Deployment 03

Deployment verwendet auch die gleichen Labels, Selector und Operators wie ReplicaSets.

ReplicaSet vs. Deployment
 ReplikaSet     Deployment
  ReplicaSets sind dafür verantwortlich, eine bestimmte Anzahl von Pods einer bestimmten Spezifikation bereitzustellen und aufrechtzuerhalten.  Deployments ermöglicht die Bereitstellung neuer Versionen der Anwendungen durch die schrittweise Erstellung der Pods in den neuen ReplicaSets und die schrittweise Löschung der alten.  

 

RollingUpdate - Parameter

Das RollingUpdate kann durch die beiden Parameter maxUnavailable und maxSurge gesteuert werden. Der Parameter maxUnavailable gibt an, wie viele alte Pods gleichzeitig entfernt werden können. Der Parameter maxSurge angibt, wie viele neue Pods gleichzeitig erstellt werden können. Beide Parameter sind optional und haben den Standardwert 1.

 

kubectl Befehle für Deployment

Hier sind die gängige kubectl Befehle, die dabei helfen, die Deployments im Cluster zu verwalten und zu überwachen.

  • kubectl get deployments: Zeigt eine Liste aller Deployments im Cluster an.
  • kubectl describe deployment <deployment-name>: Gibt eine detaillierte Beschreibung des angegebenen Deployments zurück.
  • kubectl apply -f <deployment-file>: Erstellt oder aktualisiert ein Deployment auf Basis einer YAML- oder JSON-Datei.
  • kubectl scale deployment <deployment-name> --replicas=<number>: Skaliert die Anzahl der Replikate im Deployment auf die angegebene Anzahl.
  • kubectl rollout status deployment <deployment-name>: Überprüft den Rollout-Status des Deployments.
  • kubectl rollout undo deployment <deployment-name>: Setzt den Rollout auf eine vorherige Version des Deployments zurück.
  • kubectl rollout history deployment <deployment-name>: Zeigt eine Liste der Rollout-Historie des Deployments an.
  • kubectl delete deployment <deployment-name>: Löscht das angegebene Deployment aus dem Cluster.

 

DaemonSet

Das DaemonSet sorgt dafür, dass eine Kopie eines bestimmten Pods auf allen oder einigen Knoten eines Clusters läuft. Wenn ein neuer Knoten zum Cluster hinzugefügt wird, startet das DaemonSet einen Pod auch auf diesem Knoten. Mit „einige Knoten“ ist gemeint, dass DaemonSets so konfiguriert werden können, dass sie nur auf bestimmten Knoten laufen.

Die DaemonSets in Kubernetes werden häufig für hauptsächlich technische Zwecke auf den Nodes verwendet. Dazu gehören: Logging-Dienste, Überwachungssysteme, Netzwerkdienste oder Sicherheitsdienste.

Im Gegensatz zu den anderen Kubernetes Controllern (ReplicaSets und Deployments) verwendet das DaemonSet nicht nur Labels und Selectors (hier detailliert beschrieben), sondern auch das nodeSelector oder nodeAffinity Attribut, um Pods einem bestimmten Node zuzuweisen. 

Beispiel eines YAML-Manifestes für DaemonSet:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: prometheus-daemonset
  namespace: default
spec:
  selector:
    matchLabels:
      name: prometheus
  template:
    metadata:
      labels:
        name: prometheus
    spec:
      nodeSelector:
        disk: ssd
      containers:
      - name: prometheus
        image: prometheus:2.44.0
        ports:
        - containerPort: 80

 

Beschreibung einigen Spezifikationen:

  • spec: in dieser Sektion werden die DaemonSets-Spezifikationen definiert.
  • selector: - über den Selector findet das DaemonSet die zu verwaltenden Pods. In diesem Fall "name: prometheus"
  • nodeSelector: Dieser Parameter bestimmt, auf welchen Knoten die Pods laufen sollen. In diesem Fall auf den Knoten, die mit den SSDs bestückt sind ("disk: ssd").

 

StatefulSet

Ein Kubernetes StatefulSet ist eine weitere Kubernetes-Ressource, die für die Bereitstellung und Skalierung einer Gruppe von Pods verantwortlich ist. Wie der Name StatefulSet schon andeutet, handelt es sich um zustandsbehaftete Anwendungen. Die von StatefulSet erzeugten Pod Instanzen haben eine eindeutige und beständige Identität.

Diese Identität muss auch dann erhalten bleiben, wenn die Pods neu geplant, aktualisiert, gelöscht oder neu erstellt werden. Typische Anwendungsbeispiele für stateful Pods sind Datenbanken.

Hier sind einige der Hauptmerkmale von StatefulSets:

  • Unveränderliche Netzwerkidentifikation. Alle Pods in einem StatefulSet müssen eindeutige und persistente Netzwerknamen haben.
  • Unveränderliche Persistent Volumes. Jeder Pod kann einem oder mehreren Persistent Volumes zugeordnet werden. Es muss gewährleistet sein, dass die Zuordnung zu bestimmten Volumes auch nach einem Neustart noch funktioniert.
  • Geordnete Bereitstellung und Skalierung, geordnetes Löschen und Beenden. Für viele Anwendungen ist es äußerst wichtig, dass Pods in einer strikten Reihenfolge erstellt, skaliert, gelöscht und geschlossen werden. Nur so kann die Datenkonsistenz gewährleistet werden.

Headless Service

Headless Services werden zusammen mit StatefulSets verwendet. Stateful-Anwendungen erfordern oft eine direkte Kommunikation zwischen den Pods oder von außerhalb des Clusters zu einem bestimmten Pod. Dies wäre mit Mechanismen wie Load Balancer oder ClusterIP kaum realisierbar, daher wird Cluster DNS verwendet, um die gegenseitige Kommunikation der Pods über den Namen zu ermöglichen.

Beispiel StatefulSet

Hier ein einfaches Beispiel für ein YAML-Manifest für ein StatefulSet zur Bereitstellung einer Prometheus-Instanz:

Headless-Service

apiVersion: v1
kind: Service
metadata:
  name: prometheus-headless
  labels:
    app: prometheus
spec:
  clusterIP: None  # macht diesen Service zu einem Headless Service
  ports:
    - name: web
      port: 9090
      targetPort: web
  selector:
    app: prometheus
 
 
StatefulSet
 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: prometheus
spec:
  serviceName: "prometheus-headless"
  replicas: 5 
  selector:
    matchLabels:
      app: prometheus
  template:
    metadata:
      labels:
        app: prometheus
    spec:
      containers:
        - name: prometheus
          image: prom/prometheus:2.44.0
          ports:
            - name: web
              containerPort: 9090
          volumeMounts:
            - name: prometheus-data
              mountPath: /prometheus
  volumeClaimTemplates:
  - metadata:
      name: prometheus-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

 

Jobs

Wenn alle oben genannten Controller-Typen für den Start der Pods und deren kontinuierlichen Betrieb vorgesehen sind, besteht der Haupteinsatzzweck der Jobs darin, die einzelnen Tasks in einem Kubernetes-Cluster auszuführen.

Die folgenden drei Szenarien beschreiben am besten die Funktionsweise von Jobs:

  • Der Job ist dafür verantwortlich, einen oder mehrere Pods innerhalb des Kubernetes-Clusters zu erstellen.
  • Er sorgt auch dafür, dass der Pod oder die Pods ein bestimmtes Programm in einem Container ausführen. Normalerweise sollte dieses Programm bis zu seinem „natürlichen Ende“ laufen. Es sei denn, es wird aufgrund eines Fehlers oder aus einem anderen Grund unterbrochen.
  • Anschließend muss der Kubernetes-Job sicherstellen, dass die angegebene Anzahl von Pods ihre Aufgaben erfolgreich abgeschlossen hat. Wenn dies nicht der Fall ist, startet der Job diese Pods neu, um sicherzustellen, dass die Aufgaben abgeschlossen sind.

Jobs können parallel oder seriell ausgeführt werden und erzeugen mindestens einen Pod zur Ausführung der Aufgabe. Nach Beendigung des Jobs werden die Pods automatisch gelöscht.

Beispiel

Hier ist ein Beispiel einer einfachen Wartungsaufgabe. Im Container mein-container werden die Daten aus dem Ordner /var/log gelöscht, wenn sie älter als 7 Tage sind.

apiVersion: batch/v1
kind: Job
metadata:
  name: log-cleaner
spec:
  template:
    spec:
      containers:
      - name: log-cleaner
        image: mein-container
        args:
        - /bin/sh
        - -c
        - find /var/log -type f -mtime +7 -delete
      restartPolicy: OnFailure

restartPolicy: OnFailure - Die Einstellung OnFailure bedeutet, dass der Container neu gestartet wird, wenn der Prozess mit einem Fehlercode endet.

Je nach Szenario kann das obige Beispiel erweitert werden, um die Fehlertoleranz zu verbessern. Hier einige Parameter:

  • backoffLimit - definiert  wie oft Kubernetes den Job neu starten soll, wenn er fehlschlägt.
  • activeDeadlineSeconds - legt die maximale Zeit in Sekunden fest, die der Job für die Fertigstellung benötigt.  Wird die Zeit überschritten, wird der Job abgebrochen und als fehlgeschlagen markiert.
  • Completions - gibt die Anzahl der Pods an, die erfolgreich abgeschlossen sein müssen, damit der Job als abgeschlossen gilt. Der Standardwert ist 1
  • parallelism - definiert die Anzahl der Pods, die gleichzeitig ausgeführt werden können. Der Standardwert ist 1.

 

CronJobs

Ein CronJob wird verwendet, um eine bestimmte Aufgabe in regelmäßigen Abständen automatisch auszuführen. Im Gegensatz zu einem "normalen" Job, der nur einmal ausgeführt wird, kann ein CronJob zu einem bestimmten Zeitplan ausgeführt werden. Dieses Konzept ähnelt dem UNIX- oder Linux-CronJob.

Beispiel:

Hier ist ein Beispiel für einen Kubernetes CronJob, der zwei Mal täglich um 8:00 und um 18:00 Uhr einen Job ausführt, um Log-Dateien zu sichern.

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: log-backup
spec:
  schedule: "0 8,18 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: log-backup
            image: mein-container
            args:
            - /bin/sh
            - -c
            - tar czf /backup/logs-$(date +\%Y\%m\%d-\%H\%M\%S).tar.gz /var/log
          restartPolicy: OnFailure
          volumes:
          - name: backup-volume
            hostPath:
              path: /mnt/backup
          volumeMounts:
          - name: backup-volume
            mountPath: /backup

 

schedule: hier wird den Zeitplan für den CronJob definiert. In unserem Fall "0 8,18 * * *" bedeutet, dass der Job die Aufgabe zu Beginn der Stunde "0" um 8:00 und um 18:00 Uhr, jeden Tag "*", in jedem Monat "*" und an jedem Wochentag "*" ausgeführt wird.

restartPolicy: OnFailure - Die Einstellung OnFailure bedeutet, dass der Container neu gestartet wird, wenn der Prozess mit einem Fehlercode endet.

 

Healthcheck-Objekte 

Liveness, Readiness und Startup Probes sind Mechanismen in Kubernetes, um die Gesundheit von Containern in einem Pod zu überwachen und sicherzustellen, dass sie ordnungsgemäß funktionieren.

Liveness Probe

Die Liveness Probe prüft, wie der Name schon sagt, ob ein Container noch läuft. Schlägt die Liveness Probe fehl, wird der Container neu gestartet.
Die Überprüfung des Container-Status kann folgendermaßen funktionieren: Es wird ein HTTP-Request an eine bestimmte URL oder einen bestimmten Port des Containers gesendet, solange eine erwartete Antwort zurückgegeben wird, wird der Container als „lebendig“ markiert. Es kann auch ein bestimmtes Kommando innerhalb des Containers ausgeführt werden.

Hier ist ein Beispiel für eine Liveness Probe, die alle 10 Sekunden eine HTTP-Anfrage an die URL "/healthcheck" sendet:

apiVersion: v1
kind: Pod
metadata:
  name: mein-pod
spec:
  containers:
  - name: mein-container
    image: mein-image
    livenessProbe:
      httpGet:
        path: /healthcheck
        port: 80
      periodSeconds: 10

Readiness Probe

Die Readiness Probe prüft, ob der Container im Pod bereit ist, eingehende Netzwerkanfragen zu empfangen. In diesem Fall ist die Readiness Probe der Liveness Probe sehr ähnlich, jedoch mit einem wichtigen Unterschied. Wenn die Readiness Probe eines Containers fehlschlägt, wird Kubernetes keinen Netzwerkverkehr mehr an diesen Container senden, aber der Container wird nicht neu gestartet.

Ein typischer Anwendungsfall für eine Readiness Probe wäre, dass eine Anwendung eine gewisse Zeit benötigt, um zu starten (z.B. eine Datenbankverbindung zu einem Backend-Server herzustellen), bevor sie Anfragen bearbeiten kann.

Hier ist ein Beispiel für eine Readiness Probe, die alle 5 Sekunden eine TCP-Verbindung zum Port 8080 herstellt:

apiVersion: v1
kind: Pod
metadata:
  name: mein-pod
spec:
  containers:
  - name: mein-container
    image: mein-image
    readinessProbe:
      tcpSocket:
        port: 8080
      periodSeconds: 10

Startup Probe

Die Startup-Probe prüft, ob die Anwendung im Container erfolgreich gestartet wurde. Diese Probe wird verwendet, wenn die Anwendung eine lange Zeit benötigt (eine lange Initialisierungsphase haben), um zu starten. Wenn die Probe erfolgreich ist, wird davon ausgegangen, dass der Container korrekt gestartet wurde. Wenn die Startup Probe fehlschlägt, wird der Container neu gestartet.

Die Startup Probe kann verhindern, dass Kubernetes den Container ständig neu startet, weil die Liveness Probe fehlschlägt, weil die Anwendung noch nicht initialisiert wurde.

Hier ist ein Beispiel für eine Startup Probe, die eine HTTP-Anfrage an die URL "/startCheck" sendet und den Container als erfolgreich gestartet markiert, wenn eine erfolgreiche Antwort zurückgegeben wird:

apiVersion: v1
kind: Pod
metadata:
  name: mein-pod
spec:
  containers:
  - name: mein-container
    image: mein-image
    startupProbe:
      httpGet:
        path: /startCheck
        port: 8080
      failureThreshold: 30
      periodSeconds: 10

 

Kubernetes-Architektur Teil 5 - Taints & Tolerations, NodeSelector, Affinity

Wir nutzen Cookies auf unserer Website. Einige von ihnen sind essenziell für den Betrieb der Seite, während andere uns helfen, diese Website und die Nutzererfahrung zu verbessern (Tracking Cookies). Sie können selbst entscheiden, ob Sie die Cookies zulassen möchten. Bitte beachten Sie, dass bei einer Ablehnung womöglich nicht mehr alle Funktionalitäten der Seite zur Verfügung stehen.