
September 02, 2026
13 min read
By Kokil Thapa | Last reviewed: September 2026
Your pod lands on the wrong node, or worse, never lands at all. That is usually an affinity misconfiguration, not a mystery scheduler bug. Understanding node affinity vs pod affinity is the fastest way to fix Pending pods and enforce real topology rules. Node affinity filters by node labels such as GPU type or availability zone. Pod affinity and anti-affinity filter by labels on pods already running on candidate nodes. This guide walks through the YAML patterns I use when deploying Laravel applications to Kubernetes, plus the debugging steps that actually resolve production scheduling failures.
What Is the Difference Between Node Affinity vs Pod Affinity in Kubernetes?
Kubernetes scheduling is constraint satisfaction. The scheduler must find a node with enough CPU and memory, matching tolerations, and passing every affinity rule. These rules are evaluated after basic filtering but before final scoring.
Node affinity replaces the older nodeSelector field. It reads labels on Node objects. Typical keys include gpu=nvidia-a100, disk=ssd, or topology.kubernetes.io/zone. Operators such as In, NotIn, Exists, DoesNotExist, Gt, and Lt give you boolean logic beyond simple equality.
Pod affinity and pod anti-affinity look at pod labels on nodes under consideration. Pod affinity co-locates workloads. Pod anti-affinity spreads them apart. The scheduler asks a different question: do the pods already on this node match my label selector?
That scope difference is the entire node affinity vs pod affinity debate. Node affinity answers "does this hardware match?" Pod affinity answers "does this neighbourhood match?" Mixing them up produces rules that look valid in YAML but fail at runtime.
Most production clusters need both mechanisms. On a client project, worker pods needed GPU nodes through node affinity. API replicas needed zone-level anti-affinity for high availability. Treating them as interchangeable is a common mistake that leads to unschedulable pods or fragile architectures.
The official Kubernetes documentation on assigning pods to nodes using affinity defines the field structure. Read that page once, then treat this article as the operational layer on top.
How Do You Configure Kubernetes Node Affinity for Hardware Workloads?
Node affinity uses two scheduling phases in the field name. requiredDuringSchedulingIgnoredDuringExecution is a hard constraint. If no node matches, the pod stays Pending. preferredDuringSchedulingIgnoredDuringExecution is a soft preference. The scheduler still places the pod, but matching nodes score higher.
The IgnoredDuringExecution suffix matters. If labels change after scheduling, running pods are not evicted. Only new scheduling decisions use the updated labels. For strict enforcement after placement, combine affinity with admission webhooks or operational label governance.
Hard node affinity for GPUs and licensed nodes
Use hard node affinity when the workload cannot run elsewhere. ML inference, video transcoding, and licensed software tied to specific hardware fall here. This manifest pins a pod to nodes labelled gpu=nvidia-a100 in two zones:
apiVersion: v1
kind: Pod
metadata:
name: ocr-worker
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- nvidia-a100
- key: topology.kubernetes.io/zone
operator: In
values:
- us-east-1a
- us-east-1b
containers:
- name: ocr
image: ocr-processor:2026.08
resources:
limits:
nvidia.com/gpu: 1 Boolean logic trips people up. Multiple matchExpressions inside one nodeSelectorTerm are ANDed. Multiple nodeSelectorTerms are ORed. Need GPU type A or type B? Create two terms. Need GPU and a specific zone? Put both expressions in one term.
Soft node affinity for cost control
Batch jobs and background workers often prefer spot or preemptible nodes without blocking when capacity is gone. Soft affinity uses weights from 1 to 100:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: node.kubernetes.io/lifecycle
operator: In
values:
- spot
- weight: 20
preference:
matchExpressions:
- key: node-size
operator: In
values:
- large The scheduler sums weights for every matching preference. In my experience managing cloud hosting costs, soft affinity for spot instances cuts compute spend on background processors without blocking scheduling during capacity crunches.
Before adding node affinity to a new cluster, confirm your node labelling strategy matches your scheduler filtering pipeline. Unlabelled nodes silently fail hard rules.
When Should You Use Pod Affinity vs Pod Anti-Affinity?
Pod affinity pulls pods together. Pod anti-affinity pushes them apart. The choice depends on your failure model and latency budget. Getting it backwards is worse than having no rules at all.
| Scenario | Mechanism | Topology Key | Risk if Misconfigured |
|---|---|---|---|
| Spread replicas across AZs | Anti-Affinity (required) | topology.kubernetes.io/zone | All replicas in one zone; zone outage takes the service down |
| Co-locate app and cache | Affinity (preferred) | kubernetes.io/hostname | Cross-node cache calls add latency; hard rule causes Pending pods |
| Isolate noisy tenants | Anti-Affinity (required) | kubernetes.io/hostname | Noisy neighbour degrades SLA for adjacent workloads |
| Separate DB primary and replica | Anti-Affinity (required) | topology.kubernetes.io/zone | Primary and replica fail together on storage or power loss |
| Workers near data source | Affinity (preferred) | topology.kubernetes.io/zone | Cross-zone egress charges; hard rule blocks scaling in one zone |
A pattern I see in multi-tenant SaaS applications is hard pod anti-affinity on kubernetes.io/hostname for tenant isolation. It works until tenant count exceeds node count. Then new pods pend indefinitely. Pair hard anti-affinity with alerts on Pending duration. Or use soft anti-affinity with weight 90 or higher to strongly prefer separation without guaranteeing it.
Pod anti-affinity YAML for zone spreading
This Deployment spreads three API replicas across availability zones. Each replica refuses to schedule in a zone that already runs a pod with the same app=api label:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- api
topologyKey: topology.kubernetes.io/zone
containers:
- name: api
image: my-api:2026.09 Hard anti-affinity with three replicas needs at least three distinct zones. Two zones means one replica stays Pending forever. Count your topology domains before you deploy.
The topologyKey trap
Every pod affinity rule needs a topologyKey. This defines the boundary for co-location or separation. kubernetes.io/hostname means same node or different node. topology.kubernetes.io/zone means same zone or different zone.
Hostname-level anti-affinity with three replicas on five nodes works until two nodes drain for maintenance. Then the third replica cannot schedule anywhere. Match the topology key to your real failure domain. In cloud clusters, zone-level anti-affinity is usually correct for HA. Hostname-level suits cache locality or noisy-neighbour isolation.
See the well-known labels and annotations reference for standard topology keys. Custom keys work only if every node carries them consistently.
Why Are Pods Stuck in Pending After Adding Affinity Rules?
Pending pods after affinity changes almost always come from impossible constraints, label drift, or too few topology domains. Debug each cause in order instead of guessing.
- Describe the pod: Run
kubectl describe pod <name>and read Events. Look for "0/N nodes are available" with an affinity-specific reason. - Verify node labels: Run
kubectl get nodes --show-labels. Confirm target keys exist. Typos pass YAML lint but fail at scheduling time. - Match label values exactly: Values are case-sensitive.
us-east-1ais notUS-EAST-1A. - Count nodes versus replicas: Hard hostname anti-affinity with N replicas needs at least N nodes. Maintenance on one node can block the last replica.
- Audit combined rules: Node affinity requiring zone A plus pod anti-affinity separating from pods only in zone A creates deadlock.
On a production eCommerce system, checkout pods stayed Pending for hours. Node affinity targeted tier=frontend, but a cluster upgrade had relabelled those nodes to tier=web. The YAML was fine. The infrastructure had drifted. I now include label verification in CI/CD pipeline pre-deploy checks to catch drift before rollout. Validate YAML structure with a JSON or YAML formatter locally, but never skip live label checks against the cluster.
Recovery patterns for over-constrained clusters
When hard affinity causes scheduling deadlocks, you have options beyond deleting rules. Downgrade hard rules to soft rules temporarily during maintenance. Use preferredDuringSchedulingIgnoredDuringExecution with weight 100 for strong preference without blocking. Or add nodes to satisfy implicit minimum counts that hard anti-affinity creates.
For deeper scheduler context, read about taints and tolerations. Missing tolerations often produce misleading affinity errors because the node was filtered out earlier in the pipeline.
How Does Affinity Interact with Taints, Topology Spread, and Resource Limits?
Affinity does not operate alone. Other scheduling primitives filter nodes before affinity scoring runs. Understanding the order prevents hours of misdirected debugging.
Taints and tolerations gate eligibility first. A node tainted NoSchedule=gpu:true rejects pods without a matching toleration. Affinity only evaluates among remaining eligible nodes. Always add tolerations before node affinity targeting tainted GPU or dedicated nodes. The Kubernetes basics deployment guide covers the full pod spec structure if you are starting fresh.
Topology Spread Constraints complement pod anti-affinity. Anti-affinity is binary: pods are separated or they are not. Topology spread provides proportional balancing with maxSkew. Spreading ten replicas across three zones fails with hard anti-affinity alone. Spread constraints with maxSkew: 1 allow at most one replica difference per zone. Read topology spread constraints before choosing anti-affinity for large replica counts.
Resource requests filter before affinity scoring. A node can match every affinity rule and still reject the pod for insufficient CPU or memory. On resource-constrained clusters, affinity rules look broken when fragmentation is the real issue. Check kubectl top nodes alongside affinity debugging. Set requests correctly first using the resource limits and requests guide.
Horizontal Pod Autoscaler respects affinity at scale. When HPA adds replicas, each new pod must satisfy the same affinity rules. Hard anti-affinity creates a ceiling on replica count equal to your topology domain count. Factor that into autoscaling design alongside horizontal pod autoscaling configuration.
What Production Patterns Make Node Affinity vs Pod Affinity Reliable?
Documentation teaches syntax. Production reliability needs affinity treated as infrastructure code. Version-control every rule. Test against a staging cluster with the same node labels as production. Document the business reason behind each constraint in YAML comments or runbooks.
- Start with soft rules during initial rollout. Confirm label schemas match reality. Graduate to hard rules only after verifying topology coverage.
- Alert on pods Pending longer than five minutes with affinity-related Events. Pending duration is a leading indicator of misconfiguration.
- Validate node labels in CI before deploy. A single relabel during cluster upgrade can block an entire Deployment.
- Document implicit minimum node counts that hard anti-affinity creates. Three zone-spread replicas need three zones, not two.
- Combine zone anti-affinity with PodDisruptionBudgets so voluntary drains respect your HA intent during upgrades.
Teams running PHP workloads on Kubernetes often layer affinity after fixing resource requests and health probes. That order matters. Affinity cannot schedule pods that fail readiness checks or exceed available capacity. For booking platforms like Adventure Third Pole Trek, zone-spread API pods survived an availability zone outage because anti-affinity was tested before go-live, not added after an incident.
If your team lacks in-house Kubernetes expertise, Linux system administration and cluster operations support can cover scheduler tuning, label governance, and production debugging. For architecture review on your specific topology, reach out to discuss your setup.
Related reading: debugging CrashLoopBackOff pods, network policies for pod isolation, and GitOps deployments with Argo CD for keeping affinity manifests synchronized across environments. The scheduler performance tuning guide covers filter latency when clusters grow past a few hundred nodes.
Key Takeaways
- Node affinity vs pod affinity: node rules filter by node labels; pod rules co-locate or spread based on pod labels and a topology key.
- Use
requiredDuringSchedulingIgnoredDuringExecutiononly when Pending is acceptable; usepreferredDuringSchedulingIgnoredDuringExecutionfor cost and capacity flexibility. - Hard pod anti-affinity on hostname with N replicas requires at least N nodes; zone-level anti-affinity requires enough distinct zones.
- Debug Pending pods with
kubectl describe pod, then verify node labels, topology domain counts, and rule conflicts. - Taints, resource requests, and topology spread constraints filter nodes before affinity scoring—check those first when rules look ignored.
- Treat affinity YAML as production code: version it, validate labels in CI, and alert on scheduling failures before users notice.
People Also Ask
What is the difference between nodeSelector and node affinity?
nodeSelector is a simple map of label key-value pairs. All must match or the pod stays Pending. Node affinity adds operators, soft preferences with weights, and OR logic across multiple terms. Node affinity replaces nodeSelector for any non-trivial hardware or topology requirement in modern Kubernetes clusters.
Does node affinity evict pods when node labels change?
No. The IgnoredDuringExecution suffix means running pods stay put if labels change after scheduling. Only new pods evaluate updated labels. For strict post-placement enforcement, use operational processes, admission controllers, or consider the alpha requiredDuringSchedulingRequiredDuringExecution field where your cluster version supports it.
Can pod affinity and pod anti-affinity conflict in the same pod spec?
Yes, and the scheduler must satisfy both simultaneously. A pod requiring co-location with cache pods through affinity while anti-affining away from other API replicas can become unschedulable if no node satisfies both rules. Audit combined constraints before deploying to production.
How does node affinity vs pod affinity affect cluster autoscaling?
Hard anti-affinity sets an upper bound on replica count equal to available topology domains. Cluster autoscaler adds nodes, but it cannot create new zones. If you need more replicas than zones or nodes allow, soften anti-affinity or expand topology domains before enabling aggressive autoscaling.
Put Node Affinity vs Pod Affinity to Work in Your Cluster
Node affinity vs pod affinity is not academic trivia. It encodes failure domains, latency budgets, and cost trade-offs directly into your scheduler. Start soft, validate labels against live nodes, then harden rules once topology coverage is proven. Monitor Pending pods, document minimum node counts, and keep affinity manifests in the same Git repo as your application code.
Need help designing scheduling policies for a Laravel, eCommerce, or multi-tenant workload? Contact us for a cluster architecture review and we will map affinity rules to your actual failure model—not a generic template copied from documentation.
Frequently Asked Questions
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.

