Kubernetes
Pod Scheduling
Node Affinity
Kubernetes Configuration
Resource Management

Allow only one pod of a type on a node in Kubernetes

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

If you want no more than one pod of a given type on each Kubernetes node, the normal answer is pod anti-affinity. You give the pods a label, then instruct the scheduler not to place two matching pods on the same hostname.

This is a scheduling problem, not a container image problem or a resource limit problem. Kubernetes needs an explicit rule that says matching pods should avoid each other at the node level.

Use Pod Anti-Affinity On kubernetes.io/hostname

The most direct solution is a hard pod anti-affinity rule with topologyKey: kubernetes.io/hostname. That tells the scheduler to treat each node as a separate placement domain.

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: api
5spec:
6  replicas: 3
7  selector:
8    matchLabels:
9      app: api
10  template:
11    metadata:
12      labels:
13        app: api
14    spec:
15      affinity:
16        podAntiAffinity:
17          requiredDuringSchedulingIgnoredDuringExecution:
18            - labelSelector:
19                matchExpressions:
20                  - key: app
21                    operator: In
22                    values:
23                      - api
24              topologyKey: kubernetes.io/hostname
25      containers:
26        - name: api
27          image: nginx:1.27

With enough schedulable nodes, Kubernetes will place one app=api pod per node. If you ask for more replicas than available eligible nodes, the extra pods remain pending.

Required Versus Preferred Rules

There are two forms of anti-affinity. The hard form is requiredDuringSchedulingIgnoredDuringExecution. Use it when colocating two matching pods on one node must never happen.

If you only want a preference, use preferredDuringSchedulingIgnoredDuringExecution:

yaml
1affinity:
2  podAntiAffinity:
3    preferredDuringSchedulingIgnoredDuringExecution:
4      - weight: 100
5        podAffinityTerm:
6          labelSelector:
7            matchLabels:
8              app: api
9          topologyKey: kubernetes.io/hostname

A preferred rule asks the scheduler to spread pods out when possible, but it will still place multiple matching pods on the same node if the cluster is tight on capacity.

When A DaemonSet Is The Better Answer

Sometimes the real requirement is not “at most one per node” but “exactly one on every eligible node.” If that is what you mean, a DaemonSet is usually the correct workload controller.

yaml
1apiVersion: apps/v1
2kind: DaemonSet
3metadata:
4  name: log-agent
5spec:
6  selector:
7    matchLabels:
8      app: log-agent
9  template:
10    metadata:
11      labels:
12        app: log-agent
13    spec:
14      containers:
15        - name: log-agent
16          image: fluent/fluent-bit:3.1

A DaemonSet naturally runs one pod on each eligible node, subject to node selectors, taints, and tolerations. That is cleaner than forcing a Deployment to simulate node-local behavior.

Node Affinity Is Different

The file title mentions a pod type on a node, and that often leads people to node affinity. Node affinity selects which nodes a pod may run on. It does not by itself prevent two matching pods from landing on the same node.

If you need both behaviors, combine them:

  • node affinity to restrict the eligible nodes
  • pod anti-affinity to keep matching pods apart on those nodes

That combination is common for workloads that should run only on GPU nodes, storage nodes, or nodes with a specific compliance label.

How To Verify Placement

After applying the manifest, inspect the scheduled pods:

bash
kubectl get pods -o wide
kubectl describe pod api-xxxx

If a pod stays pending, kubectl describe pod usually includes a scheduler event showing that anti-affinity rules blocked placement. That message is often the fastest way to confirm the rule is working as intended.

Common Pitfalls

  • Using a soft preferred rule when the requirement is actually strict.
  • Forgetting to put the matching label on the pod template, so the anti-affinity selector never matches.
  • Expecting Kubernetes to schedule all replicas even when there are not enough nodes.
  • Using node affinity alone and wondering why duplicate pods still land on one node.
  • Choosing a Deployment when the workload is really a DaemonSet use case.

Summary

  • Use pod anti-affinity to keep matching pods from sharing a node.
  • Set topologyKey to kubernetes.io/hostname for one-per-node behavior.
  • Use requiredDuringSchedulingIgnoredDuringExecution for a hard rule.
  • Expect pending pods if replica count exceeds the number of eligible nodes.
  • Use a DaemonSet when the requirement is one pod on every eligible node.

Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design