Help improve this page
To contribute to this user guide, choose the Edit this page on GitHub link that is located in the right pane of every page.
Prevent Pods from being scheduled on accelerated nodes
Overview
Nodes with specialized processors, such as GPUs, can be more expensive to run than nodes on standard machines. To protect these nodes from workloads that don’t require special hardware, you can use Kubernetes taints. Taints mark nodes to repel Pods that don’t have matching tolerations, ensuring only compatible workloads are scheduled. For more information, see Taints and Tolerations
A taint is the complement of a node label. A label lets a workload opt in to a kind of node, but it does nothing to keep other workloads off. Only a taint makes exclusion the default, so that a Pod has to declare a toleration to land on the node. Use both together: taint the accelerated nodes so nothing lands there by accident, and label them so the workloads that belong there can find them. For the label side, see Target AI/ML workloads to specific nodes with labels.
A convention for the taint key
Any key works, but a consistent one is worth more than a descriptive one, because every workload and every add-on DaemonSet that needs an accelerated node has to spell the same key. The convention used throughout the Amazon EKS AI/ML guidance is to taint on the resource the node provides, with no value:
taints: - key: nvidia.com/gpu effect: NoSchedule
Workloads then tolerate it with operator: Exists, which matches the key regardless of value:
tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule
This pairing is what the Terraform and manifest examples in the sample-eks-docsaws.amazon.com/neuron key.
Considerations
-
Taints applied through a NodePool are reconciled. If you add or remove a taint directly on a node with
kubectl taint, EKS Auto Mode or Karpenter restores the NodePool’s definition. Change the NodePool, not the node. Nodes provisioned by EKS Auto Mode and Karpenter carry thekarpenter.sh/do-not-sync-taintslabel, which governs this behavior. -
Changing a NodePool’s taints replaces nodes. The change is drift, so existing nodes are drained and replaced rather than updated in place. Managed node groups behave differently, and update the taint on running nodes.
-
Taints can be updated after you create a managed node group using the
UpdateNodegroupConfigAPI. -
The taint key must begin with a letter or number. It can contain letters, numbers, hyphens (
-), periods (.), and underscores (_). It can be up to 63 characters long. -
Optionally, the taint key can begin with a DNS subdomain prefix and a single
/. If it begins with a DNS subdomain prefix, it can be 253 characters long. Thenvidia.com/gpukey used in this recipe takes this form. -
The value is optional and must begin with a letter or number. It can contain letters, numbers, hyphens (
-), periods (.), and underscores (_). It can be up to 63 characters long. -
When using Kubernetes directly or the AWS Management Console, the taint effect must be
NoSchedule,PreferNoSchedule, orNoExecute. However, when using the AWS CLI or API, the taint effect must beNO_SCHEDULE,PREFER_NO_SCHEDULE, orNO_EXECUTE. -
A maximum of 50 taints are allowed per node group.
-
If taints that were created using a managed node group are removed manually from a node, then Amazon EKS doesn’t add the taints back to the node. This is true even if the taints are specified in the managed node group configuration.
-
NoExecuteevicts Pods that are already running and do not tolerate the taint, unlikeNoSchedule, which only affects new scheduling decisions. UseNoScheduleunless you intend to evict running workloads.
Prerequisites
-
AWS CLI installed and configured with appropriate permissions.
-
kubectlconfigured to communicate with your cluster. For more information, see Install or update kubectl.
Step 1: Taint the accelerated nodes
Where you declare the taint depends on how you provision nodes. In all three cases the taint is applied as the node joins the cluster, so there is no window in which an untainted accelerated node is schedulable. Choose the tab for the way you provision nodes.
Confirm the taint reached the nodes:
kubectl get nodes -o custom-columns='NODE:.metadata.name,TAINTS:.spec.taints[*].key'
Step 2: Tolerate the taint in your workload
A tainted node repels every Pod that does not tolerate it, including your own GPU workloads. Add the toleration to the Pod spec, and pair it with a node selector so the Pod is both allowed onto the accelerated nodes and directed to them.
apiVersion: apps/v1 kind: Deployment metadata: name: inference spec: replicas: 1 selector: matchLabels: app: inference template: metadata: labels: app: inference spec: tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule nodeSelector: workload-class: gpu-inference containers: - name: app image: public.ecr.aws/docker/library/busybox:latest command: ["sh", "-c", "sleep 3600"] resources: limits: nvidia.com/gpu: "1"
Important
A toleration on its own does not place a Pod. It only removes the objection. Without the nodeSelector, this Pod is free to schedule anywhere in the cluster, including on general-purpose nodes where the nvidia.com/gpu limit cannot be satisfied and the Pod stays Pending. Taints and labels do different jobs, and a GPU workload needs both.
Step 3: Give add-on DaemonSets a matching toleration
Tainting accelerated nodes also repels the DaemonSets that have to run on those nodes, such as device plugins, EFA plugins, and metrics exporters. A device plugin that cannot schedule onto a GPU node never advertises the GPU resource, so the node reports no allocatable GPUs and your workloads stay Pending. This failure is easy to misread, because the node looks healthy.
EKS Auto Mode manages its own device plugins and tolerates its NodePool taints without configuration. For any component you install yourself, add the toleration to the chart values or the DaemonSet spec:
tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule
If workloads are Pending on a node that looks healthy, check whether the node advertises the resource at all:
kubectl get node <node-name> -o jsonpath='{.status.allocatable}'
Related topics
Taints repel workloads from nodes. To steer workloads toward nodes, use node labels:
-
Target AI/ML workloads to specific nodes with labels — Target workloads to the right nodes using the labels that Amazon EKS applies automatically.
-
Place distributed AI/ML workloads with network topology labels — Place the Pods of a distributed job close to each other using network topology labels.
-
kubectl taint
in the Kubernetes reference documentation.