Split a Deployment Evenly Across Two Zones
Scenario
The cluster has three nodes. Two of them are tagged with the zone they sit in, and the third is reserved for system workloads and refuses everything else:
| Node | topology.kubernetes.io/zone | Takes your Pods |
|---|---|---|
node-01 | zone-a | yes |
node-02 | zone-b | yes |
cplane-01 | none | no |
Have a look at what the nodes actually carry before you start:
kubectl get nodes --show-labels
kubectl describe node cplane-01 | grep -i taint
The storefront namespace is empty.
Task
Create a Deployment named web in the storefront namespace:
- 8 replicas, all running.
- Image
nginx:alpine-slim. - Pods labelled
app: web, which is also what the Deployment selects on. - The replicas end up split 4 and 4 between the two zones, never 5 and 3. Group them by the
nodes'
topology.kubernetes.io/zonelabel, not by node name or any other label. - The split is enforced by the Deployment itself, through one rule that counts the
app: webPods. If a Pod cannot be placed without breaking the balance, the scheduler must leave it unscheduled rather than put it in the fuller zone.

Eight Pods, four per zone. A Pod that would make one zone fuller waits instead of landing there.
That rule is the only placement setting in the Pod template. No nodeSelector, no node or Pod
affinity, no tolerations, no nodeName. Leave the node labels and the taint on cplane-01 alone.
Hint 1: Who decides where a Pod lands?
A Deployment never picks nodes. It creates Pods and hands them to the scheduler, so a rule about placement is useless anywhere except the Pod template, which is the part of the Deployment the scheduler reads.
Start by seeing what a Pod can say for itself:
kubectl explain pod.spec
Read the field list, not the descriptions yet. A handful of fields are about where a Pod runs. Your job is to work out which of them can describe a set of Pods rather than a single one.
Hint 2: Ruling out the obvious candidates
Take the placement fields one at a time and ask what each can express:
- one pins a Pod to a node by name, no scheduler involved
- one requires or prefers nodes carrying particular labels
- one lets a Pod onto a node that would otherwise turn it away
Every one of those describes a Pod's relationship to a node. An even split is a statement about this Pod's relationship to the other Pods like it, which none of them can say. One field in the list is left. Read its description in full:
kubectl explain pod.spec.<the remaining field>
Hint 3: The shape of the block
Four of its fields matter here, and the task has already decided three of them:
- the node label whose values define the groups, which the task names outright
- which Pods get counted, which the task fixes at
app: web - what happens when the rule cannot be met, which the task describes in words
- how far the group sizes may drift apart, which is the only one left to reason about
Filled in, it sits in the Pod template like this:
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: <...>
topologyKey: <...>
whenUnsatisfiable: <...>
labelSelector:
matchLabels:
app: web
containers:
- name: nginx
image: nginx:alpine-slim