☸ Kubernetes im Heimlabor

Kurs › Folge 21

Scheduling: nodeSelector, Taints, Affinity

Pods gezielt platzieren — und warum auf der Control Plane nichts landet.

Das Video zu dieser Folge ist noch nicht veröffentlicht. Die Notizen stehen trotzdem schon hier.

Was du danach kannst

  • mit Labels und nodeSelector Pods gezielt verteilen
  • Taints und Tolerations erklaeren, setzen und wieder entfernen
  • Affinity und Anti-Affinity fuer Ausfallsicherheit nutzen
  • erklaeren, warum dieselbe Mechanik auch bei einem Knotenausfall greift

Worum es geht

Der Scheduler sucht für jeden Pod einen Knoten aus. Meistens ist das genau richtig so — man muss ihm nicht hineinreden. Diese Folge handelt von den Fällen, in denen doch: wenn ein Dienst auf einen bestimmten Rechner gehört, wenn ein Knoten in Wartung geht, oder wenn zwei Repliken nicht auf derselben Maschine liegen dürfen.

Der Einstieg steht im eigenen Cluster: Auf der Control Plane läuft kein einziger Anwendungspod — und eingestellt hat das niemand.

nodeSelector — der einfachste Weg

Der Knoten bekommt ein Label, der Pod verlangt es:

kubectl label node w2-k8n platte=ssd
spec:
  nodeSelector:
    platte: ssd

Passt kein Knoten, bleibt der Pod auf Pending — und kubectl describe pod sagt, warum. Das ist kein Fehler, sondern eine Auskunft.

Taint und Toleration

Ein Taint ist eine Markierung am Knoten: „Hier landet nur, wer das aushält." Die Toleration steht am Pod: „Ich halte das aus."

schlüssel=wert:wirkung

Auf jeder Control Plane steht standardmäßig:

node-role.kubernetes.io/control-plane:NoSchedule

Und deshalb laufen dort keine Anwendungspods. Cilium und kube-proxy laufen trotzdem — sie bringen die passende Toleration mit:

spec:
  tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule

Die drei Wirkungen

Wirkung Bedeutung
NoSchedule Keine neuen Pods hierher. Was läuft, bleibt.
PreferNoSchedule Nur ein Wunsch — der Scheduler weicht aus, wenn er kann.
NoExecute Keine neuen Pods und die vorhandenen werden vertrieben.

Setzen und entfernen

kubectl taint nodes w2-k8n wartung=ja:NoSchedule      # setzen
kubectl taint nodes w2-k8n wartung=ja:NoSchedule-     # entfernen

Das Minus am Ende ist die ganze Syntax zum Entfernen. Und man vergisst es zuverlässig.

Der Unterschied, der oft verwechselt wird

Beide steuern die Platzierung, aber aus entgegengesetzten Richtungen:

  • nodeSelector und Affinity ziehen an — der Pod sagt, wohin er will
  • Taints stoßen ab — der Knoten sagt, wen er nicht will

Wer sicherstellen will, dass nur bestimmte Pods auf einem Knoten laufen und diese Pods nur dort, braucht beides zusammen. Genau so binden wir später Prometheus an die Control Plane.

Affinity und Anti-Affinity

nodeAffinity ist der große Bruder von nodeSelector: mehrere Bedingungen, harte (required...) und weiche (preferred...).

Wichtiger im Alltag ist Pod-Anti-Affinity: „Verteile meine Repliken auf verschiedene Knoten." Ohne sie kann der Scheduler alle drei Repliken auf denselben Rechner legen — und beim Ausfall dieses Rechners ist der Dienst komplett weg, obwohl er dreifach lief.

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
    - labelSelector:
        matchLabels:
          app: web
      topologyKey: kubernetes.io/hostname

IgnoredDuringExecution heißt: Die Regel gilt beim Platzieren, nicht rückwirkend. Ein laufender Pod wird nicht verschoben, wenn sich die Lage ändert.

Wo Kubernetes selbst Taints setzt

Und hier wird es interessant — das ist keine reine Handsteuerung:

  • kubectl drain vor einem Update setzt einen Taint und schiebt alles weg
  • bei knappem Speicher oder voller Platte setzt der Knoten memory-pressure bzw. disk-pressure
  • fällt ein Knoten aus, setzt der Node-Controller node.kubernetes.io/unreachable:NoExecute

Die Brücke zum Stromstecker

Genau der letzte Punkt ist die Mechanik hinter Folge 28:

  1. Ein Knoten meldet sich nicht mehr → nach etwa 40 Sekunden gilt er als NotReady
  2. Der Node-Controller setzt unreachable:NoExecute
  3. Jeder Pod bekommt automatisch eine Toleration für diesen Taint — mit tolerationSeconds: 300
  4. Nach fünf Minuten ist die Geduld zu Ende, und die Pods werden auf anderen Knoten neu erstellt

Diese fünf Minuten sind kein Zufallswert und keine Trägheit. Sie sind eine Voreinstellung, die man sehen, verstehen und ändern kann.

Taints sind also nicht nur ein Platzierungswerkzeug, sondern die Mechanik, mit der Kubernetes auf Ausfälle reagiert. Wer sie verstanden hat, versteht die Umverteilung beim Stromausfall von selbst.

Zum Nachlesen

Eine geordnete Übersicht über die offizielle Dokumentation steht unter Nachlesen.

Befehle aus dieser Folge

  • kubectl describe node control-1 | grep -i -A2 taint
  • kubectl label node w2-k8n platte=hdd
  • kubectl taint nodes w2-k8n wartung=ja:NoSchedule
  • kubectl taint nodes w2-k8n wartung=ja:NoSchedule-
  • kubectl get pods -o wide
  • kubectl drain w3-k8n --ignore-daemonsets

Begriffe

Taint
eine Markierung am Knoten — hier landet nur, wer das aushaelt
Toleration
die Erlaubnis am Pod, einen bestimmten Taint auszuhalten
nodeSelector
die einfachste Form der Zuweisung — Pod nennt ein Label, das der Knoten tragen muss
Affinity
dasselbe in flexibel, mit weichen und harten Bedingungen
Anti-Affinity
verteilt Repliken absichtlich auf verschiedene Knoten
tolerationSeconds
wie lange ein Pod einen NoExecute-Taint aushaelt, bevor er vertrieben wird

Übung — mach es selbst

Bau dir den Fall nach, der spaeter in echt kommt.

  1. Setze auf einen Worker einen Taint: kubectl taint nodes w2-k8n wartung=ja:NoSchedule
  2. Starte ein Deployment mit 4 Repliken. Wo landen sie? Warum nicht auf w2?
  3. Was passiert mit den Pods, die auf w2 schon liefen? (Nichts — NoSchedule gilt nur fuer Neuzugaenge.)
  4. Jetzt dasselbe mit NoExecute statt NoSchedule. Beobachte mit kubectl get pods -o wide -w, was mit den vorhandenen Pods geschieht.
  5. Taints wieder entfernen (Minus am Ende nicht vergessen).

Zusatz: Gib dem Deployment eine Anti-Affinity auf kubernetes.io/hostname und setze die Repliken auf 3, obwohl du nur 2 Worker hast. Eine Replik bleibt auf Pending — sieh dir mit kubectl describe pod an, wie der Scheduler das begruendet. Diese Meldung zu lesen ist die eigentliche Faehigkeit.