Shared Kubernetes clusters often need reserved nodes for workloads that ordinary Pods should not use. A taint marks a node as something the scheduler should repel, while a toleration in a Pod spec names the exception that is allowed through.

A NoSchedule taint blocks new scheduler placements that do not tolerate the taint. It does not evict Pods that were already running, and it does not attract matching Pods by itself. Pair a toleration with nodeSelector or node affinity when a workload must land on the reserved nodes.

In a real cluster, worker-2 is any node chosen for reserved workloads, nodepool=reserved is any scheduling label, and dedicated=reserved:NoSchedule is any taint key, value, and effect used consistently by the workload. Run from a kubectl context that can update nodes and create test Pods.

Steps to configure Kubernetes taints and tolerations:

  1. List the cluster nodes and choose the node that should accept only tolerated workloads.
    $ kubectl get nodes
    NAME            STATUS   ROLES           AGE   VERSION
    control-plane   Ready    control-plane   6d    v1.36.1
    worker-1        Ready    <none>          6d    v1.36.1
    worker-2        Ready    <none>          6d    v1.36.1
  2. Label the reserved node so matching workloads can target it.
    $ kubectl label node worker-2 nodepool=reserved --overwrite
    node/worker-2 labeled

    The label selects the reserved node. The toleration only permits scheduling onto a tainted node; it does not force the scheduler to choose that node.

  3. Add the NoSchedule taint to the reserved node.
    $ kubectl taint node worker-2 dedicated=reserved:NoSchedule
    node/worker-2 tainted

    Remove the reservation later with kubectl taint node worker-2 dedicated:NoSchedule- only after ordinary workloads may schedule there again.

  4. Confirm the node has the label and taint.
    $ kubectl describe node worker-2
    Name:               worker-2
    Roles:              <none>
    Labels:             kubernetes.io/hostname=worker-2
                        kubernetes.io/os=linux
                        nodepool=reserved
    Taints:             dedicated=reserved:NoSchedule
    Unschedulable:      false
    Conditions:
      Type    Status  Reason
      Ready   True    KubeletReady
    ##### snipped #####
  5. Create a namespace for the scheduling test.
    $ kubectl create namespace taint-demo
    namespace/taint-demo created

    Use a temporary namespace for proof Pods so cleanup does not affect application workloads.

  6. Save a Pod manifest that targets the reserved node without tolerating its taint.
    without-toleration.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: untolerated
      namespace: taint-demo
    spec:
      nodeSelector:
        nodepool: reserved
      containers:
        - name: pause
          image: registry.k8s.io/pause:3.10

    The nodeSelector makes the scheduler consider the reserved node, and the missing tolerations field should keep the Pod unscheduled.

  7. Apply the untolerated Pod manifest.
    $ kubectl apply -f without-toleration.yaml
    pod/untolerated created
  8. Check that the untolerated Pod remains unscheduled.
    $ kubectl get pod untolerated --namespace taint-demo -o wide
    NAME           READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED NODE   READINESS GATES
    untolerated    0/1     Pending   0          8s    <none>   <none>   <none>           <none>
  9. Read the scheduler warning for the untolerated Pod.
    $ kubectl events --namespace taint-demo --for pod/untolerated --types=Warning
    LAST SEEN   TYPE      REASON             OBJECT            MESSAGE
    8s          Warning   FailedScheduling   Pod/untolerated   0/3 nodes are available: 1 node(s) had untolerated taint(s), 2 node(s) didn't match Pod's node affinity/selector. no new claims to deallocate, preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

    The warning should mention the custom taint or a broader untolerated-taint message for the reserved node. Other scheduler blockers can appear in the same row when the Pod also uses node selection.
    Related: How to check Kubernetes events
    Tool: Kubernetes Events Timeline Analyzer

  10. Save a Pod manifest with a matching toleration and the same node selector.
    with-toleration.yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: tolerated
      namespace: taint-demo
    spec:
      nodeSelector:
        nodepool: reserved
      tolerations:
        - key: dedicated
          operator: Equal
          value: reserved
          effect: NoSchedule
      containers:
        - name: pause
          image: registry.k8s.io/pause:3.10

    operator: Equal requires the key, value, and effect to match the node taint. Use operator: Exists when any value for the same key and effect should be accepted.

  11. Apply the tolerated Pod manifest.
    $ kubectl apply -f with-toleration.yaml
    pod/tolerated created
  12. Wait for the tolerated Pod to become ready.
    $ kubectl wait pod/tolerated --namespace taint-demo --for=condition=Ready --timeout=180s
    pod/tolerated condition met
  13. Verify that only the Pod with the matching toleration runs on the tainted node.
    $ kubectl get pods --namespace taint-demo -o wide
    NAME           READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
    tolerated      1/1     Running   0          1s    10.244.1.2   worker-2   <none>           <none>
    untolerated    0/1     Pending   0          9s    <none>       <none>     <none>           <none>
  14. Delete the temporary scheduling-test namespace.
    $ kubectl delete namespace taint-demo
    namespace "taint-demo" deleted

    Delete only the temporary namespace used for proof Pods. Keep the node label and taint when the reserved-node policy should remain active.