Pod anti-affinity expresses where a Pod should avoid selected peers. A hostname topology can reduce shared-node exposure; a zone topology can address a broader failure domain. The rule does not create extra capacity or prove application availability. It shapes placement, while replication, readiness, storage, and failure recovery remain separate responsibilities.

Required rules filter candidates; preferred rules contribute scores. If hard constraints leave no feasible node, the new Pod remains Pending until something relevant changes. That can be the correct outcome for a strict isolation requirement, but it must be included in capacity planning rather than discovered during an incident.

A peer label selector and namespace scope select Pods whose locations are grouped by node topology labels. A required hostname rule for nine mutually selected replicas occupies six eligible domains and leaves three Pending. A separate topology-spread example starts with equally eligible counts 2 and 1 and maxSkew 1: adding to the fuller domain yields 3 minus 1 and is blocked; adding to the emptier domain yields 2 minus 1 and passes this check, leaving final counts 2 and 2. Eligible-domain shortfall below minDomains uses global minimum zero. Admission, missing labels, other hard rules, preferences and no automatic rebalance remain caveats. Cluster integration was not run.
Required anti-affinity excludes selected peers from an occupied topology domain. Topology spread instead bounds a candidate placement’s count difference; both depend on selectors, namespace scope, eligible domains and other constraints.

Select peers and define their domains

The label selector identifies peer Pods, and topologyKey names the node label used to group their locations. kubernetes.io/hostname commonly distinguishes nodes; topology.kubernetes.io/zone groups nodes into zones. Check the actual values rather than assuming every infrastructure provider supplies an identical label surface.

Namespace scope is part of the rule. With neither an explicit namespace list nor a namespace selector, matching defaults to the incoming Pod’s namespace. An empty namespaceSelector object matches all namespaces. Thus cross-namespace influence is possible when configured; the feature is not inherently a tenant-isolation boundary. Review both peer labels and namespace selection together.

All relevant nodes need consistent topology labels. Kubernetes warns that missing labels cause unintended anti-affinity behavior. Do not collapse that warning into a universal claim that every unlabelled node is always accepted or always excluded. Inter-pod rules, existing placements, and topology spread are different mechanisms; inspect the rule and actual candidate set.

A hard rule can cap placement

For replicas that select each other, required hostname anti-affinity generally permits only one matching replica per eligible hostname domain. Nine such replicas across six eligible nodes leave three waiting, even if each node has spare CPU. Rollout surge Pods can hit the same cap before ordinary replica growth does.

Required zone anti-affinity would likewise restrict matching peers per zone, but admission configuration matters. LimitPodHardAntiAffinityTopology restricts required anti-affinity to the hostname key when enabled. Do not assume a zone-level hard rule will be admitted by every cluster. Check the cluster’s permitted topology before designing availability around it.

IgnoredDuringExecution means these scheduling rules do not automatically evict or redistribute already-running Pods after the situation changes. A workload-template update affects replacement Pods; it does not instantly rebalance existing ones. Existing hard anti-affinity can also constrain incoming Pods, so examining only the new Pod’s preferred term is insufficient.

The Kubernetes 1.34.0 scoring plugin also considers existing Pods’ preferred terms; an option can skip that work when the incoming Pod has no inter-pod preferences. Existing preferences are therefore not universally ignored.

Use spread constraints for bounded imbalance

Topology spread expresses a different contract from one-peer-per-domain exclusion. With DoNotSchedule, maxSkew limits a candidate domain’s matching Pod count relative to the global minimum across eligible domains. With ScheduleAnyway, reducing skew is a preference. Multiple constraints combine, so every hard constraint must be satisfiable.

For equally eligible zones with two and one matching Pods and maxSkew one, adding to the fuller zone would make the relevant difference two; adding to the emptier zone preserves balance. If eligible domains fall below minDomains, the global minimum becomes zero. It is not raised. That option is valid with DoNotSchedule and can deliberately prevent further concentration.

Balanced counts depend on labels, selectors, eligibility, and the sequence of placements; they are not an unconditional promise of three-three-three for nine replicas. Inter-pod affinity can impose significant scheduling cost in large clusters. Prefer the mechanism that expresses the intended contract clearly, and measure scheduler performance rather than claiming a universal speed comparison.

When it breaks

Too few eligible domains can leave extra replicas Pending while HPA continues recommending growth. Adding a node in an already occupied zone does not resolve a one-per-zone rule. Relaxing a hard rule changes the availability contract; choose that trade-off deliberately instead of treating it as a harmless scheduling fix.

Missing topology labels and an incorrect peer selector can make intended separation meaningless. A rule selecting no peers proves nothing, while overly broad selection can block unrelated workloads. Inspect namespace scope and labels before attributing every uneven distribution to a scheduler failure.

Check it quickly

Use the intended read-only context and namespace. These existing commands were preserved and were not rerun against a cluster during this review.

kubectl get nodes -L topology.kubernetes.io/zone -L kubernetes.io/hostname
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,PHASE:.status.phase
kubectl get events --field-selector reason=FailedScheduling --sort-by=.lastTimestamp | tail -5
kubectl get pods -o jsonpath='{range .items[*]}{.spec.nodeName}{"\n"}{end}' | sort | uniq -c

The Pod listings cover the whole namespace, not necessarily the rule’s peer set. Blank node names include unscheduled Pods; blank topology cells identify missing labels. Filter the observations conceptually by the actual selector and namespace scope, then compare domains and events. One Pod per node does not prove anti-affinity caused the distribution, and uneven counts do not alone prove a spread violation.

Sources