FailedScheduling means no node passed the scheduler's predicates — the pod never started, so read pod Events, not container logs
Run kubectl describe pod first: the Events section names the exact failing predicate (cpu, taint, affinity) per node
Insufficient cpu usually means bloated requests, not a full cluster — compare Allocated versus Allocatable before adding nodes
Taints repel pods without matching tolerations — audit node taints after every upgrade, cordon, and incident
Unbound PVCs and spread constraints can pin pods down — align storage zones and do the replica math on maxSkew; with maxSkew 1 across 3 zones and 2 replicas, 1/1/0 has skew 1 and is valid, and scaling down never pends pods, but a 3rd replica pends when the only zones keeping skew within maxSkew have no eligible node (e.g. zone C full, tainted or excluded by affinity, forcing A or B to skew 2) — free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew
✦ Definition~90s read
What is Kubernetes FailedScheduling Pods Pending Fix?
FailedScheduling isn't a crash or a bug — it's the Kubernetes scheduler declining to assign your pod to any node. Every few seconds the scheduler evaluates each pending pod against every node in two phases: predicates that veto (not enough CPU, missing toleration, failed affinity, unusable volume) and priorities that rank the survivors.
★
Picture a hotel front desk assigning guests to rooms.
When every node fails at least one predicate, the pod stays Pending and the scheduler emits a FailedScheduling warning summarizing the per-node vetoes. No kubelet is involved yet, no container runtime, no image pull — the pod exists only as an API object waiting for an assignment.
That pre-node position shapes the whole debugging approach. Container logs can't exist because no container started. Restarts don't happen because there's nothing to restart. The only evidence is the scheduler's event stream on the pod object, plus cluster state: node allocatable versus allocated, taints, labels, PVC bindings, and spread math.
Engineers arriving from crash-debugging instincts waste the first half hour in the wrong place — logs, exec shells, image tags — while the answer sits in one describe command.
In production, FailedScheduling is the gatekeeper event for launches, migrations, and upgrades: any change to requests, taints, labels, storage classes, or replica counts can flip scheduling from feasible to impossible. Treat the scheduler's verdict as a contract — predicates are exact, skew math is literal, zones are physical — and most Pending pods resolve to one-line fixes once you read the majority veto instead of fighting it.
Note that topology spread only pends a pod (with whenUnsatisfiable: DoNotSchedule) when the only zones that would keep skew within maxSkew have no eligible node — for example, 3 zones with 1/1/0 already placed and zone C's nodes full, tainted, or excluded by affinity, so the 3rd replica would have to go to A or B, making skew 2 > 1; zones still count as domains even when their nodes are unusable (unless nodeAffinityPolicy/nodeTaintsPolicy say otherwise, Kubernetes 1.26+). Scaling down never makes pods pend, and a placement like 1/1/0 with maxSkew 1 across 3 zones and 2 replicas has skew 1 and is valid.
The fix: free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew.
Plain-English First
Picture a hotel front desk assigning guests to rooms. Each guest has demands: a king bed (CPU), a quiet floor (taint toleration), a room facing the pool (node label), luggage already stored on floor 2 (PVC zone), and a rule that their group must spread evenly across floors (topology spread). FailedScheduling is the clerk explaining why every room was rejected — too small, wrong floor, pool view unavailable. Fix the demand that blocks all rooms and everyone gets a key.
Your deployment looks healthy in Git, the rollout starts, and then... nothing. kubectl get pods shows Pending, and the Events stream fills with FailedScheduling warnings naming predicates like Insufficient cpu, TaintToleration, and NodeAffinity. No container ever starts, so there are no logs to read and no crash to debug. The scheduler tried every node, rejected all of them, and told you exactly why — if you know where to look.
FailedScheduling is uniquely frustrating because it's a decision log, not an error. Each event line explains one reason one node was skipped, and the real cause hides among a dozen near-misses. Teams routinely misread it: they add nodes when requests are bloated, they lower requests when a taint is the blocker, or they chase zone labels while an unbound PVC holds the pod hostage.
This guide teaches you to read the scheduler's verdict the way it was meant to be read. You'll start with per-node event reasons, then work through the six real causes in frequency order: resource insufficiency, taints and tolerations, selectors and affinity, unbound PVCs with zone mismatch, and topology spread math. By the end you'll clear most Pending pods with three commands and stop the rest with constraints you've actually done the math on.
Every FailedScheduling investigation starts in exactly one place: the Events section of kubectl describe pod. The scheduler logs a Warning per scheduling cycle naming each predicate that failed, and — critically — it logs per node. You'll see lines like 0/14 nodes available: 9 Insufficient cpu, 5 TaintToleration, and that ratio is the whole diagnosis. The majority reason is your cause; the minority lines are nodes that failed for their own unrelated reasons. Engineers who grep logs cluster-wide miss this because they're reading aggregated noise instead of one pod's verdict.
Learn to separate the scheduler's two decision phases. Predicates are hard filters — a node failing any predicate is out, no scoring involved. Priority scoring only ranks the survivors, so it never appears in FailedScheduling at all. That means every event line is a veto, and vetoes compose: a node can fail taint tolerance and capacity simultaneously while the event counts it once. When multiple predicates appear, fix the one covering the most nodes first, then re-describe — the next cycle's events will reveal whatever was hiding underneath.
Make describe-pod-events your reflex before any YAML edit or scale-up. The command costs seconds and has prevented countless wasted node additions: if 9 of 14 nodes say TaintToleration, no amount of new capacity schedules onto the tainted majority. Read the ratio, fix the majority veto, and let the next scheduling cycle tell you what's left.
scheduler-verdict.shBASH
1
2
3
4
5
6
7
8
9
# The single highest-value command forPending pods
kubectl get pods -A | grep -v Running
kubectl describe pod <pod-name> -n <namespace> | sed -n '/Events:/,$p'
# Watch the verdict change as you fix things
kubectl get events --sort-by=.lastTimestamp -n <namespace> | grep FailedScheduling
# Count rejections per reason across recent cycles
kubectl describe pod <pod-name> -n <namespace> | grep -oE '[0-9]+ [A-Za-z]+' | sort | uniq -c
📊 Production Insight
The Friday team scaled before describing: 14 emergency nodes at $380 per day while events plainly said 9 of 14 nodes failed on TaintToleration. One describe command would have redirected the whole response in the first minute.
🎯 Key Takeaway
Describe the pod, read the Events ratio, fix the majority veto first — the scheduler tells you the cause when you ask the right pod.
Insufficient cpu and Insufficient memory are the most common FailedScheduling lines and the most misread. They don't mean your cluster is busy — they mean the sum of container requests on each node leaves no room for your pod's requests. Requests are reservations deducted from allocatable capacity at schedule time, whether the containers use them or not. A pod requesting 4 CPUs while burning 300m consumes 4 CPUs of scheduling ledger and 300m of silicon, and the scheduler only cares about the ledger.
The gap between reserved and used is where phantom capacity crises live. Teams copy request values from load-test manifests, double them for safety, and never revisit — until routine pods can't schedule on nodes running at 18 percent real utilization. Memory is harsher than CPU: CPU throttles past limits, but memory requests that exceed allocatable block scheduling outright, and memory limits that get breached kill with OOM. Both show up as scheduling vetoes long before any runtime symptom.
Fix it with measurement, not Spares. Compare Allocated against Allocatable in kubectl describe node to see the ledger, then find the hogs with a sorted kubectl top pods. Set requests near measured p95 usage — 800m for a pod burning 700m — and reserve headroom at the node-pool level instead of per pod. Right-sizing 41 launch pods from 4 CPUs to 800m freed the equivalent of 11 nodes without spending a dollar. Add capacity only when the ledger is honest and still full.
capacity-ledger.shBASH
1
2
3
4
5
6
7
8
9
10
# The scheduling ledger: what nodes really have left
kubectl describe node <node> | grep -A 6'Allocated resources'
# Real usage versus reservations, cluster-wide
kubectl top nodes
kubectl top pods -A --sort-by=cpu | head -15
kubectl top pods -A --sort-by=memory | head -15
# Find the most over-provisioned requests
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.name}{" cpu_req="}{.spec.containers[0].resources.requests.cpu}{"\n"}{end}' | head -20
📊 Production Insight
Launch pods requested 4 CPUs while burning 300m — a 13x over-reservation that faked a capacity crisis at 18 percent real utilization. Cutting to 800m freed 11 nodes' worth of phantom reservations in one edit.
🎯 Key Takeaway
Requests are ledger entries, not usage — measure p95, set requests near it, and only add nodes when the honest ledger is full.
Taints are the scheduler's keep-out signs: any node carrying a taint rejects every pod that doesn't declare a matching toleration. They appear after cordons, GPU and spot-node provisioning, and upgrades — and they linger. A NoSchedule taint applied during Thursday's upgrade survives on any node whose cleanup job failed, quietly shrinking your schedulable fleet while dashboards show all nodes Ready. Ready means the kubelet is healthy; it says nothing about whether the scheduler is allowed to use the node.
Matching is exact and unforgiving. A toleration must agree on key, value, and effect — effect NoSchedule doesn't match NoExecute, and a missing value doesn't match a valued taint. Teams stare at a toleration that looks right while one field differs, then conclude taints aren't the problem. The describe output shows both sides if you look: node Taints at the top of kubectl describe node, pod Tolerations in the pod spec. Diff them character by character before theorizing further.
Treat taints as incident debris to be cleaned, not as permanent architecture. After every upgrade, cordon, and GPU job, audit taints across the fleet and remove the ones whose reason expired. Bake the audit into runbooks: the nodes that reboot mid-cleanup are exactly the ones that keep their taints. And when a taint is legitimate — spot nodes, dedicated hardware — put the toleration in the deployment template once, not as tribal knowledge engineers re-derive each outage.
Nine nodes kept a NoSchedule upgrade taint because the cleanup job died on hosts that rebooted mid-run. Removing them scheduled 34 of 41 pending pods within 90 seconds — no new nodes needed.
🎯 Key Takeaway
Taints persist past their reason — audit them after every operational event and match tolerations on key, value, and effect exactly.
Node selectors and affinity rules tell the scheduler which nodes are acceptable, and a required rule that matches nothing vetoes the entire fleet. The failure modes are mundane: a label typo like zone-a versus zone_a, a label that existed on the old node pool but not the new one, or an In operator listing values nobody applied. The pod spec looks deliberate, the intent was reasonable six months ago, and today zero nodes qualify — so the pod pends with a perfectly logical explanation nobody re-examined.
Required versus preferred is the whole game. requiredDuringSchedulingIgnoredDuringExecution is a veto: miss it and the pod never schedules, full stop. preferredDuringSchedulingIgnoredDuringExecution is a wish: the scheduler tries, fails soft, and places the pod anyway. Teams reach for required when they want tidiness — keep the API near the cache — and buy fragility: one relabeled node pool and the tier can't schedule anywhere. Affinity also compounds across terms: nodeAffinity plus podAntiAffinity plus spread constraints must all pass on the same node, and each added term multiplies the ways to get zero.
Defend with verification before constraint. List real labels with kubectl get nodes --show-labels and write selectors against what exists, not what you remember. Default to preferred for placement wishes and reserve required for genuine hard needs like GPU models or compliance zones. When a pod pends on affinity, comment out terms one at a time and re-describe — the cycle that flips to Scheduled names the guilty term faster than any amount of YAML staring.
soft-affinity.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Affinity that degrades instead of blocking
# Preferthis shape for placement wishes:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: workload-type
operator: In
values: [compute-optimized]
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: [amd64]
⚠ Required affinity on cloud labels is fragile
Never use required affinity on cloud zone or instance-type labels you don't control. Cloud providers rename zones and rotate instance families without asking — a required rule on either is a future outage waiting for a routine node rotation.
📊 Production Insight
A team renamed its node pool labels during a migration and left required affinity pointing at the old key — 100 percent of the tier pended for 40 minutes. Preferred affinity would have scheduled everything with a warning instead of an outage.
🎯 Key Takeaway
Verify labels exist before constraining, prefer preferred over required, and bisect affinity terms one at a time when pods pend.
Storage can veto scheduling before a single container consideration matters. When a pod claims a PersistentVolumeClaim with immediate binding, the scheduler must place the pod on a node that can access the bound volume — and cloud volumes live in exactly one zone. A PV provisioned in zone A pins its pod to zone A. If the pod's affinity, spread constraints, or available capacity forbid zone A, no node on earth satisfies both, and the pod pends with an event about unbound or unsuitable volumes that reads like gibberish until you know the topology.
Zone mismatch is the signature failure. It creeps in when the StorageClass allows provisioning anywhere but the pod is constrained somewhere: allowedTopologies spanning three zones while node affinity permits two, or a manually created PV in the old zone after a migration moved workloads. Immediate binding makes it worse by binding the PVC at creation time — possibly in a zone the pod can't use — before the scheduler ever sees the pod. The PVC looks fine (Bound!) while the pod pends forever, and each half looks innocent in isolation.
The structural fix is delayed binding. StorageClasses with volumeBindingMode WaitForFirstConsumer postpone provisioning until the scheduler picks a node, so the volume is created in a zone the pod can actually reach. Pair that with allowedTopologies matching your pod affinity zones, and the two decisions stop fighting. For existing mismatches, compare the PV's nodeAffinity against the pod's constraints, then either move the workload's zone or reprovision the volume — don't edit either blindly, because the binding is a contract both sides signed.
pvc-zone-diagnosis.shBASH
1
2
3
4
5
6
7
8
9
# Which side of the binding is stuck?
kubectl get pvc -A | grep -v Bound
kubectl get pv -o yaml | grep -B 2 -A 6 nodeAffinity
# StorageClass binding mode decides who moves first
kubectl get storageclass -o yaml | grep -E 'name:|volumeBindingMode|allowedTopologies'
# Pod's zone demands for comparison
kubectl get pod <pod> -o yaml | grep -A 8'nodeAffinity\|topologyKey'
📊 Production Insight
A migrated workload kept its old-zone PVs while affinity moved to new zones: PVCs showed Bound, pods showed Pending, and each dashboard blamed the other for 3 hours. Reprovisioning volumes in the workload's zones cleared all 12 pods at once.
🎯 Key Takeaway
Volumes live in one zone — delay binding with WaitForFirstConsumer and keep StorageClass topology aligned with pod affinity.
Topology spread constraints are the most sophisticated way to pend a pod with good intentions. They demand even distribution — no zone or host may exceed the others by more than maxSkew — and the scheduler enforces the math literally. The math alone rarely blocks: with maxSkew 1 across 3 zones, 2 replicas land 1/1/0 (skew 1, valid). The trap is capacity in the wrong place.
whenUnsatisfiable: DoNotSchedule turns the rule into a veto when the only zones that would keep skew within maxSkew have no eligible node — e.g. 1/1/0 already placed and zone C's nodes full, tainted or excluded by affinity, so the 3rd replica would have to go to A or B, making skew 2 > 1. Zones still count as domains even when their nodes are unusable (unless nodeAffinityPolicy/nodeTaintsPolicy say otherwise, Kubernetes 1.26+). ScheduleAnyway would place the pod with a warning. Teams also stack spread against anti-affinity against zone affinity until the feasible set is empty, then debug each constraint in isolation while the combination is the culprit.
Do the arithmetic before applying the constraint, not after the page. Replicas must be at least zones times (minDomainCount behavior aside) — roughly, you need enough pods to fill each domain within skew. For 3 zones at skew 1, run 3+ replicas or accept imbalance with ScheduleAnyway. And when a spread-constrained pod pends, test by temporarily switching to ScheduleAnyway: if it schedules instantly, the constraint math — not capacity, taints, or storage — is your answer. Fix: free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew.
📊 Production Insight
A launch scaled from 2 to 3 replicas while zone C's only node pool was cordoned for an upgrade. Replicas sat at 1/1/0, the third could only go to A or B (skew 2 > 1), and it stayed Pending until the pool came back. The runbook now checks every zone has schedulable capacity before scaling spread-constrained workloads.
🎯 Key Takeaway
Spread pends a pod only when the zone that would keep skew within maxSkew has no eligible node. Check the empty zone's capacity, taints and affinity before blaming the constraint.
● Production incidentPOST-MORTEMseverity: high
Stale Upgrade Taints Plus Bloated Requests Pended 41 Pods
Symptom
At 2:15 PM Friday, a launch deployment left 41 pods Pending across 3 services. Events showed TaintToleration rejections on 9 nodes and Insufficient cpu on the other 5. An emergency scale-up added 14 nodes by 2:40 PM at roughly $380 per day in extra cost, but pending count barely moved — new nodes took 6 minutes to join while the tainted majority still repelled everything. Full recovery took 52 minutes.
Assumption
The team assumed a traffic spike had exhausted the cluster because Pending pods coincided with a marketing launch. They approved an emergency node-pool doubling and blamed the autoscaler when new nodes didn't help. Nobody read the event lines saying TaintToleration, because Insufficient cpu appeared on two older nodes and confirmed the story they already believed.
Root cause
Two stacked faults. A Thursday node-image upgrade left a NoSchedule taint on 9 of 14 nodes — the cleanup job failed silently on nodes that rebooted mid-run. Separately, the launch deployment carried CPU requests of 4 cores copied from a load-test manifest, while the pods actually burned 300m at p95. The scheduler correctly rejected the 9 tainted nodes on toleration and the remaining 5 on capacity, since phantom requests consumed 3.4x real usage. The emergency 14-node scale-up added capacity the scheduler still couldn't use on tainted hosts.
Fix
First, the stale NoSchedule taint was removed from all 9 upgraded nodes with kubectl taint, which let 34 of 41 pending pods schedule within 90 seconds. Second, the 4-CPU requests were cut to measured p95 of 800m, freeing the equivalent of 11 nodes of phantom reservations. Third, the upgrade runbook gained a post-upgrade taint audit step, and CI now rejects request values more than 3x the last measured p95 without an explicit override annotation.
Key lesson
Read the majority predicate in pod Events before spending money — 41 pending pods needed zero new nodes, just 9 taint removals.
Requests are reservations, not usage: 4-CPU requests on pods burning 300m faked a capacity crisis while real utilization sat at 18 percent.
Upgrade runbooks must include taint cleanup, because nodes that complete upgrades don't always shed the repellents they carried through them.
Production debug guideFive checks that read the scheduler's verdict directly — run them before adding nodes or editing YAML.5 entries
Symptom · 01
Pod is Pending and you don't know which of six causes applies
→
Fix
Run kubectl describe pod <name> and read only the Events section at the bottom. Each Warning names the failed predicate per node — Insufficient cpu, TaintToleration, NodeAffinity. Count which reason covers most nodes; the majority reason is your cause and the minority lines are noise.
Symptom · 02
Events show Insufficient cpu or Insufficient memory on all nodes
→
Fix
Compare real capacity with kubectl top nodes and kubectl describe node <node> | grep -A 5 Allocated. If Allocated requests exceed 85 percent of Allocatable on every node, requests are the blocker. Find the hogs with kubectl top pods -A --sort-by=cpu and lower bloated requests to measured p95.
Symptom · 03
A node has free capacity but the scheduler skips it
→
Fix
List repellents with kubectl describe node <node> | grep -B 1 -A 3 Taints and compare against your pod's tolerations in kubectl get pod <name> -o yaml. A NoSchedule taint with no matching toleration explains an empty node instantly. Either add the toleration or remove the taint with kubectl taint nodes <node> <key>-.
Symptom · 04
Events cite NodeAffinity or NodeSelector mismatch on every node
→
Fix
Verify labels exist with kubectl get nodes --show-labels | grep -E 'your-key|zone' and diff against your pod's nodeSelector or affinity terms. A label typo or a value no node carries means zero candidates. Fix the selector or soften requiredDuringScheduling to preferredDuringScheduling.
Check storage with kubectl get pvc and kubectl get pv -o yaml | grep -A 6 nodeAffinity. If the PVC is Pending or the PV's zone differs from your pod's allowed zones, binding is the blocker. Align StorageClass allowedTopologies with pod affinity, or switch the class to volumeBindingMode: WaitForFirstConsumer.
FailedScheduling Causes at a Glance
Root Cause
How to Confirm
Fix
Prevention
CPU or memory requests exceed free capacity
Events show Insufficient cpu/memory on all nodes
Lower requests or add right-sized nodes
Set requests from measured p95, not guesses
Taint repels pods with no toleration
Events show TaintToleration predicate failing
Add matching toleration or remove stale taint
Audit taints after every incident and upgrade
Selector or affinity matches zero nodes
Events show NodeAffinity/NodeSelector mismatch
Fix labels or soften to preferred affinity
Verify labels exist before constraining
PVC unbound or in the wrong zone
Events show unbound immediate PVCs
Align StorageClass zones with pod affinity
Use WaitForFirstConsumer binding mode
Topology spread can't be satisfied
Events cite TopologySpreadConstraint
Adding replicas can cause pods to pend: with maxSkew 1 across 3 zones and 2 replicas, the placement 1/1/0 has skew 1 and is valid, but a 3rd replica pends if zone C's nodes are full, tainted or excluded by affinity, since it would have to go to A or B, making skew 2 > 1. Zones still count as domains even when their nodes are unusable (unless nodeAffinityPolicy/nodeTaintsPolicy say otherwise, Kubernetes 1.26+). Fix: free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew.
Do the skew math before applying constraints. With maxSkew 1 across 3 zones and 2 replicas, the placement 1/1/0 has skew 1 and is valid; nothing pends. Scaling down never makes pods pend. Topology spread pends a pod (whenUnsatisfiable: DoNotSchedule) when the only zones that would keep skew within maxSkew have no eligible node: e.g. 3 zones with 1/1/0 already placed and zone C's nodes full, tainted or excluded by affinity, so the 3rd replica would have to go to A or B, making skew 2 > 1. Zones still count as domains even when their nodes are unusable (unless nodeAffinityPolicy/nodeTaintsPolicy say otherwise, Kubernetes 1.26+). Fix: free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew.
⚙ Quick Reference
5 commands from this guide
File
Command / Code
Purpose
scheduler-verdict.sh
kubectl get pods -A | grep -v Running
bash configuration
capacity-ledger.sh
kubectl describe node <node> | grep -A 6 'Allocated resources'
bash configuration
pod-tolerations.yaml
tolerations:
yaml configuration
soft-affinity.yaml
affinity:
yaml configuration
pvc-zone-diagnosis.sh
kubectl get pvc -A | grep -v Bound
bash configuration
Key takeaways
1
FailedScheduling is a decision log
kubectl describe pod Events name the exact predicate that rejected each node.
2
Requests reserve capacity and drive scheduling; limits don't
bloated requests are the top cause of Insufficient cpu.
3
Taints repel pods without matching tolerations; audit them after every incident, upgrade, and cordon.
4
Required affinity on missing labels blocks everything
verify labels exist and prefer soft constraints.
5
PVCs pin pods to their zone
align StorageClass topology with pod affinity or use WaitForFirstConsumer.
6
Spread constraints need replica math
maxSkew 1 across 3 zones does not require 3 replicas: with 2 replicas, 1/1/0 has skew 1 and is valid.
Common mistakes to avoid
6 patterns
×
Scaling the cluster before reading scheduler events
Symptom
New nodes join, pods still pend, and the bill grows while a nodeSelector nobody remembers keeps excluding every node.
Fix
Read the Events section of kubectl describe pod first — it names the exact failing predicate per node. Only then decide whether you need more nodes, fewer requests, or different constraints.
×
Copy-pasting requests equal to limits from a tutorial
Symptom
Pods request 4 CPUs they never use, nodes report Insufficient cpu, and utilization sits at 12 percent while work queues up.
Fix
Set requests to measured p95 usage and keep limits only where you need burst caps. Audit with kubectl top pods and adjust quarterly.
×
Cordoning a node and forgetting the taint it leaves
Symptom
A node sits half-empty for weeks because NoSchedule still repels every pod after the incident that caused it ended.
Fix
Add the matching toleration with the exact key, value, and effect, or remove the taint if the node is healthy again. Verify with kubectl describe node | grep -A 3 Taints.
×
Using required node affinity on labels that don't exist
Symptom
Zero nodes match, the pod pends forever, and the typo (zone-a vs zone_a) hides in plain sight in the YAML.
Fix
Check node labels with kubectl get nodes --show-labels before writing selectors. Prefer preferredDuringScheduling affinity so a label typo degrades placement instead of blocking it.
×
Binding PVCs in one zone while constraining pods to another
Symptom
Pod stays Pending with unbound volumes while the PVC sits Available in a zone the pod is forbidden to enter.
Fix
Align the StorageClass volumeBindingMode, allowedTopologies, and the pod's node affinity to the same zones. Use WaitForFirstConsumer so binding waits for the scheduling decision.
×
Adding topology spread constraints without checking capacity in every zone
Symptom
One replica pends forever while other zones have free nodes — the empty zone is full, tainted or excluded by affinity, so any placement would break maxSkew.
Fix
Make sure every counted zone has schedulable capacity, or relax with whenUnsatisfiable: ScheduleAnyway or a larger maxSkew.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What's the difference between requests and limits for scheduling?
Q02SENIOR
A node has free capacity but pods won't land on it. How do taints explai...
Q03SENIOR
How does a zone-mismatched PVC cause FailedScheduling?
Q04SENIOR
Your spread constraint leaves one replica Pending. Walk me through the m...
Q05JUNIOR
Which three commands diagnose most FailedScheduling cases?
Q01 of 05JUNIOR
What's the difference between requests and limits for scheduling?
ANSWER
Requests reserve capacity and drive scheduling; limits cap burst at runtime. A pod with high requests and low usage blocks the scheduler while the node sits idle. I'd show kubectl describe node allocatable versus requested to prove the gap.
Q02 of 05SENIOR
A node has free capacity but pods won't land on it. How do taints explain that?
ANSWER
Taints on nodes repel pods; tolerations on pods opt into them. I'd run kubectl describe node for taints and match key, value, and effect against the pod's tolerations. Stale NoSchedule taints after incidents are the classic culprit.
Q03 of 05SENIOR
How does a zone-mismatched PVC cause FailedScheduling?
ANSWER
The PVC binds in one zone while node affinity constrains the pod to another. I'd check the PV's nodeAffinity, the StorageClass allowedTopologies, and the pod's affinity together — then align them or switch to WaitForFirstConsumer.
Q04 of 05SENIOR
Your spread constraint leaves one replica Pending. Walk me through the math.
ANSWER
Skew is the max minus min pod count across eligible domains. With maxSkew 1 and replicas at 1/1/0, the next pod must go to the empty zone; if that zone's nodes are full, tainted or excluded by affinity, placing it in A or B would make skew 2, so with DoNotSchedule it pends. I'd add capacity in that zone, switch to ScheduleAnyway, or raise maxSkew.
Q05 of 05JUNIOR
Which three commands diagnose most FailedScheduling cases?
ANSWER
kubectl describe pod for the event lines, kubectl top nodes plus describe node for real allocatable capacity, and kubectl get pvc for unbound volumes. Those three cover resources, constraints, and storage in under a minute.
01
What's the difference between requests and limits for scheduling?
JUNIOR
02
A node has free capacity but pods won't land on it. How do taints explain that?
SENIOR
03
How does a zone-mismatched PVC cause FailedScheduling?
SENIOR
04
Your spread constraint leaves one replica Pending. Walk me through the math.
SENIOR
05
Which three commands diagnose most FailedScheduling cases?
JUNIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
Is FailedScheduling the same as ImagePullBackOff?
No. FailedScheduling means the pod never got assigned to a node, so no container ever started. Image pulls, crashes, and readiness probes all happen after scheduling. Check pod Events, not container logs.
Was this helpful?
02
What's the single fastest way to see why a pod won't schedule?
Describe the pod and read Events: kubectl describe pod <name> shows per-node predicate failures like Insufficient cpu or TaintToleration. Those lines name the exact blocker.
Was this helpful?
03
How do I fix Insufficient cpu without adding nodes?
Lower the requests to measured usage, free capacity by evicting or rescheduling wasteful workloads, or add nodes. Never raise limits to fix scheduling — limits don't reserve capacity, requests do.
Was this helpful?
04
Will a pod schedule onto a tainted node automatically?
No. Taints repel pods unless the pod declares a matching toleration. Rolling updates, cluster autoscaler nodes, and GPU nodes commonly carry taints. Add the toleration to the pod spec or remove the taint from the node.
Was this helpful?
05
Can a PVC in another zone block scheduling?
Yes. A PVC bound in zone A pins its pod to zone A. If node affinity restricts the pod to zone B, nothing can satisfy both and the pod pends forever. Keep storage topology and pod affinity in the same zones.
Was this helpful?
06
What does a TopologySpreadConstraint event actually mean?
It means your spread constraint demands a distribution your replica count can't produce — for example maxSkew 1 across 3 zones with 2 replicas, where 1/1/0 has skew 1 and is valid, so nothing pends. Topology spread pends a pod (whenUnsatisfiable: DoNotSchedule) when the only zones that would keep skew within maxSkew have no eligible node — e.g. 3 zones with 1/1/0 already placed and zone C's nodes full, tainted or excluded by affinity, so the 3rd replica would have to go to A or B, making skew 2 > 1. Zones still count as domains even when their nodes are unusable (unless nodeAffinityPolicy/nodeTaintsPolicy say otherwise, Kubernetes 1.26+). Fix: free or add capacity in the empty zone, use ScheduleAnyway, or raise maxSkew.