01 · Know the cluster
Three zones, all spot.
The cluster-state scan records the shape of each cluster: how many
availability zones it spans, whether it has spot nodes, and whether
it runs only on spot. It reads node labels and nothing else.
Here: three zones, four spot nodes. checkout-api runs
two replicas, but both landed on the same node.
payments-operator and orders-worker run a
single replica each, on that same node.
02 · The node gets taken back
One preemption, three outages.
The provider reclaims node a-1 with a few seconds of
notice. Everything on it stops at once.
checkout-api had two replicas and still went fully
dark: replicas that share a node share its fate. Until the scheduler
finds room and the new pods pass their readiness checks, nothing
serves checkout.
03 · Find the weak spots, before it happens
The rule pack reads the manifests that allowed it.
The same gaps are visible in git long before any node dies. The
availability rules walk every workload daily and check it against
the cluster it runs on:
- multi-replica, but nothing spreads the replicas over nodes and zones,
- multi-replica, but no PodDisruptionBudget,
- a controller-manager running as a single replica,
- a grace period longer than spot allows.
04 · No model in the loop
Deterministic rules, not a guess.
This agent does not call a language model at all. Each rule is a
plain check with a fixed fix, so a finding can be reproduced and
explained line by line, and it costs nothing to run every day.
Rules that need topology skip themselves when the zone count is
unknown, rather than guessing. When no clean diff can be written,
the finding becomes an issue instead of a risky MR.
05 · Spread the replicas
One replica per node, one per zone where possible.
On a multi-zone cluster, a multi-replica workload without
topologySpreadConstraints gets two: one keyed on the node,
one on the zone, each with maxSkew: 1 and the
workload's own selector labels.
Both are ScheduleAnyway: a best-effort spread that can
never leave a pod Pending when capacity is short. You can tighten it
to DoNotSchedule if you have guaranteed room.
06 · Budget the disruptions
Drains and upgrades take one pod at a time.
A multi-replica workload with no PodDisruptionBudget gets one, in a
new file next to the workload, wired into the directory's
kustomization.
To be precise about what that buys: a PDB governs voluntary
evictions, such as node drains, pool upgrades and autoscaler
scale-downs. A spot preemption does not ask permission. That is
what the spread in step 05 is for. The two cover different failures,
so you get both.
07 · Controllers and grace periods
Leader-elected controllers get standbys. Grace fits spot.
A controller-manager running alone gets a proposal for 3
replicas: leader election means only one works at a time, the
others take over when its node dies. It ships as a "judgment call"
draft, and it is skipped for controllers whose standbys cannot pass
readiness.
On a spot-only cluster, a terminationGracePeriodSeconds
above 30 is trimmed to 30: spot never waits longer, and a long grace
also slows every voluntary drain. Trimming further, to 15 s when
every live pod is provably on spot, is
In development.
08 · The same preemption, after merge
Node a-1 goes. Checkout keeps serving.
With the replicas spread, losing a-1 costs one
checkout-api replica, not the service. The operator's
standby in another zone takes the lease. The lost pods are
rescheduled in the background.
orders-worker still has one replica, so it still
blips. Proposing extra replicas for ordinary single-replica apps,
with evidence that the app tolerates it, is
Roadmap: some singletons are singletons
on purpose.