Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Topology Spread Constraints in Kubernetes

By Kokil Thapa | Last reviewed: September 2026

Topology Spread Constraints in Kubernetes solve a problem every production cluster eventually hits: replicas pile onto one node or one availability zone until a single failure takes down half your service. Pod anti-affinity can spread workloads, but the rules get brittle fast. Spread constraints give the Kubernetes scheduler a cleaner way to balance pods across failure domains. If you run stateless APIs, queue workers, or microservices behind an ingress, this feature belongs in your pod spec from day one.

What Are Topology Spread Constraints in Kubernetes?

Topology Spread Constraints in Kubernetes are scheduling rules defined in a pod or workload template. They describe how far apart matching pods should sit across topology domains. A domain is any node label the scheduler understands, such as topology.kubernetes.io/zone or kubernetes.io/hostname.

Each constraint references a topologyKey, a maxSkew value, and a whenUnsatisfiable action. The scheduler counts matching pods per domain and picks a node that keeps the skew within your limit. This is simpler than chaining pod anti-affinity expressions for every replica set you deploy.

On client projects where I deploy Laravel APIs on Kubernetes, spread constraints sit alongside resource requests and limits. Requests reserve capacity. Spread rules distribute risk. Both matter when a Kathmandu-hosted cluster shares nodes with other production workloads.

Pod Spread Across ZonesZone APod 1Pod 2Zone BPod 3Pod 4Zone CPod 5Pod 6maxSkew=1 limits count gapbetween any two zones
Topology Spread Constraints in Kubernetes balance replica counts across zone domains with maxSkew limits.

Core fields you must understand

  • topologyKey — the node label that defines each domain, commonly topology.kubernetes.io/zone.
  • maxSkew — the largest allowed difference in pod count between any two domains.
  • whenUnsatisfiable — either DoNotSchedule (hard rule) or ScheduleAnyway (soft preference).
  • labelSelector — which existing pods count toward the skew calculation.
  • matchLabelKeys — optional pod-label keys that scope counting to pods from the same controller revision.

How Does the Kubernetes Scheduler Apply Spread Constraints?

The scheduler evaluates spread constraints after filtering nodes that fail hard requirements like taints and tolerations. For each feasible node, it computes the skew per topology domain. Skew equals the pod count in the candidate domain minus the count in the domain with the fewest matching pods.

If every feasible node would produce skew above maxSkew and whenUnsatisfiable is DoNotSchedule, the pod stays pending. That is correct behaviour. It means your cluster cannot satisfy the rule with current capacity. Soft constraints with ScheduleAnyway still place the pod but may leave an imbalance.

I have seen teams confuse pending pods with a broken deployment when the real issue is an unsatisfiable spread rule on a two-node cluster spanning one zone. Check events first. The scheduler message usually names the constraint that blocked placement.

Scheduler Spread FlowPending Podwith spread rulesFilter Nodestaints, affinityCount Skewper topologyKeyDoNotScheduleskew too highBind Nodeskew within maxSkew = domain count minus lowest countScheduler picks the node that minimises skew
The scheduler counts pods per domain and rejects placements that exceed maxSkew when DoNotSchedule is set.

How Do You Configure Topology Spread Constraints in Kubernetes?

Start with a Deployment that runs at least three replicas across three zones. Without spread rules, the scheduler may place all pods on one node because it optimises for packing, not fault tolerance. Add a constraint block to the pod template spec.

Zone spread with a hard constraint

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 6
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-server
      containers:
        - name: api
          image: myregistry/api:1.4.2
          resources:
            requests:
              cpu: 250m
              memory: 256Mi

This manifest spreads six API pods across zones with at most one pod difference between the fullest and emptiest zone. Pair it with a second constraint on hostname if you also want node-level separation within each zone.

Hostname spread inside each zone

      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-server
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels:
              app: api-server

The zone rule is hard. The hostname rule is soft. During a surge, soft rules let pods schedule even if two replicas temporarily share a node. That trade-off beats leaving pods pending during a traffic spike on a booking platform like Adventure Third Pole Trek during peak season.

Using matchLabelKeys for rolling updates

During a rolling update, old and new pods coexist. Without scoping, skew counts both revisions together and can block new pods. Add matchLabelKeys so each ReplicaSet revision spreads independently:

        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: api-server
          matchLabelKeys:
            - pod-template-hash

The pod-template-hash label is injected automatically on Deployments. This pattern prevents update deadlocks I have hit on production clusters managed through Argo CD GitOps pipelines.

How Do Spread Constraints Compare to Pod Anti-Affinity?

Both tools influence pod placement. They differ in expressiveness and operational cost. Spread constraints count pods per domain. Anti-affinity forbids or prefers specific pairwise relationships. For even distribution across zones, spread constraints usually win.

CriteriaTopology Spread ConstraintsPod Anti-Affinity
Primary goalEven distribution across domainsAvoid or prefer co-location with specific pods
Rule styleDeclarative skew limit per topologyKeyRequired or preferred scheduling terms
Scaling behaviourRebalances naturally as replicas growCan require N² term growth for complex rules
Failure during shortageDoNotSchedule leaves pod pending with clear eventsRequired anti-affinity also blocks; preferred may ignore
Best fitHA replicas across zones or nodesNever co-locate two specific workloads on one node

Use anti-affinity when two particular applications must never share a host. Use spread constraints when you want three of everything in every zone. Many production manifests combine both, plus NetworkPolicies for east-west traffic control.

Before vs After Spread RulesWithout ConstraintsWith maxSkew=1Single NodeNode 1Node 2Node 3One node loss without spread can wipe all replicas
Without spread rules pods stack on one node; Topology Spread Constraints in Kubernetes enforce even distribution.

What Production Gotchas Break Topology Spread Constraints?

Spread constraints assume your nodes expose meaningful topology labels. Managed clusters on AWS, GCP, and DigitalOcean usually set zone labels automatically. Bare-metal clusters built with Kubespray need you to label nodes yourself or every pod lands in one logical domain.

  1. Insufficient domains — six replicas with maxSkew: 1 across two zones forces a skew of two. Either add nodes in a third zone or relax the rule.
  2. Missing topology labels — run kubectl get nodes -L topology.kubernetes.io/zone and confirm values exist before enforcing hard constraints.
  3. DaemonSets inflate counts — system pods on a node do not affect spread unless your labelSelector matches them. Scope selectors tightly to your app label.
  4. HPA scale-up timingHorizontal Pod Autoscaler adds pods fast. Soft hostname spreads prevent pending pods during scale events.
  5. Single-zone dev clusters — use ScheduleAnyway locally with Minikube or Kind. Keep DoNotSchedule for staging that mirrors production topology.

The official Kubernetes documentation on pod topology spread constraints describes minDomains for requiring a minimum number of domains before scheduling. That helps when you need at least two zones represented before accepting placement. Read the reference at kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints before tuning production values.

Spread Constraint DecisionsNeed zone HA?YesNoDoNotSchedulemaxSkew 1 on zoneScheduleAnywayor skip zone ruleAdd hostname rulesoft unless strictVerify labels before hard rules
Choose DoNotSchedule for zone HA and ScheduleAnyway for node-level preferences during capacity pressure.

Debugging pending pods

When a pod stays pending, inspect scheduler output:

kubectl describe pod api-server-7f8b9c-xyz | grep -A5 Events
kubectl get nodes -L topology.kubernetes.io/zone,kubernetes.io/hostname

Cross-check replica count against available domains. Validate your manifest with a JSON formatter if you generate specs from Helm or Kustomize overlays. Typos in topologyKey silently reduce the rule to a no-op because no nodes match the key.

For Laravel workloads moving from VMs to containers, spread constraints complement the patterns in Kubernetes for Laravel getting started. Queue workers benefit from hostname spreads. Web pods benefit from zone spreads behind an ingress controller.

Capacity planning with spread rules

Hard zone spreads change how you plan node pools. If each zone needs at least two replicas and maxSkew is one, a zone outage still leaves usable capacity elsewhere. That aligns with multi-AZ design on the highly available control plane guidance. Worker spread protects the app layer. Control-plane spread protects the cluster API.

Teams operating on tight budgets in Nepal often run smaller clusters. A three-node pool across one zone cannot satisfy strict multi-zone spreads. Document that limitation. Use soft constraints until budget allows a second zone at Rs 15,000–25,000 per month (~USD 110–185) for additional worker capacity. Honest architecture notes prevent midnight pages.

Key Takeaways

  • Define Topology Spread Constraints in Kubernetes on every HA Deployment using zone and hostname topologyKey values.
  • Set maxSkew: 1 with DoNotSchedule for zone rules when you have at least three zones available.
  • Add matchLabelKeys: [pod-template-hash] on Deployments so rolling updates do not deadlock scheduling.
  • Confirm node topology labels exist before enforcing hard constraints on bare-metal or self-managed clusters.
  • Combine spread constraints with resource requests, HPA, and anti-affinity only where pairwise exclusion is required.
  • Inspect scheduler events on pending pods instead of blindly lowering replica counts or deleting constraints.

People Also Ask

What is maxSkew in topology spread constraints?

maxSkew is the maximum allowed difference between the number of matching pods in any topology domain and the domain with the fewest pods. A value of one means no zone may have more than one extra pod compared to the least populated zone.

Should I use DoNotSchedule or ScheduleAnyway?

Use DoNotSchedule for failure-domain rules you refuse to compromise, such as cross-zone HA for payment APIs. Use ScheduleAnyway for node-level preferences where brief co-location beats leaving pods unscheduled during scale-up or maintenance.

Do topology spread constraints work with StatefulSets?

Yes. Add the same topologySpreadConstraints block to the StatefulSet pod template. Remember that StatefulSets bind to persistent volumes. Spreading pods also requires volumes available in each target zone, which ties spread planning to your storage class and PersistentVolume strategy.

How is this different from pod topology spread default constraints?

Cluster administrators can set default constraints via the PodTopologySpread scheduling plugin configuration in the scheduler config. Pod-level constraints merge with defaults. Explicit pod spec rules override cluster defaults for the same topologyKey when both exist. See the scheduler configuration reference for plugin options.

Build Resilient Clusters With Intentional Pod Placement

Topology Spread Constraints in Kubernetes turn high availability from a hope into a schedulable rule. Start with one hard zone spread and one soft hostname spread on your most critical Deployment. Validate labels, test a zone failure in staging, and wire the manifest into GitOps. If you want help designing HA Kubernetes for a Laravel app, eCommerce stack, or enterprise application, review the portfolio or reach out via contact us. Spread constraints are small YAML blocks that pay off the first time a node dies without taking your API with it.

Frequently Asked Questions

Topology Spread Constraints in Kubernetes are scheduling rules in a pod or workload template that tell the scheduler how evenly matching pods should spread across topology domains. A domain is any node label the scheduler understands, such as topology.kubernetes.io/zone or kubernetes.io/hostname. Each constraint uses topologyKey, maxSkew, whenUnsatisfiable, and labelSelector. The scheduler counts matching pods per domain and picks a node that keeps skew within your limit. This is cleaner than chaining pod anti-affinity expressions for every replica set.

maxSkew is the largest allowed difference in pod count between any two topology domains. A value of one means no zone may have more than one extra pod compared to the least populated zone.

topologyKey names the node label that defines each failure domain the scheduler counts against. Common values are topology.kubernetes.io/zone for availability-zone spread and kubernetes.io/hostname for node-level spread. The scheduler groups nodes by that label value, counts matching pods in each group using your labelSelector, and compares counts when placing a new pod. If the topologyKey is misspelled or no nodes carry that label, the rule silently does nothing because no domains match.

Use DoNotSchedule for failure-domain rules you refuse to compromise, such as cross-zone HA. Use ScheduleAnyway for node-level preferences where brief co-location beats leaving pods unscheduled during scale-up.

Add a topologySpreadConstraints block to the pod template spec inside your Deployment. Set maxSkew, topologyKey, whenUnsatisfiable, and labelSelector with matchLabels for your app. For zone HA, use topology.kubernetes.io/zone with DoNotSchedule and maxSkew: 1. Add a second constraint on kubernetes.io/hostname with ScheduleAnyway if you also want node separation within each zone. Pair spread rules with resource requests and limits. On HA Deployments, define at least one hard zone spread and one soft hostname spread from day one.

The scheduler evaluates spread constraints after filtering nodes that fail hard requirements like taints and tolerations. For each feasible node, it computes skew per topology domain. Skew equals the pod count in the candidate domain minus the count in the domain with the fewest matching pods. If every feasible node would produce skew above maxSkew and whenUnsatisfiable is DoNotSchedule, the pod stays pending. Soft constraints with ScheduleAnyway still place the pod but may leave an imbalance. Check pod Events first; scheduler messages usually name the blocking constraint.

Both influence pod placement but differ in expressiveness and operational cost. Spread constraints count pods per domain and express an even distribution goal with a declarative skew limit per topologyKey. Anti-affinity forbids or prefers specific pairwise relationships and can require N² term growth for complex rules. For even distribution across zones, spread constraints usually win. Use anti-affinity when two particular applications must never share a host. Use spread constraints when you want replicas balanced across every zone or node. Many production manifests combine both.

With whenUnsatisfiable set to DoNotSchedule, the pod stays pending until the cluster can satisfy the rule. That is correct behaviour, not a broken deployment. Common causes include too few topology domains for your replica count and maxSkew, missing node labels, or insufficient capacity. With ScheduleAnyway, the scheduler still places the pod even if skew exceeds maxSkew, which may leave temporary imbalance during HPA scale-up or maintenance. Inspect kubectl describe pod Events and cross-check replica count against available labeled domains before lowering replicas or deleting constraints.

labelSelector defines which existing pods count toward the skew calculation for each topology domain. Scope it tightly to your application labels, such as app: api-server, so unrelated workloads on the same node do not affect placement. DaemonSets and other system pods do not inflate counts unless your labelSelector matches them. Without a precise selector, spread math includes pods you did not intend, which can block scheduling or produce misleading balance. Always align labelSelector matchLabels with your Deployment or StatefulSet pod template labels.

During a rolling update, old and new pods coexist. Without scoping, skew counts both revisions together and can block new pods from scheduling, causing update deadlocks on production clusters managed through Argo CD GitOps pipelines. Adding matchLabelKeys with pod-template-hash lets each ReplicaSet revision spread independently because Kubernetes injects pod-template-hash automatically on Deployments. This pattern prevents pending new pods when maxSkew would be exceeded only because outdated revision pods still run in the same zone.

Spread constraints assume nodes expose meaningful topology labels. Bare-metal clusters built with Kubespray need manual labeling or every pod lands in one domain. Six replicas with maxSkew: 1 across two zones forces skew of two; add a third zone or relax the rule. HPA scale-up can outpace hostname spreads, so use ScheduleAnyway at node level. Single-zone dev clusters cannot satisfy hard multi-zone rules; use ScheduleAnyway locally with Minikube or Kind. The minDomains field helps require a minimum number of domains before scheduling. Confirm labels with kubectl get nodes -L before enforcing DoNotSchedule.

Yes. Add the same topologySpreadConstraints block to the StatefulSet pod template. Remember that StatefulSets bind to persistent volumes. Spreading pods also requires volumes available in each target zone, which ties spread planning to your storage class and PersistentVolume strategy. Zone and hostname rules behave the same as on Deployments, but you must verify that PVs exist in every domain where the scheduler may place pods. Without zone-aligned storage, spread constraints can leave StatefulSet pods pending even when node capacity looks sufficient.

Inspect scheduler output first: kubectl describe pod and review Events. Run kubectl get nodes -L topology.kubernetes.io/zone,kubernetes.io/hostname to confirm topology labels exist and domains are distinct. Cross-check replica count against available domains and maxSkew math. Validate manifests generated from Helm or Kustomize with a JSON formatter; typos in topologyKey silently reduce the rule to a no-op. Teams often confuse pending pods with a broken deployment when the real issue is an unsatisfiable spread rule on a two-node cluster spanning one zone.

Cluster administrators can set default constraints via the PodTopologySpread scheduling plugin configuration in the scheduler config. Pod-level constraints merge with those defaults. Explicit pod spec rules override cluster defaults for the same topologyKey when both exist. Pod-level rules give workload owners direct control per Deployment, while defaults enforce baseline placement policy cluster-wide. See the scheduler configuration reference for plugin options. If you rely on defaults alone, confirm they match your HA expectations before assuming every workload spreads across zones automatically.

Hard zone spreads change how you plan node pools. If each zone needs at least two replicas and maxSkew is one, a zone outage still leaves usable capacity elsewhere. That aligns with multi-AZ design for the app layer while control-plane spread protects the cluster API. Teams on tight budgets often run smaller clusters; a three-node pool in one zone cannot satisfy strict multi-zone spreads. Document that limitation and use soft constraints until budget allows a second zone at Rs 15,000–25,000 per month (~USD 110–185) for additional worker capacity. Honest architecture notes prevent midnight pages.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: