Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

Affinity & Anti-Affinity Rules

Master Kubernetes node and pod affinity rules to control workload placement: required vs preferred constraints, operators, and practical YAML examples.

Focus: use affinity and anti-affinity rules

Sponsored

You've built the cluster, deployed pods, and scaled them with ReplicaSets. But what happens when your database pod is scheduled on a node alongside your CPU-hungry video transcoder, or your required PCI-compliant pods end up on the same host as a noisy neighbor? Without explicit placement rules, the Kubernetes scheduler treats all nodes as equals — which can lead to performance bottlenecks, availability risks, or compliance violations. Affinity and anti-affinity rules give you surgical control over where your pods land, allowing you to co-locate or separate workloads based on node characteristics or other pod identities.

The problem this lesson solves

Default pod scheduling is distributed — the scheduler spreads pods across available nodes to balance resource usage. That's fine for stateless web apps, but many real-world scenarios demand targeted placement:

  • Performance isolation: Keep your database pods away from batch processing pods that might eat CPU or memory.
  • Availability & resilience: Spread replicas of a StatefulSet across different nodes, zones, or racks so a single failure doesn't take down the service.
  • Compliance & data locality: Ensure pods that must stay in a specific region or on hardware with special security markings run only on those nodes.
  • Cost optimization: Collocate pods that communicate heavily (e.g., app and cache) on the same node or in the same zone to reduce cross-node network traffic and latency.
  • Hardware requirements: Some workloads need GPUs, fast SSDs, or large memory — you can pin them to nodes with those resources.

Pro Tip: Affinity and anti-affinity rules are declarative, not imperative. You express your intentions in the Pod spec, and the scheduler respects them during scheduling only. They don't prevent existing pods from being moved — for that, you need taints and tolerations.

Core concept / mental model

Think of the Kubernetes scheduler as a matchmaker. Without rules, it introduces pods to any node with enough resources. Affinity rules are like saying "I only want pods matched to nodes from this region" or "I want my frontend pods near my API pods." Anti-affinity rules say "Don't sit my database replicas on the same node."

There are two dimensions in Kubernetes:

  • Node affinity: Constrains which nodes a pod can be scheduled on, based on node labels.
  • Pod affinity / anti-affinity: Constrains scheduling based on the labels of other pods already running on the node.

Both support two enforcement levels: - requiredDuringSchedulingIgnoredDuringExecution — hard constraint: the pod will not be scheduled unless the condition is met. - preferredDuringSchedulingIgnoredDuringExecution — soft constraint: the scheduler tries to satisfy it, but will place the pod elsewhere if necessary.

The "IgnoredDuringExecution" part means that once a pod is running, changes to labels or node resources won't evict it. For runtime enforcement, you'd need a mutating admission webhook or custom controller.

How it works step by step

1. Label your nodes (or rely on existing labels)

Many cloud providers set labels like failure-domain.beta.kubernetes.io/zone, kubernetes.io/hostname, and beta.kubernetes.io/instance-type. You can also add custom labels:

kubectl label node worker-1 topology.kubernetes.io/zone=us-east-1a
kubectl label node worker-2 hw-type=gpu

2. Write the Pod spec with affinity rules

Node affinity example — run only on nodes with SSD storage:

apiVersion: v1
kind: Pod
metadata:
  name: fast-storage-pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disk-type
            operator: In
            values:
            - ssd
  containers:
  - name: app
    image: nginx

Pod anti-affinity example — ensure replicas of a deployment aren't on the same node (high availability):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web
            topologyKey: kubernetes.io/hostname
      containers:
      - name: nginx
        image: nginx

3. The scheduler matches labels with expressions

Inside matchExpressions, you use operators: - In / NotIn: set membership - Exists / DoesNotExist: presence of label key (value ignored) - Gt / Lt: greater-than or less-than (for numeric labels)

A pod with podAntiAffinity can use topologyKey — the label domain for placement. Common keys: - kubernetes.io/hostname — separate nodes - topology.kubernetes.io/zone — separate availability zones - topology.kubernetes.io/region — separate regions

Hands-on walkthrough

Let's deploy two microservices — api and cache — where: - api must run on GPU nodes (hw-type=gpu) - cache should be as far from other cache pods as possible (anti-affinity by zone)

Step 1: Label nodes

kubectl label node worker-2 hw-type=gpu
kubectl label node worker-3 hw-type=gpu

Step 2: Deploy API with node affinity

Create api-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: hw-type
                operator: In
                values:
                - gpu
      containers:
      - name: api
        image: nginx
kubectl apply -f api-deployment.yaml

Verify pods land only on GPU nodes:

kubectl get pods -o wide | grep api

Step 3: Deploy cache with pod anti-affinity (by zone)

Assume your nodes have topology.kubernetes.io/zone labels (set by cloud provider or manually).

Create cache-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cache
spec:
  replicas: 3
  selector:
    matchLabels:
      app: cache
  template:
    metadata:
      labels:
        app: cache
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - cache
              topologyKey: topology.kubernetes.io/zone
      containers:
      - name: cache
        image: redis
kubectl apply -f cache-deployment.yaml
kubectl get pods -o wide | grep cache

You'll see each cache pod is scheduled to a different zone (if available). If only two zones exist, two pods land in separate zones and the third will share one — because it's preferred, not required.

Compare options / when to choose what

Rule type Hard (required) Soft (preferred) Use case
Node affinity Pod won't schedule unless node label matches Scheduler tries best effort, may skip Pin GPU workloads to GPU nodes (hard); prefer SSD nodes for databases (soft)
Pod affinity Pod must co-locate with matching pods Scheduler prefers co-location Place frontend near backend for low latency (preferred); enforce same-node for sidecar (required)
Pod anti-affinity Pod must not co-locate with matching pods Scheduler avoids co-location Spread replicas across nodes for HA (required); avoid putting cache pods on same rack (preferred)

When to use each:

  • Use node affinity when placement depends on node capabilities (GPU, SSD, region).
  • Use pod affinity when communication latency or data locality matters (e.g., app + cache).
  • Use pod anti-affinity for high availability, failure isolation, or noisy neighbor avoidance.

Note: requiredDuringScheduling rules can cause pods to remain Pending if no node matches. Always set preferred unless the workload absolutely cannot run elsewhere.

Troubleshooting & edge cases

  • Pod stuck in Pending: Check kubectl describe pod <name> for events. Look for 0/X nodes are available: 1 node(s) didn't match node affinity. Ensure nodes have the required labels. If using requiredDuringScheduling, the pod won't schedule until a matching node exists.
  • Wrong pods separated: Pod anti-affinity acts on pods with matching labels. If your cache and web pods share a label value (e.g., app: myapp), they'll anti-affect each other. Use unique label values per service.
  • Too many rules slow scheduling: Complex matchExpressions with many values or nested conditions can make the scheduler iterate more. Keep expressions simple. For very large clusters, consider sticking to nodeSelector (which is simpler) when possible.
  • Scheduling across zones fails: If your cluster doesn't have topology.kubernetes.io/zone labels on nodes (e.g., minikube or single-node cluster), anti-affinity by zone won't work. Add labels manually for testing, or use kubernetes.io/hostname for node-level separation.
  • preferred rules ignored: A soft rule with weight: 1 is almost never considered. Use higher weight (e.g., 100) to make it more influential. Weights are summed across all soft rules; the scheduler scores nodes and picks the highest.

What you learned & what's next

You now understand how to use affinity and anti-affinity rules to control pod placement. You learned: - The difference between node affinity (pinning to hardware) and pod affinity/anti-affinity (co-location or separation with other pods). - Hard vs soft constraints and when to use each. - How to write YAML with matchExpressions and topologyKey. - Practical examples for high availability (pod anti-affinity) and hardware targeting (node affinity).

Next topic: Now that you can place pods precisely, you'll want to ensure they run with appropriate resources. Move to Resource limits and requests to guarantee your scheduled pods get the CPU and memory they need — without starving others.

Practice recap

Try this: Deploy a 3-replica web app with podAntiAffinity using requiredDuringSchedulingIgnoredDuringExecution on kubernetes.io/hostname. Then label one node with gpu=true and redeploy a second app with node affinity to that GPU node. Use kubectl get pods -o wide to verify placement. If you hit a scheduling deadlock, change the anti-affinity to preferred.

Common mistakes

  • Using requiredDuringScheduling for pod anti-affinity with a topologyKey that has only one value (e.g., single zone) — pods will never schedule because no two nodes satisfy the rule. Use preferred or widen topologyKey to kubernetes.io/hostname.
  • Forgetting to label nodes — relying on default labels that don't exist in your cluster (e.g., minikube lacks zone labels). Always verify labels with kubectl get nodes --show-labels before writing rules.
  • Using matchExpressions with In operator but an empty values list — the pod will never match any node. Always provide at least one valid value.
  • Applying pod anti-affinity with the same labelSelector for pods that have identical label values (e.g., app: myapp on both cache and web) — they will anti-affect each other and may fail to schedule together on separate nodes. Use distinct label values per workload.

Variations

  1. Use nodeSelector for simple node selection based on a single label (no operators). It's quicker but less expressive than nodeAffinity.
  2. Combine requiredDuringScheduling with preferredDuringScheduling — e.g., must be on a GPU node (required), prefer same zone as the cache pod (preferred).
  3. Leverage weight in preferred rules: set weight 1 for minor preferences, weight 100 for strong preferences. The scheduler sums weights and selects the node with the highest total.

Real-world use cases

  • High-availability database cluster: Deploy three PostgreSQL replicas with requiredDuringScheduling pod anti-affinity on kubernetes.io/hostname to ensure no two replicas live on the same node.
  • GPU workload isolation: Use node affinity to schedule machine learning training pods only on nodes with label accelerator=nvidia-tesla-v100, preventing CPU-only nodes from being occupied.
  • Latency-sensitive microservices: Co-locate a Redis cache and its consuming API service in the same zone using pod affinity with topologyKey: topology.kubernetes.io/zone to reduce cross-zone network cost.

Key takeaways

  • Node affinity controls pod placement based on node labels (hardware, region, feature).
  • Pod affinity/anti-affinity controls placement based on labels of other pods running on nodes.
  • Use requiredDuringScheduling for hard constraints that must be met; use preferredDuringScheduling for best-effort placement.
  • Always set a topologyKey for pod affinity/anti-affinity to define the scope of separation or co-location.
  • Pod anti-affinity is critical for high availability — spread replicas across hosts, zones, or regions.
  • Keep matchExpressions simple to avoid scheduler slowdown in large clusters.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.