Back to Blog

Kubernetes Scheduling & Resource Management for the KCNA Exam: Requests, Limits, QoS Classes, Affinity, Taints & Tolerations

A practitioner's guide to Kubernetes scheduling and resource management for the KCNA exam. Understand how the scheduler places Pods, how requests and limits work, QoS classes and eviction order, nodeSelector, node and pod affinity, taints and tolerations, and DaemonSets — with manifests, kubectl commands, and the exam traps that trip candidates up.

By Sailor Team , July 25, 2026

Introduction

Pod scheduling and resource management is one of the quietest but most heavily tested corners of the Kubernetes and Cloud Native Associate (KCNA) exam. It lives inside the Kubernetes Fundamentals domain — the largest single section of the exam at roughly 44–46% of your score — and it shows up again in Container Orchestration questions about troubleshooting and cluster behavior.

The reason it matters so much is that scheduling sits at the exact point where your intent (a manifest that says “run this Pod”) meets the cluster’s reality (nodes with finite CPU and memory). Understanding how the scheduler makes its decision, and how the numbers you write in a manifest change that decision, is what separates candidates who memorize definitions from candidates who can predict what a cluster will actually do.

The KCNA never drops you into a terminal — it’s a proctored, multiple-choice exam of roughly 60 questions in 90 minutes (see the KCNA Exam Format breakdown). So the goal here is not to make you a scheduler tuning expert; it’s to make you fluent enough that when a question describes a Pod stuck in Pending, or asks which Pod gets evicted first under memory pressure, the answer is obvious.

This guide walks the topic the way a practitioner learns it: how the scheduler works, how requests and limits shape placement, how Quality of Service classes decide who survives, and then the placement controls — nodeSelector, affinity, taints, and tolerations — that let you steer Pods onto (or away from) specific nodes.

How the Kubernetes Scheduler Actually Works

When you create a Pod, it doesn’t run immediately. The Pod object is written to the API server and stored in etcd with its spec.nodeName field empty. The Pod is now unscheduled — it exists as a record but isn’t assigned to any node. It sits in the Pending phase.

The kube-scheduler, a control-plane component, watches for these unassigned Pods. For each one it runs a two-step algorithm:

  1. Filtering (predicates): The scheduler eliminates every node that cannot run the Pod. A node is filtered out if it doesn’t have enough allocatable CPU or memory to satisfy the Pod’s requests, if the Pod’s nodeSelector doesn’t match the node’s labels, if a taint on the node isn’t tolerated, or if a required affinity rule can’t be met. What remains is the list of feasible nodes.

  2. Scoring (priorities): Among the feasible nodes, the scheduler ranks them and picks the best. Scoring favors things like spreading Pods across nodes, packing onto nodes that already have the container image, and honoring preferred affinity rules.

The scheduler then performs binding — it sets the Pod’s nodeName to the winning node. The kubelet on that node notices a Pod assigned to it, pulls the images, and starts the containers.

Two consequences of this design come up constantly on the exam:

  • If no node passes filtering, the Pod stays Pending indefinitely. The scheduler does not create capacity; it only places Pods on nodes that already fit. (A Cluster Autoscaler can add nodes, but that’s a separate component, not the scheduler.)
  • The scheduler only decides placement. It doesn’t start containers — that’s the kubelet’s job — and it doesn’t keep Pods running. Understanding this division of labor connects directly to the Kubernetes architecture fundamentals you need for the broader domain.

You can inspect a scheduling decision (or failure) with:

kubectl describe pod my-pod
# Look at the Events section:
#   Warning  FailedScheduling  0/3 nodes are available:
#   3 Insufficient cpu.

That FailedScheduling event, and the human-readable reason after it, is the single most useful troubleshooting signal for scheduling problems.

Requests and Limits: The Numbers That Drive Everything

Resource requests and limits are the two values that most influence scheduling and runtime behavior. They’re set per container, for CPU and memory:

apiVersion: v1
kind: Pod
metadata:
  name: web
spec:
  containers:
    - name: app
      image: nginx:1.27
      resources:
        requests:
          cpu: "250m"       # 0.25 of a CPU core
          memory: "128Mi"
        limits:
          cpu: "500m"       # 0.5 of a CPU core
          memory: "256Mi"

The distinction is worth memorizing precisely, because the KCNA loves to test it:

ConceptWhat it meansWho uses it
RequestThe amount of CPU/memory the container is guaranteed. Reserved on the node.The scheduler — it only places a Pod on a node with enough unreserved capacity to meet the request.
LimitThe maximum CPU/memory the container may use.The kubelet / container runtime — it enforces the ceiling at runtime.

The units trip people up, so nail them down:

  • CPU is measured in cores. 1 = one full core; 500m = 500 millicores = half a core. CPU is a compressible resource — exceed your CPU limit and the container is throttled (slowed down), not killed.
  • Memory is measured in bytes, usually with Mi (mebibytes) or Gi (gibibytes). Memory is incompressible — exceed your memory limit and the container is killed with an OOMKilled (Out Of Memory) event, then restarted per its restart policy.

This CPU-throttle-vs-memory-kill asymmetry is a favorite exam trap. A question describing a container that keeps restarting with OOMKilled is pointing at a memory limit that’s too low. A container that’s simply “slow” but stable is hitting a CPU limit.

Scheduling Is Based on Requests, Not Actual Usage

A critical subtlety: the scheduler reserves capacity based on requests, not on what a Pod is actually using right now. A node with 4 cores can be fully “booked” by Pods requesting 4 cores total even if those Pods are idle. New Pods requesting CPU will then fail to schedule and sit Pending — even though the node’s real CPU usage looks low. If a KCNA question shows a node with low utilization but Pods that won’t schedule, over-committed requests are the cause.

Quality of Service (QoS) Classes

When a node runs low on memory, the kubelet must evict Pods to reclaim it. It decides the order using the QoS class Kubernetes assigns each Pod automatically, based purely on how its requests and limits are configured:

QoS ClassConditionEviction priority
GuaranteedEvery container has requests equal to limits for both CPU and memory.Evicted last — most protected.
BurstableAt least one container has a request or limit set, but they don’t all match (or only some are set).Evicted after BestEffort, before Guaranteed.
BestEffortNo container sets any requests or limits.Evicted first — least protected.

You don’t set the QoS class directly; Kubernetes derives it. You can read it back:

kubectl get pod web -o jsonpath='{.status.qosClass}'
# Guaranteed

The exam pattern here is: “Under memory pressure, which Pod is evicted first?” The answer is always the BestEffort Pod (no requests/limits), then Burstable, and Guaranteed Pods are the safest. If you want a workload to be as protected as possible, give it equal requests and limits so it lands in the Guaranteed class.

Steering Pods onto Nodes: nodeSelector

By default the scheduler treats all feasible nodes as candidates. Often you want more control — “run this Pod only on nodes with SSDs” or “keep this workload on GPU nodes.” The simplest tool is nodeSelector, a hard match on node labels.

First, label a node:

kubectl label nodes node-1 disktype=ssd

Then constrain the Pod:

spec:
  nodeSelector:
    disktype: ssd

Now the Pod schedules only on nodes carrying the label disktype=ssd. If no such node exists, the Pod stays Pending. nodeSelector is an all-or-nothing exact match — simple, but not expressive. When you need “prefer, but don’t require,” or more complex logic, you reach for affinity.

Node Affinity and Anti-Affinity

Node affinity is the richer successor to nodeSelector. It supports operators (In, NotIn, Exists) and, crucially, two strengths:

  • requiredDuringSchedulingIgnoredDuringExecution — a hard rule. Like nodeSelector: if it can’t be met, the Pod won’t schedule.
  • preferredDuringSchedulingIgnoredDuringExecution — a soft rule. The scheduler tries to honor it, but if no matching node is available it places the Pod elsewhere rather than leaving it Pending.
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values: ["ssd"]
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 1
          preference:
            matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: ["us-east-1a"]

The long field names look intimidating, but the KCNA only expects you to decode them: the Ignored­DuringExecution half means the rule is evaluated at scheduling time, not re-checked later — so if a node’s labels change after a Pod is running, the Pod is not evicted. Required = hard, Preferred = soft. That contrast is the whole exam point.

Pod Affinity and Pod Anti-Affinity

Where node affinity attracts Pods to nodes, Pod affinity/anti-affinity places Pods relative to other Pods:

  • Pod affinity: “Schedule me near Pods that match this label” — for example, co-locating a web front end with its cache to reduce latency.
  • Pod anti-affinity: “Keep me away from Pods that match this label” — for example, spreading replicas of the same app across different nodes for high availability.

Anti-affinity for high availability is the most common conceptual scenario: you don’t want all three replicas of a service on one node, because losing that node takes the whole service down. Pod anti-affinity (or the newer topology-spread constraints) achieves that spread.

Taints and Tolerations: Repelling Pods

Affinity and nodeSelector are Pod-side controls — the Pod chooses nodes. Taints and tolerations are the mirror image: a node-side control that lets a node repel Pods that don’t explicitly opt in.

A taint is applied to a node and says “don’t schedule Pods here unless they tolerate this”:

kubectl taint nodes node-1 gpu=true:NoSchedule

A toleration on a Pod says “I accept that taint”:

spec:
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"

The taint has three possible effects, and knowing the difference is a classic exam question:

EffectBehavior
NoScheduleNew Pods without a matching toleration are not scheduled onto the node. Already-running Pods stay.
PreferNoScheduleThe scheduler tries to avoid the node, but will use it if necessary (soft version).
NoExecuteNew Pods without a toleration aren’t scheduled and existing Pods without a toleration are evicted.

The single most important mental model: a toleration does not force a Pod onto a tainted node — it only permits it. Tolerating a taint removes the repulsion; it doesn’t add attraction. To require placement on GPU nodes you’d combine a toleration (to be allowed on the tainted GPU node) with node affinity or a nodeSelector (to be attracted to it).

This is also why control-plane nodes normally don’t run your workloads: they carry a node-role.kubernetes.io/control-plane:NoSchedule taint, so ordinary Pods are repelled.

DaemonSets: Scheduling on Every Node

One workload type interacts with scheduling in a special way. A DaemonSet ensures a copy of a Pod runs on every node (or every node matching a selector) — think log collectors, monitoring agents, or CNI networking plugins. As nodes join the cluster, the DaemonSet controller automatically adds its Pod to them.

Because system agents often need to run everywhere, DaemonSet Pods are typically configured with tolerations broad enough to land even on tainted nodes. For the KCNA, the key fact is the shape of the workload: Deployment = a desired number of replicas placed by the scheduler wherever they fit; DaemonSet = exactly one Pod per (matching) node. When a question asks “which object runs an agent on every node,” the answer is DaemonSet.

Putting It Together: A Troubleshooting Lens

Most KCNA scheduling questions are really troubleshooting questions in disguise. Here’s the reasoning chain to apply when a Pod is Pending:

kubectl get pod app -o wide          # STATUS: Pending, NODE: <none>
kubectl describe pod app             # read the Events section

Then match the reason to a cause:

Symptom in EventsLikely cause
Insufficient cpu / Insufficient memoryRequests exceed allocatable capacity on all nodes.
node(s) didn't match node selectornodeSelector / node affinity has no matching node.
node(s) had taint {…} that the pod didn't tolerateMissing toleration for a node taint.
OOMKilled (in a running Pod’s restarts)Memory limit too low.
Pod slow but stableCPU limit throttling.

If you can walk that table from memory, you’ll answer the majority of scheduling questions correctly.

Common KCNA Traps to Watch For

  • Requests schedule, limits enforce. The scheduler cares about requests; the kubelet enforces limits. Don’t swap them.
  • CPU throttles, memory kills. Over-limit CPU is throttled; over-limit memory is OOMKilled.
  • BestEffort evicted first. Under memory pressure, Pods with no requests/limits go first; Guaranteed Pods (requests == limits) go last.
  • A toleration permits, it doesn’t attract. Combine with affinity/nodeSelector to force placement.
  • NoExecute evicts running Pods; NoSchedule only blocks new ones.
  • Pending means the scheduler found no feasible node — it will not create capacity on its own.
  • Ignored­DuringExecution — affinity is checked at schedule time, not re-evaluated for already-running Pods.

Conclusion

Scheduling and resource management is where Kubernetes’ declarative promise gets real: you describe what a Pod needs, and the scheduler reconciles that against the capacity the cluster actually has. For the KCNA you don’t need to tune the scheduler — you need to predict its behavior. Know that requests drive placement and QoS while limits enforce ceilings; that QoS class decides eviction order; and that nodeSelector, affinity, taints, and tolerations are the four levers that steer Pods toward or away from nodes.

Read through this once to build the model, then drill it. The KCNA rewards fast recognition — 60 questions in 90 minutes leaves no time to reason from scratch. Warm up with our free KCNA practice questions, and when you want full-length, timed practice across every domain, the KCNA Certification Ready Mock Exam Bundle gives you five mock exams with detailed explanations so you walk in knowing exactly where your gaps are.

To round out the Fundamentals domain, pair this with the Kubernetes Architecture guide and the Object Model & kubectl guide. And if you’re planning to continue toward the hands-on exams, the Kubernetes Certification Path Guide 2026 maps the journey — the scheduling concepts here become hands-on tasks in the CKA Workloads & Scheduling domain.

Frequently Asked Questions

What’s the difference between a request and a limit in Kubernetes?

A request is the amount of CPU or memory a container is guaranteed; the scheduler reserves it and uses it to decide which node the Pod fits on. A limit is the maximum the container may consume; the kubelet enforces it at runtime. Requests affect scheduling; limits affect runtime enforcement.

How does Kubernetes decide which Pod to evict under memory pressure?

By QoS class. BestEffort Pods (no requests/limits) are evicted first, then Burstable Pods, and Guaranteed Pods (requests equal to limits for both CPU and memory) are evicted last. Setting equal requests and limits is how you make a workload most resistant to eviction.

Why is my Pod stuck in Pending?

The scheduler found no feasible node. Run kubectl describe pod <name> and read the Events. Common reasons: requests exceed available capacity (Insufficient cpu/memory), no node matches the nodeSelector/affinity, or every candidate node has a taint the Pod doesn’t tolerate. The scheduler won’t add capacity — that’s the Cluster Autoscaler’s job.

What is the difference between nodeSelector and node affinity?

nodeSelector is a simple, exact label match and is always a hard requirement. Node affinity does the same job with richer operators (In, NotIn, Exists) and supports both required (hard) and preferred (soft) rules, so you can express “prefer this node type but fall back if unavailable.”

Do tolerations force a Pod onto a specific node?

No. A toleration only permits a Pod to be scheduled onto a node that carries a matching taint — it removes the repulsion. It does not attract the Pod there. To require placement on specific nodes you combine a toleration with node affinity or a nodeSelector.

What happens when a container exceeds its CPU limit versus its memory limit?

Exceeding a CPU limit causes the container to be throttled (slowed) because CPU is compressible. Exceeding a memory limit causes the container to be OOMKilled (terminated) and restarted, because memory can’t be reclaimed by slowing the process down.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now