Chapter 39 of 47 · More workload types

Singletons and disruptions

Usually more copies are better. But some jobs must be done by exactly one copy at a time: send the daily newsletter once, not three times. And the opposite worry: when you take a node down for maintenance, you want enough copies to keep running. This chapter covers both: singletons, and PodDisruptionBudgets.

When "only one" matters

Imagine the posts service gets a new feature: every night at 08:00 it emails "today's top posts" to all users. With 3 replicas, three Pods wake up at 08:00 and every user gets three emails. Other examples:

There are two ways to guarantee one active copy: from the outside (Kubernetes runs only one) or from the inside (many run, but they agree on one boss).

Two ways to get a single active copyLeft: Kubernetes runs only one Pod. Right: three Pods run, one holds a lock and is active, the other two wait on standby. Outside: only one Pod exists replicas: 1 newsletter simple; a short gap while it is replaced Inside: leader election Lease (the lock) posts-aleader posts-bstandby posts-cstandby a standby takes over within seconds

Option 1: let Kubernetes keep it at one

The easiest fix: move the nightly email into its own small workload with one replica. Kubernetes keeps exactly one running and replaces it if it crashes. Your app code stays simple.

If the task runs on a schedule, an even better fit is a CronJob, which starts one Pod at 08:00 and lets it finish (chapter 36).

The replicas: 1 trap

"One replica" sounds like "never two". It is not, with a normal Deployment. Pick an update strategy and see:

08:00:00  newsletter-old   Running
08:00:01  newsletter-new   Starting      ← rollout begins (maxSurge)
08:00:05  newsletter-new   Running
          newsletter-old   Running       ← two copies at once!
08:00:06  newsletter-old   Terminating

A rolling update starts the new Pod before stopping the old one (chapter 10). For a few seconds, two copies run. If that is when the email goes out, people get it twice.

Option 2: leader election inside the app

Sometimes you want several copies running, so a standby can take over in seconds, but only one may act. They hold an election using a shared lock. In Kubernetes that lock is a small object called a Lease. It is like a library book: whoever has it borrowed is the leader, and the loan must be renewed regularly or someone else can take it.

  1. posts-a: try to take Lease "newsletter-leader" → success, I am leader
    posts-b: try → already held by posts-a, wait
    posts-c: try → already held by posts-a, wait

    All three start. Only the first to grab the Lease becomes leader.

  2. apiVersion: coordination.k8s.io/v1
    kind: Lease
    metadata:
      name: newsletter-leader
    spec:
      holderIdentity: posts-a
      leaseDurationSeconds: 15
      renewTime: "2026-10-20T08:00:10Z"

    The Lease records who holds it and when they last renewed. The leader renews every few seconds.

  3. posts-a: crashes ❌ (stops renewing)
    ...15 seconds pass...
    posts-b: Lease expired → take it → I am leader

    The leader died, so the renewals stopped. After the lease time, a standby takes over and sends the 08:00 email.

  4. // posts/index.js (sketch)
    elector.on('leader', () => startNightlyEmailJob());
    elector.on('follower', () => stopNightlyEmailJob());

    In code you only answer two questions: what to do when I become leader, and what to stop when I lose it. Go has a ready-made package for this (client-go's leaderelection); in other languages use a small library, or a sidecar that does the election for you.

Kubernetes uses this itself: only one controller manager and one scheduler are active at a time, even when three control-plane nodes run them (chapter 44).

Outside (replicas: 1)Inside (leader election)
Code changesNoneElection logic or a library
Takeover timeA new Pod must start (seconds to minutes)A standby is already running (about one lease period)
Good forMost background jobsWhen the gap must be short

PodDisruptionBudget: keep enough running

Now the opposite problem. You have 3 posts Pods. An admin drains nodes to upgrade them. If all three posts Pods are evicted at once, your site is down. A PodDisruptionBudget (PDB) says how many may be missing at the same time because of planned work.

minAvailable

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: posts-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels: { app: posts }

"Always keep at least 2 posts Pods running."

maxUnavailable

spec:
  maxUnavailable: 1
  selector:
    matchLabels: { app: posts }

"Never take away more than 1 at a time." Often better: it keeps working when you scale up or down. Percentages like "25%" also work.

A PDB protects againstA PDB does not protect against
Voluntary disruptions that go through the eviction API: kubectl drain, node upgrades, the cluster autoscaler removing a nodeInvoluntary ones: a node crashing, a power cut, a container running out of memory, you running kubectl delete pod

Try it: drain a node

Three posts Pods, one per node. Pick a budget and drain a node. With minAvailable: 2, the drain waits until a replacement is Ready before evicting the next Pod. Then try minAvailable: 3.

Do not lock yourself in. minAvailable equal to the replica count (or maxUnavailable: 0) means "never evict anything". Node drains then hang forever and admins cannot upgrade the cluster. A PDB for a single-replica app has the same effect. Leave room for at least one disruption.
$ kubectl get pdb
NAME        MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
posts-pdb   2               N/A               1                     3d

ALLOWED DISRUPTIONS is the number to watch: 0 means a drain will wait.

Which tool?

Recap

Quick check

The newsletter Deployment has replicas: 1, yet after one deploy some users got the email twice. What happened?
Show answer

A rolling update started the new Pod before stopping the old one, so for a moment two copies ran, both at 08:00. Use strategy: Recreate, a StatefulSet, or a CronJob.

Singleton services and PodDisruptionBudgets: common questions

Can a Deployment with replicas 1 run two Pods?

Yes, briefly. A rolling update starts the new Pod before stopping the old one. Use strategy Recreate or a StatefulSet if two copies must never run together.

Does a PodDisruptionBudget protect against node crashes?

No. It only limits voluntary disruptions that use the eviction API, such as kubectl drain, node upgrades and the cluster autoscaler.