Fixing Kubernetes Error: 0/3 nodes are available: pod topology spread constraints

intermediate☸️ Kubernetes2026-07-23| Kubernetes 1.19+ clusters (EKS, GKE, AKS, or self-managed) using TopologySpreadConstraints in Deployment or Pod specs.

Error Message

0/3 nodes are available: 3 node(s) didn't match pod topology spread constraints.
#kubernetes#k8s-troubleshooting#devops#scheduling

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: DoNotSchedule to whenUnsatisfiable: ScheduleAnyway. This tells the scheduler to try its best but prioritize getting the pod running.
  • Loosen the math: Increase your maxSkew. If it is currently 1, try 2. 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 (like topology.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 labelSelector is too broad. If two different deployments share the app: web label, 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.

Related Error Notes