Kubernetes taints, tolerations en node affinity: pod-plaatsing sturen

Kubernetes plant pods standaard op elke beschikbare node. Als je GPU-workloads op GPU-nodes wilt draaien, batchjobs op spot-instances, of tenant-workloads op dedicated hardware, combineer je taints (die pods weren van nodes), tolerations (die specifieke pods toestaan ondanks de taint) en node affinity (die pods naar nodes met de juiste labels trekt). Deze gids behandelt alle drie de mechanismen, topology spread constraints en praktijkpatronen voor productiecluster.

Doel

Na dit artikel weet je hoe je taints, tolerations, node affinity, pod anti-affinity en topology spread constraints inzet om precies te bepalen welke pods op welke nodes draaien in een Kubernetes-cluster.

Vereisten

  • Een Kubernetes-cluster op v1.28 of nieuwer met kubectl-toegang en rechten om nodes te tainten en Deployments aan te maken
  • Minimaal twee nodes in het cluster (schedulingregels hebben weinig zin met één node)
  • Bekendheid met Kubernetes Services en pod-labels. Als een pod op Pending blijft staan na het toepassen van deze regels, dan beschrijft de pod Pending-gids elke schedulerfout in detail.

Taints en tolerations: pods weren van nodes

Een taint is een eigenschap op een node die pods weert, tenzij een pod de taint expliciet tolereert. Zie het als een "verboden toegang"-bord. Een toleration op een pod zegt "ik kan daar wel mee overweg."

Een taint heeft drie delen: een key, een optionele value en een effect.

Taints toevoegen en verwijderen

# Taint toevoegen aan een node
kubectl taint nodes gpu-node-1 nvidia.com/gpu=present:NoSchedule

# Taints op een node controleren
kubectl describe node gpu-node-1 | grep Taints

# Specifieke taint verwijderen (let op het min-teken)
kubectl taint nodes gpu-node-1 nvidia.com/gpu=present:NoSchedule-

De drie taint-effecten

NoSchedule is het meest gebruikt. Nieuwe pods zonder matchende toleration komen niet op de node terecht. Pods die al draaien worden niet verwijderd. Gebruik dit voor het reserveren van GPU-nodes, spot-pools of tenant-dedicated hardware.

PreferNoSchedule is de zachte variant. De scheduler probeert de node te vermijden maar plaatst pods er toch als er geen alternatief is. Bestaande pods blijven staan. Handig als je verkeer wilt wegsturen van bepaalde nodes zonder hard te blokkeren.

NoExecute is het strengst. Pods zonder matchende toleration worden direct verwijderd (evicted). Nieuwe pods zonder toleration kunnen ook niet schedulen. Dit is het effect dat de node lifecycle controller automatisch toepast als een node not-ready of unreachable wordt.

Als een node meerdere taints heeft, evalueert Kubernetes ze als gecombineerd filter: als een ongematchte taint NoSchedule heeft, wordt de pod geblokkeerd. Als alleen PreferNoSchedule-taints ongemacht zijn, vermijdt de scheduler de node maar kan er toch op plaatsen. Als een ongematchte taint NoExecute heeft, worden draaiende pods verwijderd.

Tolerations schrijven

Tolerations staan in spec.tolerations op de pod (of in de pod-template van een Deployment). Twee operators bepalen de matching:

Equal matcht een specifieke key, value en effect:

tolerations:
- key: "nvidia.com/gpu"
  operator: "Equal"
  value: "present"
  effect: "NoSchedule"

Exists matcht elke value voor de opgegeven key:

tolerations:
- key: "nvidia.com/gpu"
  operator: "Exists"
  effect: "NoSchedule"

Een lege key met Exists tolereert alle taints op alle keys. DaemonSets als monitoring-agents gebruiken dit soms zodat ze op elke node draaien, ongeacht taints. Wees er zuinig mee.

tolerationSeconds bij NoExecute

Als een NoExecute-taint aan een node wordt toegevoegd (tijdens onderhoud of bij een nodecondition), kunnen pods met een matchende toleration aangeven hoe lang ze mogen blijven staan voor eviction:

tolerations:
- key: "node.kubernetes.io/not-ready"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 120  # pod krijgt 2 minuten om netjes af te sluiten

Ingebouwde automatische taints

De node lifecycle controller voegt automatisch taints toe bij bepaalde condities. Dit zijn de meest voorkomende:

Taint Trigger Effect
node.kubernetes.io/not-ready Node Ready-conditie is False NoExecute
node.kubernetes.io/unreachable Node Ready-conditie is Unknown NoExecute
node.kubernetes.io/memory-pressure MemoryPressure-conditie NoSchedule
node.kubernetes.io/disk-pressure DiskPressure-conditie NoSchedule
node.kubernetes.io/pid-pressure PIDPressure-conditie NoSchedule

De DaemonSet-controller voegt automatisch tolerations toe voor deze ingebouwde taints, zodat systeempods (CNI-plugins, logcollectors, monitoring-agents) blijven draaien. Maar als je eigen taints aan nodes toevoegt, tolereert een DaemonSet die niet automatisch. Dan moet je de toleration expliciet aan de DaemonSet-spec toevoegen.

Node affinity: pods naar nodes trekken

Taints weren. Node affinity doet het omgekeerde: het trekt pods naar nodes met specifieke labels. Het vervangt de oudere nodeSelector met expressievere matching.

Required vs. preferred

requiredDuringSchedulingIgnoredDuringExecution is een harde regel. De pod blijft Pending als geen enkele node matcht. IgnoredDuringExecution betekent dat de pod niet verwijderd wordt als nodelabels veranderen nadat de pod is ingepland.

preferredDuringSchedulingIgnoredDuringExecution is een zachte voorkeur. De scheduler probeert een match te vinden maar plant de pod ergens anders in als dat niet lukt. Elke voorkeur heeft een weight tussen 1 en 100. Hoger gewicht betekent sterkere voorkeur.

YAML-voorbeeld

apiVersion: v1
kind: Pod
metadata:
  name: gpu-training-job
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: nvidia.com/gpu.present    # moet een GPU hebben
            operator: In
            values:
            - "true"
          - key: topology.kubernetes.io/zone  # en in een van deze zones
            operator: In
            values:
            - eu-west-1a
            - eu-west-1b
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: node.kubernetes.io/instance-type
            operator: In
            values:
            - p4d.24xlarge   # voorkeur voor de grotere GPU-instance
  containers:
  - name: training
    image: ml-training:v2.3
    resources:
      limits:
        nvidia.com/gpu: 1

Operators en termlogica

Node affinity ondersteunt zes operators: In, NotIn, Exists, DoesNotExist, Gt en Lt. De laatste twee vergelijken integer-labelwaarden.

Meerdere nodeSelectorTerms worden ge-ORd: de pod wordt ingepland als een van de terms matcht. Meerdere expressies binnen een enkele matchExpressions-lijst worden ge-ANDd: allemaal moeten matchen. Als je nodeSelector en nodeAffinity combineert, moeten beide voldoen.

Veelgebruikte nodelabels

Deze labels worden automatisch gezet op de meeste cloud-provisioned nodes en zijn veilig om te gebruiken als affinity-target:

kubernetes.io/arch: amd64        # of arm64
kubernetes.io/os: linux           # of windows
node.kubernetes.io/instance-type: m5.large
topology.kubernetes.io/region: eu-west-1
topology.kubernetes.io/zone: eu-west-1a

De volledige lijst staat in de Kubernetes-referentiedocumentatie.

Waarom je zowel taints als affinity nodig hebt voor exclusieve plaatsing

Dit is het punt dat de meeste gidsen overslaan. Een toleration alleen laat een pod op een getainte node draaien, maar niets voorkomt dat diezelfde pod op een niet-getainte node terechtkomt. Node affinity alleen trekt een pod naar specifieke nodes, maar blokkeert andere pods niet van die nodes.

Voor exclusieve plaatsing (GPU-pods alleen op GPU-nodes, niets anders op GPU-nodes) heb je beide nodig:

  • Taint de node zodat niet-GPU-pods er niet op kunnen landen
  • Voeg een toleration toe zodat GPU-pods wel mogen
  • Voeg node affinity toe zodat GPU-pods er ook daadwerkelijk naartoe gestuurd worden

Zonder alle drie komt ofwel de verkeerde pod op de node, ofwel de juiste pod op de verkeerde node.

Pod affinity en anti-affinity

Inter-pod affinity en anti-affinity sturen scheduling op basis van labels van pods die al draaien op een node, niet labels op de node zelf.

topologyKey

Het topologyKey-veld bepaalt wat "dezelfde locatie" betekent. Het is een nodelabel-key waarvan de waarde het topologiedomein definieert:

topologyKey Betekenis
kubernetes.io/hostname Dezelfde node
topology.kubernetes.io/zone Dezelfde beschikbaarheidszone
topology.kubernetes.io/region Dezelfde regio

Pod affinity: co-locatie voor latency

Dwing een pod af op dezelfde node als een Redis-cache waar hij van afhankelijk is:

spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - redis-cache
        topologyKey: kubernetes.io/hostname

Pod anti-affinity: spreiden voor beschikbaarheid

Voorkom dat twee replica's van dezelfde Deployment op dezelfde node terechtkomen:

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - web-server
        topologyKey: kubernetes.io/hostname

Gebruik preferredDuringSchedulingIgnoredDuringExecution in plaats van required als het aantal replica's het aantal nodes kan overschrijden. Een harde anti-affinity-regel met 5 replica's en 3 nodes laat 2 pods voor altijd op Pending staan.

Performanceopmerking

Pod affinity en anti-affinity vereisen dat de scheduler pod-labels over het hele cluster controleert bij elke schedulingcyclus. In clusters met honderden nodes of duizenden pods voegt dit aanzienlijke latency toe aan schedulingbeslissingen. Voor simpelweg spreiden zijn topology spread constraints efficienter.

Topology spread constraints

Topology spread constraints verdelen pods gelijkmatig over faaldomein (zones, nodes, regio's) zonder de per-pod label-scanning overhead van pod anti-affinity.

Kernvelden

spec:
  topologySpreadConstraints:
  - maxSkew: 1                             # max verschil in podcount tussen domeinen
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule       # of ScheduleAnyway
    labelSelector:
      matchLabels:
        app: web

maxSkew is het maximaal toegestane verschil in podcount tussen het drukste en het leegste topologiedomein. Moet groter zijn dan 0. Een maxSkew van 1 met 3 zones en 6 replica's betekent een 2-2-2-verdeling.

whenUnsatisfiable bepaalt wat er gebeurt als de constraint niet gehaald kan worden. DoNotSchedule houdt de pod op Pending (gebruik dit voor HA-kritieke services). ScheduleAnyway plaatst de pod toch maar geeft voorkeur aan nodes die de scheefheid minimaliseren.

minDomains (GA sinds Kubernetes 1.30) stelt het minimumaantal topologiedomeinen in dat moet bestaan voordat de constraint geldt. Alleen geldig met DoNotSchedule.

Dubbele constraint: spreiden over zones en nodes

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone   # strikte zonebalans
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web
- maxSkew: 2
  topologyKey: kubernetes.io/hostname        # zachte nodebalans
  whenUnsatisfiable: ScheduleAnyway
  labelSelector:
    matchLabels:
      app: web

Dit geeft strikte zoneverdeling (geen zone heeft meer dan 1 extra pod) terwijl er wat nodeongelijkheid binnen een zone mag zijn. In de praktijk is dit het patroon dat ik het vaakst zie in productieclusters met stateless webservices.

Praktijkpatronen

GPU-node-isolatie

Dure GPU-nodes moeten alleen GPU-workloads draaien. De combinatie van taint + toleration + affinity dwingt dit af:

# GPU-nodes labelen en tainten
kubectl label nodes gpu-node-1 accelerator=nvidia-a100
kubectl taint nodes gpu-node-1 nvidia.com/gpu=present:NoSchedule

GKE voegt automatisch de taint nvidia.com/gpu=present:NoSchedule toe als je GPU-nodepools aanmaakt.

De GPU-Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-training
spec:
  replicas: 2
  selector:
    matchLabels:
      app: ml-training
  template:
    metadata:
      labels:
        app: ml-training
    spec:
      tolerations:
      - key: "nvidia.com/gpu"
        operator: "Equal"
        value: "present"
        effect: "NoSchedule"
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: accelerator
                operator: In
                values:
                - nvidia-a100
      containers:
      - name: training
        image: ml-training:v2.3
        resources:
          limits:
            nvidia.com/gpu: 1   # GPU-resources alleen in limits, niet in requests

GPU-resources moeten in limits staan, niet in requests. De NVIDIA-, AMD- of Intel-deviceplugin moet geinstalleerd zijn op het cluster.

Spot / preemptible node-isolatie

Fouttolerante batchworkloads draaien op goedkopere spot-instances. Kritieke workloads blijven op on-demand-nodes. Elke cloudprovider gebruikt andere taint-keys:

Cloud Spot-nodelabel Automatische taint
AKS kubernetes.azure.com/scalesetpriority=spot kubernetes.azure.com/scalesetpriority=spot:NoSchedule
GKE cloud.google.com/gke-spot=true cloud.google.com/gke-spot=true:NoSchedule
EKS eks.amazonaws.com/capacityType=SPOT Custom (meestal spot=true:NoSchedule)

Een batchworker die spot-nodes tolereert en eviction netjes afhandelt:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: batch-worker
spec:
  replicas: 8
  selector:
    matchLabels:
      app: batch-worker
  template:
    metadata:
      labels:
        app: batch-worker
    spec:
      tolerations:
      - key: "spot"
        operator: "Equal"
        value: "true"
        effect: "NoSchedule"
      - key: "node.kubernetes.io/not-ready"
        operator: "Exists"
        effect: "NoExecute"
        tolerationSeconds: 120   # 2 minuten respijt als spot wordt teruggenomen
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: node.kubernetes.io/capacity-type
                operator: In
                values:
                - spot
      containers:
      - name: worker
        image: batch-processor:v1.8

Combineer met een PodDisruptionBudget om te beperken hoeveel pods tegelijk onbeschikbaar mogen zijn als spot-nodes worden teruggenomen:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: batch-worker-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: batch-worker

Gebruik je Karpenter in plaats van de cluster autoscaler, definieer dan aparte NodePools voor spot en on-demand met verschillende karpenter.sh/capacity-type-requirements en bijbehorende taints.

Multi-tenant dedicated nodes

Elke tenant krijgt nodes waar alleen hun workloads op draaien:

kubectl label nodes tenant-a-node-1 tenant=team-alpha
kubectl taint nodes tenant-a-node-1 tenant=team-alpha:NoSchedule
spec:
  tolerations:
  - key: "tenant"
    operator: "Equal"
    value: "team-alpha"
    effect: "NoSchedule"
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: tenant
            operator: In
            values:
            - team-alpha

Een belangrijk voorbehoud: taints en tolerations zijn geen securitygrens. Een verkeerd geconfigureerde pod kan gewoon een matchende toleration toevoegen en de taint omzeilen. Voor echte multi-tenant-isolatie dwing je tolerationbeperkingen af met een policy-engine als Kyverno of OPA/Gatekeeper. Voor labelsecurity gebruik je het prefix node-restriction.kubernetes.io/ zodat kubelet die labels niet zelf kan aanpassen.

Resultaat verifieren

Na het toepassen van taints, tolerations en affinity-regels verifieer je of alles goed terechtgekomen is:

# Controleer alle taints op alle nodes
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

# Bevestig dat de pod op de verwachte node draait
kubectl get pod <pod-name> -o wide

# Als een pod op Pending blijft staan, lees de schedulerevents
kubectl describe pod <pod-name>

De Events-sectie in kubectl describe pod laat precies zien welk schedulerfilter faalde. Zoek naar berichten als 0/5 nodes are available: 3 node(s) had untolerated taint {nvidia.com/gpu: present}. De pod Pending-gids beschrijft elke schedulerfoutmelding in detail.

Veelvoorkomende problemen

Symptoom Waarschijnlijke oorzaak Oplossing
Pod Pending met "untolerated taint" Ontbrekende toleration in podspec Voeg de matchende toleration toe
Pod Pending met "didn't match node affinity" Node mist het vereiste label Voeg het label toe aan de node of versoepel naar preferred
Pod Pending met "didn't match pod anti-affinity" Meer replica's dan nodes Schakel over naar preferred anti-affinity of voeg nodes toe
Pod komt op verkeerde node ondanks affinity Toleration zonder node affinity Voeg node affinity toe om de pod te sturen, niet alleen toe te staan
DaemonSet draait niet op getainte node Custom taint zonder toleration in DaemonSet Voeg de custom taint-toleration toe aan de DaemonSet-spec

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.