Zum Inhalt

Pods auf Nodes verteilen

Ziel

In diesem Projekt geht es um den Scheduler und wie Pods auf Nodes verteilt werden. Sie werden:

  • Node-Selektoren verwenden
  • Node-Affinity und Pod-(Anti-)Affinity verwenden
  • Taints und Tolerations testen
  • Resource Requests und Limits setzen

Hilfsmittel

  • Versuchen Sie, die unten stehenden Aufgaben mit Hilfe der Folien und der Cheatsheets eigenständig zu lösen.
  • Sollten Sie dabei Probleme haben, finden Sie bei jeder Aufgabe einen ausklappbaren Block, in dem der Lösungsweg beschrieben wird.

Aufgabe 1: Kubernetes Scheduling verstehen

Der Kubernetes Scheduler ist für die Zuweisung von Pods zu Nodes verantwortlich. Er berücksichtigt dabei verschiedene Kriterien wie Ressourcenverfügbarkeit, Scheduling-Richtlinien, Affinitäts- und Anti-Affinitätsspezifikationen, Taints und Tolerations.

  • Erstellen Sie eine Deployment YAML-Datei scheduling.yaml und definieren Sie ein Deployment, das das nginx-Image verwendet und 10 Replikate spezifiziert:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment-scheduling
spec:
  replicas: 10
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
  • Wenden Sie die Deployment-Datei an und überprüfen Sie, wie die Pods auf den Nodes verteilt werden
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f scheduling.yaml
kubectl get deployments
kubectl get pods -o wide

Aufgabe 2: Node-Selektoren verwenden

Node-Selektoren sind eine Möglichkeit, Pods auf bestimmte Nodes zu beschränken. Sie können verwendet werden, um Pods auf Nodes mit bestimmten Eigenschaften zu platzieren.

Aufgabe 2.1: Node mit Labels versehen

Der Training-Cluster hat drei Worker-Nodes, diese Nodes sind alle identisch:

k get nodes
NAME                        STATUS   ROLES           AGE    VERSION
code-0-worker-wppfn-5k28c   Ready    <none>          3d1h   v1.28.6
code-0-worker-wppfn-hx7qs   Ready    <none>          3d1h   v1.28.6
code-0-worker-wppfn-v5vts   Ready    <none>          3d1h   v1.28.6

Für diese Übung werden wir einen von ihnen als node-type=gpu bezeichnen und ein Deployment darauf planen.

  • Labeln Sie den ersten Node mit node-type=gpu:
kubectl label node <node_name> node-type=gpu
  • Überprüfen Sie, ob der Node gelabelt wurde:
kubectl get nodes --show-labels

Aufgabe 2.2: Deployment mit Node-Selektor erstellen

  • Kopieren Sie das Deployment aus Aufgabe 1 in die neue Datei node-selector.yaml und fügen Sie einen Node-Selektor hinzu, um die Pods auf den Node mit dem Label node-type=gpu zu beschränken
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      nodeSelector:
        node-type: gpu
      containers:
      - name: nginx
        image: nginx
  • Wenden Sie die Deployment-Datei an und überprüfen Sie, wie die Pods auf den Nodes verteilt werden
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f nodeselector.yaml
kubectl get pods -o wide

Aufgabe 3: Node-Affinity

Node-Affinity ist eine erweiterte Möglichkeit, Pods auf Nodes zu platzieren. Es ermöglicht Ihnen, Regeln zu definieren, die bestimmen, auf welchen Nodes ein Pod geplant werden soll.

Aufgabe 3.1: Deployment mit Node-Affinity

  • Erstellen Sie eine Deployment-Datei node-affinity.yaml und fügen Sie das folgende Deployment hinzu:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
  • Erweitern Sie das Deployment, um eine Node-Affinität zu node-type=gpu zu haben
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-nginx
  labels:
    exercise: node-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node-type
                operator: In
                values:
                - gpu
  • Wenden Sie das Deployment auf Ihrem Cluster an und beobachten Sie die Verteilung der Pods auf den Nodes
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f gpu-deployment.yaml
kubectl get pods -o wide

Aufgabe 4: Pod Resource Requests und Limits

Aufgabe 4.1: Ressourcen für einen Pod anfordern

  • Erstellen Sie eine Pod-Datei request-ressources.yaml und fügen Sie den folgenden Pod-Manifest hinzu.
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-server
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
  • Erweitern Sie das Pod-Manifest, um Ressourcenanforderungen und -limits für CPU und Speicher:
  • Anforderungen: 0,5 CPU und 512Mi Speicher
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
---
apiVersion: v1
kind: Pod
metadata:
  name: requests-demo
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
    resources:
      requests:
        memory: "512Mi"
        cpu: "500m"
  • Wenden Sie das Pod-Manifest auf den Cluster an und überprüfen Sie, ob der Pod erstellt wurde und inspizieren Sie die Ressourcenanforderungen und -limits mit dem kubectl describe pod-Befehl
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f request-ressources.yaml
kubectl describe pod requests-demo

Aufgabe 4.2: Ressourcen für einen Pod begrenzen

  • Erstellen Sie eine Datei limit-ressources.yaml mit folgendem Pod-Manifest:
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-server
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
  • Erweitern Sie das Pod-Manifest, um Ressourcenlimits für CPU und Speicher:
    • Begrenzen Sie die CPU auf 1 CPU und den Speicher auf 1Gi
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
---
apiVersion: v1
kind: Pod
metadata:
  name: limit-demo
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
    resources:
      limits:
        memory: "1Gi"
        cpu: "1"
  • Wenden Sie das Pod-Manifest auf den Cluster an und überprüfen Sie, ob der Pod erstellt wurde und inspizieren Sie die Ressourcenlimits mit dem kubectl describe pod-Befehl
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f limit-ressources.yaml
kubectl describe pod limit-demo

Aufgabe 4.3: Resourcenanforderungen und -limits verbinden

  • Erstellen Sie eine Datei requests-and-limits-demo.yaml mit folgendem Pod-Manifest:
---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-server
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
  • Erweitern Sie das Pod-Manifest, um Ressourcenanforderungen und -limits für CPU und Speicher:
  • Anforderungen: 0,5 CPU und 512Mi Speicher
  • Begrenzen Sie die CPU auf 1 CPU und den Speicher auf 1Gi
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
---
apiVersion: v1
kind: Pod
metadata:
  name: modify-demo
  labels:
    exercise: resources
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4-alpine
    resources:
      requests:
        memory: "512Mi"
        cpu: "500m"
      limits:
        memory: "1Gi"
        cpu: "1"
  • Wenden Sie das Pod-Manifest auf den Cluster an und überprüfen Sie, ob der Pod erstellt wurde und inspizieren Sie die Ressourcenanforderungen und -limits mit dem kubectl describe pod-Befehl
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f modify-ressources.yaml
kubectl describe pod modify-demo

Aufgabe 4.4: Out of Memory (OOM) Error provozieren

Um zu verstehen, wie Kubernetes auf ein Pod reagiert, der seinen Speicherlimit überschreitet, erstellen Sie einen Pod, der absichtlich mehr Speicher verwendet als ihm zugewiesen ist.

  • Erstellen Sie eine Datei oom-example.yaml und fügen Sie das folgende Pod-Manifest hinzu. Dieses Manifest enthält einen Container, der versucht, mehr Speicher zu verbrauchen, als sein Limit:
---
apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  labels:
    exercise: resources
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      requests:
        memory: "100Mi"
      limits:
        memory: "200Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]
  • Wenden Sie das Pod-Manifest auf den Cluster an
  • Beobachten Sie den Pod-Status mit dem Befehl watch kubectl get pods

Der Pod sollte sich in einem CrashLoopBackOff-Zustand befinden und der Befehl kubectl describe pod memory-demo sollte eine OOMKilled-Meldung in den Events des Pods anzeigen.

Aufgabe 5: Pod Affinity und Anti-Affinity (Optional)

Angenommen Sie haben einen Backend-Service mit dem Label role=backend und möchten Frontend-Pods mit diesen Backend-Pods auf den gleichen Nodes laufen lassen.

Aufgabe 5.1: Pod Affinity

  • Erstellen Sie eine Deployment-Datei pod-affinity.yaml und fügen Sie die folgenden Deployments hinzu:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
      role: backend
  template:
    metadata:
      labels:
        app: backend
        role: backend
    spec:
      containers:
      - name: backend
        image: alpine
        command: ["/bin/sh", "-c"]
        args: ["sleep infinity"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      role: frontend
  template:
    metadata:
      labels:
        app: nginx
        role: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
  • Erweitern Sie die Deployments um Pod-Affinität für Pods mit dem Label role=backend, um sicherzustellen, dass die Frontend-Pods auf den gleichen Nodes wie die Backend-Pods laufen
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
  • Fügen Sie die folgende Pod-Affinität unter affinity in Ihrer Deployment-Spezifikation hinzu:
podAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
  - labelSelector:
      matchLabels:
        role: backend
    topologyKey: "kubernetes.io/hostname"
  • Sie sollten jetzt ein Deployment-Manifest ähnlich dem folgenden haben:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
      role: backend
  template:
    metadata:
      labels:
        app: backend
        role: backend
    spec:
      containers:
      - name: backend
        image: alpine
        command: ["/bin/sh", "-c"]
        args: ["sleep infinity"]
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      role: frontend
  template:
    metadata:
      labels:
        app: nginx
        role: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                role: backend
            topologyKey: "kubernetes.io/hostname"
  • Wenden Sie das Deployment auf Ihrem Cluster an und beobachten Sie die Verteilung der Pods auf den Nodes
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
kubectl apply -f frontend-backend-deployment.yaml
kubectl get pods -o wide

Aufgabe 5.2: Pod Anti-Affinity

Angenommen Sie haben ein Deployment, das über verschiedene Availability Zones verteilt werden muss. Sie können Pod Anti-Affinity verwenden, um sicherzustellen, dass Pods nicht auf dem gleichen Node geplant werden. Der Einfachheit halber gehen wir davon aus, dass jeder Node in einer anderen Availability Zone ist.

  • Erstellen Sie eine Datei anti-affinity.yaml und fügen Sie das folgende Deployment hinzu
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      role: frontend
  template:
    metadata:
      labels:
        app: nginx
        role: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
  • Erweitern Sie das Deployment, um Pod Anti-Affinity zu Pods mit dem Label role=frontend, um zu verhindern, dass Pods auf demselben Node geplant werden
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
  • Fügen Sie die folgende Pod Anti-Affinity unter affinity in Ihrer Deployment-Spezifikation hinzu:
podAntiAffinity:
  requiredDuringSchedulingIgnoredDuringExecution:
  - labelSelector:
      matchExpressions:
      - key: role
        operator: In
        values:
        - frontend
    topologyKey: "kubernetes.io/hostname"
  • Ihr Deployment sollte jetzt ein Manifest ähnlich dem folgenden sein:
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-nginx
  labels:
    exercise: anti-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
      role: frontend
  template:
    metadata:
      labels:
        app: nginx
        role: frontend
    spec:
      containers:
      - name: nginx
        image: nginx:1.25.4-alpine
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: role
                operator: In
                values:
                - frontend
            topologyKey: "kubernetes.io/hostname"
  • Wenden Sie das Manifest auf Ihrem Cluster an und beobachten Sie die Verteilung der Pods auf den Nodes mit kubectl get pods -o wide

Aufgabe 5.2.1: Pod Anti-Affinity prüfen

Können wir wirklich sicher sein, dass die Pod Anti-Affinity dazu geführt hat, dass die Pods auf verschiedenen Nodes geplant wurden? Oder war das Zufall? Lassen Sie uns das überprüfen!

  • Skalieren Sie das Deployment auf 4 Replicas
  • Überprüfen Sie die Verteilung der Pods auf den Nodes
  • Schauen Sie sich alle Pods an und überprüfen Sie die Events für jeden Pod

Aufgabe 6: Taints und Tolerations (Optional)

Aufgabe 6.1: Node tainten

Wir haben bereits ein Label node-type=gpu auf einem Node hinzugefügt. Jetzt werden wir einen Taint hinzufügen, um zu verhindern, dass Pods auf diesem Node geplant werden, es sei denn, sie haben eine Toleration für diesen Taint.

  • Tainten Sie den ersten Node mit node-type=gpu:
kubectl taint node <node_name> node-type=gpu:NoSchedule

Der Taint ist ein Key-Value-Paar, das auf einem Node angewendet wird. Er wird verwendet, um Pods davon abzuhalten, auf dem Node geplant zu werden, es sei denn, der Pod hat eine passende Toleration.

Aufgabe 6.2: Deployment mit Toleration

Erstellen Sie eine Deployment-Datei toleration.yaml und fügen Sie das folgende Deployment hinzu:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gpu-app
  template:
    metadata:
      labels:
        app: gpu-app
    spec:
      containers:
      - name: cuda-app
        image: alpine
        command: ["sleep"]
        args: ["infinity"]
      nodeSelector:
        node-type: gpu
  • Wenden Sie das Deployment auf Ihrem Cluster an und beobachten Sie, wie die Pods auf den Nodes verteilt werden

Der Pod befindet sich im Zustand pending und wird nicht auf einem Node geplant. Das liegt daran, dass die Nodes mit node-type=gpu getaintet sind und die Pods keine Toleranz für diesen Taint haben.

  • Erweitern Sie das Deployment, um eine Toleranz für den node-type=gpu Taint hinzuzufügen
Lösung (Klicken Sie auf den Pfeil, falls Sie nicht weiterkommen)
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-app
  labels:
    exercise: taints
spec:
  replicas: 2
  selector:
    matchLabels:
      app: gpu-app
  template:
    metadata:
      labels:
        app: gpu-app
    spec:
      containers:
      - name: cuda-app
        image: alpine
        command: ["sleep"]
        args: ["infinity"]
      nodeSelector:
        node-type: gpu
      tolerations:
      - key: "node-type"
        operator: "Equal"
        value: "gpu"
        effect: "NoSchedule"
  • Wenden Sie das Deployment auf Ihrem Cluster an und beobachten Sie, wie die Pods auf den Nodes verteilt werden
  • Nutzen Sie kubectl get pods -o wide um zu überprüfen, dass alle Replikate auf dem gpu Node geplant wurden

Aufgabe 6.3: Taint entfernen

  • Entfernen Sie den Taint von der Node
kubectl taint node <node_name> node-type:NoSchedule-

Clean Up

  1. Delete the deployment:
    kubectl delete -l exercise=taints deployment