The Quick Fix
If your Pods are stuck in Pending because of topology constraints, you usually have two ways to get them running immediately:
- Switch to soft constraints: Change
whenUnsatisfiable: DoNotScheduletowhenUnsatisfiable: ScheduleAnyway. This tells the scheduler to try its best but prioritize getting the pod running. - Loosen the math: Increase your
maxSkew. If it is currently1, try2. This allows for a more uneven distribution across your nodes or zones.
# Update your deployment YAML:
whenUnsatisfiable: ScheduleAnyway # Changed from DoNotSchedule
Why This Is Happening
Kubernetes is failing to schedule your pod because the rules you set for high availability are too strict for your current cluster state. Think of it as a mathematical puzzle the scheduler can't solve. Common triggers include:
- The "Perfect Balance" Trap: You set
maxSkew: 1, but your node count is uneven. For example, trying to spread 5 pods across 2 nodes with a skew of 1 is mathematically impossible. - Missing Labels: Your
topologyKey(liketopology.kubernetes.io/zone) doesn't exist on your nodes. If the scheduler can't find the label, it assumes the node is ineligible. - Resource Fragmentation: You might have enough nodes, but the specific node that should take the pod to satisfy the spread is already full or has a taint.
- Label Overlap: Your
labelSelectoris too broad. If two different deployments share theapp: weblabel, Kubernetes counts all those pods together when calculating the spread.
Step-by-Step Solutions
1. Audit Your Node Labels
The scheduler is blind without proper labels. If you are spreading pods across zones, verify that your nodes actually report which zone they are in.
kubectl get nodes --show-labels
Check for the key defined in your YAML. If you used topology.kubernetes.io/zone but your nodes only have kubernetes.io/hostname, the constraint will fail every time. Match your YAML to your actual node labels.
2. Transition to Soft Constraints
DoNotSchedule is a hard requirement. It’s great for production environments with massive scale, but it can break smaller clusters during maintenance or scaling events. Changing this to ScheduleAnyway allows the pod to land on a node even if it violates the ideal spread.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: my-app
3. Recalculate Your maxSkew
The maxSkew value defines the maximum allowed difference in pod counts between any two topology domains. In a 3-node cluster, a maxSkew: 1 works perfectly for 3, 4, or 5 pods. However, if one node goes down, that same maxSkew: 1 might prevent the 4th pod from scheduling on the remaining two nodes. Increasing the value to 2 provides more breathing room during failures.
4. Isolate Your Label Selectors
Ensure the labelSelector in your constraint is surgical. If it's too generic, the scheduler will include unrelated pods in its calculations. Use a specific version or component label to ensure the math only applies to the pods in your specific deployment.
# Use a unique label to avoid counting other apps
labelSelector:
matchLabels:
app: billing-api
tier: backend
How to Verify the Fix
After applying your changes, you need to confirm the pods are moving and spreading correctly.
Watch Pod Status
kubectl get pods -w
Your pods should transition from Pending to Running. If they stay Pending, run kubectl describe pod <pod-name> to see if a new error, like Insufficient CPU, has appeared.
Analyze the Distribution
Run this command to see exactly how many pods landed on each node:
kubectl get pods -l app=my-app -o custom-columns=NODE:.spec.nodeName | sort | uniq -c
If you see an output like 3 node-a and 2 node-b, your skew is 1. This confirms the scheduler is working within your new parameters.

