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:
- 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
- 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.
- 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.
- 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 ##### - 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.
Related: How to create a Kubernetes namespace
- 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.
- Apply the untolerated Pod manifest.
$ kubectl apply -f without-toleration.yaml pod/untolerated created
- 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>
- 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 - 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.
- Apply the tolerated Pod manifest.
$ kubectl apply -f with-toleration.yaml pod/tolerated created
- Wait for the tolerated Pod to become ready.
$ kubectl wait pod/tolerated --namespace taint-demo --for=condition=Ready --timeout=180s pod/tolerated condition met
- 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>
- 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.