kernel-roll: proven green, runtime probe, pre-stage, infra-only gating, zot-aware ordering #2044

Merged
pipeline-bot merged 5 commits from fix/kernel-roll-observability into trunk 2026-08-14 16:52:55 +00:00
Contributor

Rounds 1+2 of the roll speed/observability work, all measured against the 2026-08-14 seanfarm32 roll (85m37s):

  • green-gate gains an ACTIVE runtime probe on the just-rolled node, and cannot return its first green until one has succeeded (the seanfarm31 wedge appeared ~40min post-green; passive signals stayed green throughout).
  • Fleet pre-stages images during the canary+gate window (stage-only exits above the cordon; safety-envelope-asserted).
  • Flux gating partitioned: infra blocks, build/CI Kustomizations may opt out via label — logged, never silent; default stays blocking. 25 of the canary gate 29 minutes were shipwright-kernel-sign.
  • plan never picks zot host as canary and rolls it last (bootc pulls through zot via podman stack; Spegel does not cover it; zot host deterministically sorted first).
  • awaitingGate/awaitingSince/Progress projected onto PipelineDeploy (CRD + operator + unit tests) — the 14m unnoticed gate becomes visible to every API reader.
  • verify-source-backup retries its whole predicate (mirror-delivery race); green poll 20s→10s.

Safety envelope extended with negative-control-verified assertions for the stage-only/cordon ordering, the label partition, the forced probe, and zot ordering. 130/130 guards green.

Rounds 1+2 of the roll speed/observability work, all measured against the 2026-08-14 seanfarm32 roll (85m37s): - green-gate gains an ACTIVE runtime probe on the just-rolled node, and cannot return its first green until one has succeeded (the seanfarm31 wedge appeared ~40min post-green; passive signals stayed green throughout). - Fleet pre-stages images during the canary+gate window (stage-only exits above the cordon; safety-envelope-asserted). - Flux gating partitioned: infra blocks, build/CI Kustomizations may opt out via label — logged, never silent; default stays blocking. 25 of the canary gate 29 minutes were shipwright-kernel-sign. - plan never picks zot host as canary and rolls it last (bootc pulls through zot via podman stack; Spegel does not cover it; zot host deterministically sorted first). - awaitingGate/awaitingSince/Progress projected onto PipelineDeploy (CRD + operator + unit tests) — the 14m unnoticed gate becomes visible to every API reader. - verify-source-backup retries its whole predicate (mirror-delivery race); green poll 20s→10s. Safety envelope extended with negative-control-verified assertions for the stage-only/cordon ordering, the label partition, the forced probe, and zot ordering. 130/130 guards green.
perf+safety(kernel-roll): prove the runtime, and pre-stage during the idle window
All checks were successful
pipeline/ci CI green @ 114096a7c067
114096a7c0
Measured on the 2026-08-14 seanfarm32 roll (85m37s total): 29m canary green,
14m human gate, 26m across five serialized roll-nodes, 13m of inter-node green.

PROVE THE RUNTIME (safety). green-gate only ever READ cluster state and exits
the instant everything is green — there is no dwell. The failure that matters
most is invisible that way: on 2026-08-14 a worker booted seanfarm31, passed
its on-node verify, was uncordoned and went green, then ~40 minutes later its
containerd stopped creating containers. kubelet still reported Ready and the
node kept its existing pods, so every passive signal stayed green while 34 pods
sat unable to start. A human noticing was the only thing that caught it.

green-gate now takes probeNode (the node just rolled) and PROVES it:
  passive, every cycle — pods on that node stuck in CreateContainerError or
    ContainerStatusUnknown are the wedge's fingerprint, one list call
  active, every 120s — schedule a throwaway pod ON that node and require it to
    run. This is the test that settled the seanfarm32 canary by hand; a wedged
    runtime fails it in seconds, an idle healthy node passes.

PRE-STAGE (speed). roll-node gains stageOnly, which stops after the image stage
and BEFORE the cordon. The fleet now pre-pulls in parallel with the canary roll
and its gate — time the roll already spends idle — so each serialized node
later starts at the cordon rather than at a fresh pull.

NOT DONE, deliberately: rolling two nodes at once. roll-one holds the
kernel-deploy-node mutex and tests/kernel-deploy-safety-envelope.sh asserts it,
because 5-replica quorum tolerating 2 down leaves NO margin — and today proved
a node can brick mid-roll. Two down plus one bricked is a lost quorum. The ~10
minutes are not worth trading that margin away.

The safety envelope now asserts the stage-only exit sits ABOVE the cordon, so a
fanned-out pre-stage can never disrupt many workers at once. Negative control
verified: moving the exit below the cordon fails the test.
fix+perf(kernel-roll): proven green, infra-only gating, zot-aware ordering
All checks were successful
pipeline/ci CI green @ 8a36ad748071
8a36ad7480
Three changes to the roll's decision core, each anchored to the measured
2026-08-14 roll and each asserted (with negative controls) in the safety
envelope.

PROVEN GREEN. The active runtime probe from the previous commit never fired
when the gate went green inside its 120s throttle — true on 2 of 5 measured
nodes — so the gate could pass on inference, the exact thing the probe forbids.
green_check now refuses its FIRST green until one active probe has succeeded,
forcing it at the only moment it matters; the throttle still paces probing
while other predicates hold the gate red.

INFRA GATES, CI REPORTS. green_check blocked on every Flux Kustomization in
the cluster; 25 of the canary's 29 minutes were shipwright-kernel-sign — a
build pipeline with nothing to say about fleet health. A Kustomization may now
opt out of gating with kernel-roll.seanfarm.io/gates="false": still logged
(never silent), never blocking. The default stays blocking, so new
infrastructure is protected by omission; seanfarm's exemption guard pins which
Kustomizations may carry the label.

ZOT ROLLS LAST. bootc pulls node-bootc through registry.sean.farm via podman's
stack — Spegel only mirrors containerd — so draining zot's single-replica host
breaks every other node's stage. Workers sort by name, zot sat on the first
one, so the default canary drained the registry EVERY time (today's cosign
503s, and a silent brake on the prestage fan-out). plan now resolves zot's
node fail-open, never picks it as default canary, and orders it last.

Also: green poll 20s->10s (check costs ~2s; ~10s average overshoot per gate
exit measured), and verify-source-backup retries its WHOLE predicate 3x20s —
codeberg is mirror-delivered now, so a deploy declared seconds after a release
can see refs not yet advanced.
Roll timings used to be forensics — the 2026-08-14 roll's workflow CRs were
TTL'd before anyone asked, and its timeline had to be dug out of Loki. And the
awaitingGate/Progress status fields landed write-only: nothing rendered them.

Metrics (operator, scrape path already live):
  pipelines_deploy_awaiting_gate{project,target,gate}   1 while blocked
  pipelines_deploy_gate_wait_seconds                    observed when a gate clears
  pipelines_deploy_progress_completed/_total            fleet roll position
Gauges are deleted at terminal so a finished roll cannot linger as a
stuck-looking series; the gate-wait histogram observes on the prev->cleared
edge using the AwaitingSince the operator already holds.

Surfaces (Sean's chosen channel is the deploy fold, not an external push):
  query DeployLeg gains awaitingGate/awaitingSince/progress;
  the dashboard deploy row shows an amber 'waiting on canary gate 14m' chip
  and '3/5 · <node> (<phase>)' beside the status dot;
  pipectl status prints AWAITING <GATE> <N>m with the exact approve command,
  and the fleet position.

Also: a PodDisruptionBudget for zot. The roll handles zot by ordering (its
drain bypasses PDBs anyway); this refuses everything ELSE that respects
budgets — a manual drain of the registry's host now says no instead of
silently taking image pulls down fleet-wide.
pipelines_deploy_roll_seconds{project,target,step,node}: each node's time in
each roll step (roll-node, green), extracted from the finished run's node
graph by argoengine.RollDurations — the same walk RollProgress uses, now with
timestamps. Observed exactly once, on the non-terminal->terminal edge, so a
re-reconcile of a finished deploy cannot double-count.

These are the numbers the 2026-08-14 analysis had to dig out of Loki before
log retention ate them; the next roll's shape will be a Grafana panel
instead of an archaeology session.
feat(deploy): the awaitingGate/awaitingSince/progress status fields themselves
All checks were successful
pipeline/ci CI green @ 9fcf4a6f6f7a
9fcf4a6f6f
The foundation the last three commits build on — CRD type, deepcopy, and
generated manifest. Local builds passed on working-tree state while CI cloned
the branch without these files: explicit-path staging missed the foundation.
AwaitingGate names the human gate a running deploy is blocked on (enum
canary|approval), AwaitingSince timestamps it, and DeploymentProgress carries
completed/total/current/phase for a multi-node roll — all operator-authored,
step names only, never log content.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/pipelines!2044
No description provided.