Skip to main content
RunBook Academy

KubernetesXXV · Topology Spread ConstraintsTopology spread

labelSelector and matchLabelKeys — dynamic grouping

Advanced⏱ ~16 minkubectlkubeadm

What you'll learn

  • Configure labelSelector for static topology spread
  • Configure matchLabelKeys for dynamic grouping
  • Distinguish static and dynamic constraints
  • Apply matchLabelKeys for versioned deployments and per-tenant isolation

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

labelSelector is the static form: the Pods to count are those matching a fixed selector (e.g., app: web). matchLabelKeys (introduced in Kubernetes 1.27) is the dynamic form: the Pods to count are those matching by label keys, allowing per-version or per-tenant grouping. This lesson covers both forms, the trade-offs, and the production patterns.

labelSelector — static

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web

The Pods to count are those with app: web. The selector is static; changing it requires re-applying the manifest.

flowchart LR
    A[Pod with constraint] --> B["Count Pods with<br/>app: web per domain"]
    B --> C[web-1, web-2, web-3...]
    C --> D[Skew check]
    D --> E[Schedule or Pending]

matchLabelKeys — dynamic

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  matchLabelKeys:
  - pod-template-hash

The Pods to count are those whose pod-template-hash label matches the Pod’s own pod-template-hash. This is the dynamic form: the constraint applies per version (per ReplicaSet) automatically.

flowchart LR
    A[Pod arrives] --> B["Read pod-template-hash<br/>= abc123"]
    B --> C["Count Pods with<br/>pod-template-hash=abc123<br/>per domain"]
    C --> D[Skew check]
    D --> E[Schedule]

When to use which

flowchart TB
    A{Constraint type}
    A -->|Same selector always| B[labelSelector]
    A -->|Different per version<br/>or per-tenant| C[matchLabelKeys]
    B --> D[Static, simple]
    C --> E[Dynamic, versioned]
Use labelSelector when…Use matchLabelKeys when…
The selector is fixed (e.g., app: web)You want per-version distribution during rollouts
Simple configurationsYou want per-tenant or per-customer isolation
Single-version deploymentsYou want to count only “current” Pods, not old ones

matchLabelKeys in detail

matchLabelKeys:
- pod-template-hash
- app.kubernetes.io/version

The constraint counts Pods whose values for these label keys match the new Pod’s values. With pod-template-hash, each ReplicaSet’s Pods are counted separately.

flowchart TB
    A["Old RS:<br/>pod-template-hash=abc"] --> B["Counted in<br/>distribution"]
    C["New RS:<br/>pod-template-hash=def"] --> D["Counted in<br/>distribution"]
    B --> E[Separate count]
    D --> E

Without matchLabelKeys, the constraint counts all Pods with app: web regardless of version. During a rollout, old and new Pods are counted together; the constraint applies to the union.

With matchLabelKeys: [pod-template-hash], each version’s distribution is independent. The new ReplicaSet’s Pods distribute across nodes without considering the old ReplicaSet’s Pods.

Production patterns

Pattern 1: per-version distribution during rollouts

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  matchLabelKeys:
  - pod-template-hash

During a rollout, each version’s Pods distribute across nodes. The old version stays where it was; the new version spreads to its own nodes.

flowchart TB
    subgraph "Old RS: web-v1"
      N1[node-01] --> P1[web-1]
      N2[node-02] --> P2[web-2]
      N3[node-03] --> P3[web-3]
    end
    subgraph "New RS: web-v2"
      N4[node-01] --> P4[web-4]
      N5[node-02] --> P5[web-5]
      N6[node-03] --> P6[web-6]
    end

Pattern 2: per-tenant isolation

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  matchLabelKeys:
  - tenant

Pods are grouped by tenant. Each tenant’s Pods distribute across nodes; different tenants may co-locate on the same node.

flowchart TB
    N1[node-01] --> P1A["Tenant A: pod-1"]
    N1 --> P1B["Tenant B: pod-1"]
    N2[node-02] --> P2A["Tenant A: pod-2"]
    N2 --> P2B["Tenant B: pod-2"]

Pattern 3: per-tier distribution

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  matchLabelKeys:
  - app.kubernetes.io/version

Each version’s Pods distribute across zones. During a rollout, the new version spreads to all zones; the old version stays put.

Combining with labelSelector

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web
  matchLabelKeys:
  - pod-template-hash

The Pods to count are those with app: web AND matching the new Pod’s pod-template-hash. Both conditions must hold.

flowchart TB
    A[Pod arrives] --> B{labelSelector:<br/>app=web?}
    B -->|yes| C{matchLabelKeys:<br/>same hash?}
    C -->|yes| D[Counted]
    C -->|no| E[Not counted]
    B -->|no| E
    D --> F[Skew check]

Failure modes

Failure 1: missing label key

matchLabelKeys:
- my-pod-template-hash   # typo

The Pods to count are those with my-pod-template-hash matching the new Pod’s. If the new Pod doesn’t have this label (because the typo means it’s never set), no Pods match. The constraint has no effect.

The fix: verify the label exists on Pods and matches exactly.

Failure 2: matchLabelKeys too narrow

matchLabelKeys:
- pod-template-hash
- app.kubernetes.io/version
- tenant

Three keys. The Pods to count must match all three on the new Pod. If any one is missing or different, the Pod is not counted. This may make the distribution too narrow.

The fix: include only the keys that define the grouping.

Failure 3: dynamic with static expected

A manifest designed for static labelSelector may behave unexpectedly with matchLabelKeys. The constraint is different: per-version counting vs total counting.

Quiz

Knowledge check · 4 questions

  1. Q1. What does matchLabelKeys enable in topology spread?

  2. Q2. matchLabelKeys is an alpha feature that should not be used in production.

  3. Q3. Your team uses topology spread with matchLabelKeys pod-template-hash. During a rollout, the new ReplicaSet's Pods cluster on the same nodes as the old ReplicaSet. Diagnose.

    Deployment has matchLabelKeys pod-template-hash. During a rollout, the new ReplicaSet's 5 Pods cluster on 2 nodes instead of spreading to 5 nodes.

  4. Q4. Explain how matchLabelKeys enables per-version distribution during rollouts.

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Use matchLabelKeys for rollouts. Each version’s Pods distribute independently; old and new do not interfere.
  • Use labelSelector for stable workloads. A workload with a fixed identity (e.g., a StatefulSet) uses static selectors.
  • Combine for filtering. labelSelector defines the broad set; matchLabelKeys defines the grouping.
  • Audit the resulting distribution. A dashboard that surfaces per-version Pod counts catches distribution failures.
  • Test with rollouts. A rollout that produces uneven distribution is a signal that the constraint is wrong.

matchLabelKeys enables dynamic grouping for production patterns. Operators who use it deliberately have rollouts that distribute predictably.