Repository navigation
Gated Cilium rollout has stepped zero nodes in 10 days, and its gate has suppressed every Talos config sync and all ksail reconciliation since #2963
Description
🤖 Generated by the Agentic Engineer
Evidence
The gated Cilium homogeneous-device rollout has not stepped a single node, and the gate it
installed has been suppressing unrelated work for over a week.
Read live from prod on 2026-08-05 (read-only):
daemonset/ciliuminkube-system:updateStrategy=OnDelete,desired=9,ready=9,
updatedNumberScheduledabsent (0),generation=21,observedGeneration=21.- All 9 of 9 agent pods carry
controller-revision-hashof ControllerRevision 19
(created2026-07-25T22:59:29Z). Every pod's start time is2026-07-28and every pod has
0 restarts. - The DaemonSet's newest ControllerRevision is 24 (created
2026-08-03T08:09:07Z); 20
(2026-07-28T18:19:31Z) and 23 (2026-08-01T13:44:46Z) also went by.
So since revision 20 was cut on 2026-07-28 the fleet has been 0/9 on the current spec, and it
stays that way by construction: OnDelete only replaces an agent when something deletes its pod,
and nothing does. New revisions accumulate; none is ever adopted.
Why it matters beyond Cilium
.github/actions/deploy-prod/action.yml gates the 🔄 Update cluster (Talos machine config) step
on the rollout gate:
if: >-
steps.cilium_rollout_gate.outputs.active != 'true' &&
steps.wait_flux_revision.outcome == 'success'
and scripts/guard-cilium-homogeneous-device-rollout.sh keeps active=true for as long as the
homogeneous-devices component is referenced and its kustomization still says type: OnDelete.
Both remain true, so that step is skipped on every prod deploy. Measured on the most recent prod
deploy (merge-group, 2026-08-04T23:41:46Z, job green):
##[end-action id=__self.cilium_rollout_gate;outcome=success;...] Cilium ... gate active
##[end-action id=__self.cluster_update;outcome=skipped;conclusion=skipped;duration_ms=0]
That step is the pipeline's only Talos machine-config sync and its only ksail cluster update.
Consequences, all currently live:
- Talos machine config has not been synced to prod since the gate went active (2026-07-26).
- No
ksailcluster reconciliation reaches prod at all — the component reconcilers for CNI,
CSI, load balancer, cert-manager, policy engine, autoscaler, registry credentials and the GitOps
sync resource are all reached only from that command. - This is the concrete reason Cosign verify is configured but not enforced: the deploy path never applies it to the live OCIRepository #2922 / Root OCIRepository verification does not survive onto the live cluster #2938 cannot pass:
ensureVerifyIfConfiguredruns inside
SetupInstance, which on the update path is reached only from thecluster.workload.tagand
cluster.workload.flux.distributionVersionreconcilers — none of which execute while the command
itself never runs. The live rootocirepository/flux-systemstill hasspec.verifyabsent. - Cluster Autoscaler is held suspended by the gate for the same duration.
#2951/#2962 made this suppression visible (a job-summary warning) and bounded the reporting. They
did not address the rollout being stalled, which is what keeps the gate active.
Expected behaviour
The gated rollout either completes or is rolled back, within the window it was designed for, and the
deploy pipeline does not silently lose its only machine-config sync while it waits.
Proposed direction
- Step the 9 agents onto the current revision, one node at a time with verification between steps —
the procedureOnDeletewas chosen to allow — then remove the gate by taking the component off
OnDelete.require_cilium_fleet_currentalready refuses removal until
observed==generation,scheduled==desired,updated==desired,pods==desired,ready==desired,
so gate removal stays fail-closed and needs no change. - Or, if the widened device set is no longer wanted, roll the component back and remove the gate.
- Add a forcing function so an open-ended gate cannot recur: the gate should fail the deploy — not
only warn — once it has been active beyond its intended window, so a stall surfaces as breakage
instead of a green deploy that quietly skips its own config sync.
Acceptance criteria
-
daemonset/ciliumreportsupdatedNumberScheduled == desiredNumberScheduled == numberReady,
verified by reading it back from the live cluster. - No agent pod remains on a superseded ControllerRevision.
- A subsequent prod deploy shows
cluster_updatewithoutcome=success, notskipped. - Cluster Autoscaler is restored to its pre-gate replica count by the gate's own restore path.
- A gate that stays active past its intended window fails a deploy rather than warning inside a
green one.
Size
Medium — the stepping itself is an operator action against prod networking (deliberately not
automated here); the forcing function is a small workflow change.
Blocks #2922 and #2938. Related: #2610, #2792 (activation), #2951, #2962.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Projects
- StatusShow more project fields✅ Done
Activity
coderabbitai commentedon Aug 5, 2026
🔗 Related PRs
#2633 - fix(deploy): verify incoming image on Talos nodes [merged]
#2793 - feat(cilium): activate homogeneous device selection [merged]
#2927 - fix(deploy): await pre-handoff kustomize-controller pod termination [merged]
#2930 - feat(ci): verify published evidence before promoting the prod digest [merged]
#2947 - fix(ci): refuse to promote a prod digest whose evidence cannot be verified [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
devantler commentedon Aug 5, 2026
Still active today — re-verified live, not inherited from an earlier note
Checked against current state rather than trusting the existing "gated" record, since a closed blocker is not a released one.
The suppression is unchanged. On the most recent production deploy (the
merge_groupCI run of 2026-08-04 23:41Z, job🚀 Deploy to Prod, conclusion success), the step outcomes are:The job is green and its most important step did not run — so job conclusion is not a usable signal here; the per-step outcome is.
The rollout has still stepped nothing. The DaemonSet remains on
OnDeletewith its pods several revisions behind the current spec, so no deploy will ever advance it — only a deliberate operator step or a rollback will.What has changed since this issue was filed — the risk argument has flipped
The reason for not simply enabling root artifact verification was that verification against an unsigned artifact would stop the root source reconciling. That is no longer the state. In the same deploy, the whole publication chain is green and in the right order:
verify_evidencesucceeds beforepromote_latest, which is exactly what #2859 and #2849 set out to guarantee. #2627's publication half is therefore done — 9 of its 10 children are closed, and the only open one is #2922.So the standing blocker is no longer a supply-chain gap. It is this gate:
cluster_updateis the step that would applyspec.verifyto the live rootOCIRepository, and it has not run. Live prod still readsspec.verify: null.Why this is not something to fix in the workflow
The gate is deliberate and correct — it exists because KSail owns Cluster Autoscaler and could reconcile it during the command, opening a scale-up race before the first canary is verified. Removing or weakening it to unblock the security control would trade one control for another, so I have not proposed that.
The unblock is the operator action this issue already names: step the rollout, or roll the component back. That is a judgement call on a live cluster and a mutation outside my authority, so it stays with the maintainer.
Net effect
One operator action now gates a security control that is otherwise ready to land. Blocking chain: this issue → #2922 → #2938, and #2627 closes behind them.
Detailed cluster-side evidence is kept in the private operator notes rather than here.
fix(ci): escalate the Cilium gate suppression from warning to failure
devantler commentedon Aug 6, 2026
Re-measured on 2026-08-06 (read-only). The issue still holds, and three things have changed since it was written that make the remediation riskier than it was — worth recording before anyone releases the gate.
1. The stalled fleet is running a release candidate, not the declared GA build.
Every agent pod still has a
2026-07-28start time. So the practical consequence of "0/9 on the current spec" is concrete: production has been serving onv1.20.0-rc.0while Git has declared GAv1.20.0since 2026-08-05T17:32 (#2981) — a bump that merged after this issue was filed.generation/observedGenerationare now 24/24 (they were 21/21 here).2. The HelmRelease reports success, which makes this invisible from the Flux surface.
Helm genuinely did succeed — it rewrote the DaemonSet template.
OnDeletethen declined to act on it. So the one surface an operator would check to answer "did the CNI upgrade land?" says yes while no node moved. Same shape as the green deploy whosecluster_updateisskipped duration_ms=0: the status reports the step that ran, not the outcome that didn't.3. This raises the cost of the obvious fix. Releasing the gate means dropping
type: OnDeletefromcilium/components/homogeneous-devices/, which is what the guard keys on. Doing that now would let the DaemonSet replace all 9 agents at once, onto a build that has never been canaried on a single node — the exact outcome the gate was installed to prevent. The longer it holds, the larger the pending step:rc.0 → GAtoday, plus whatever lands next.So "finish or roll back the rollout" is no longer symmetric with "flip the strategy back". Concluding it deliberately — step one node on
v1.20.0, verify the datapath, then proceed — looks like the only safe direction, and the alternative is an explicit rollback to a build matching what is actually running.Gate bookkeeping is intact, for whoever does conclude it:
deployment/cluster-autoscaler-hetzner-cluster-autoscalercarriescilium-device-rollout-previous-replicas=1andcilium-device-rollout-approved-template-sha=94910ca6..., and currently sits at 0 replicas. The release path can restore it to 1.I could not check whether that approved SHA still matches the current rendered template — the guard derives the candidate from
kubectl kustomizeplus the three Flux substitution sources, one of which is a Secret I do not have access to. Given the v1.20.0 bump landed after the approval, assume it does not match until that is computed.devantler commentedon Aug 6, 2026
New consequence: this now breaks the merge queue, not only Talos sync
Day 11. The gate's cost is no longer confined to suppressed machine-config sync — it is now
evicting pull requests from the merge queue, and the error it surfaces names the wrong subsystem.
CIrun 31110692734 (mergegroup for #3001) failed, and so did its
🩹 Heal Prodjob, which re-deploysmain. Both failed atthe same step —
🔑 Stage Flux and consumer GHCR pull credential— with:Nothing about that message points here. The actual chain, read live from prod (read-only, today):
daemonset/cilium:updateStrategy=OnDelete,desired=9,ready=9,updatedNumberScheduledabsent (0),generation=24/observedGeneration=24.Template image is
v1.20.0; all 9 running pods arev1.20.0-rc.0.Kustomizationhealth check on a DaemonSet requiresupdatedNumberScheduledto reachdesiredNumberScheduled. UnderOnDeletewith no node stepped, that value cannot ever rise,so the check cannot converge — it can only burn its timeout.
kustomization/infrastructure-controllersis therefore stuckReconciling=True / Progressing— "Running health checks … with a timeout of 25m0s".kustomization/infrastructureis consequentlyReady=False,DependencyNotReady: dependency 'flux-system/infrastructure-controllers' is not ready, and itselfReconciling=True(generation=721,observedGeneration=719).refresh-flux-ghcr-auth.sh'spause_flux_policy_handoff()requires thatinfrastructure(
IMAGE_VERIFICATION_FLUX_KUSTOMIZATION) be quiescent — noReconciling=True— before it willtake the handoff. It retries
60 × 2sand then emits the message above.So the deploy bridge is reporting a symptom two layers downstream of the un-stepped CNI.
Why it looks intermittent
The 11 merge-group deploys before this one succeeded today. The health-check windows are bounded
(25m for
infrastructure-controllers, 20m forinfrastructure) and are followed by a brieffailure-and-retry gap, so a deploy that lands inside a window fails and one that lands in a gap
passes. That makes this a recurring systemic failure with a random-looking arrival, not a
transient — re-queuing does not fix it, it just re-rolls the dice. Per the merge-queue rule in
AGENTS.md, #3001 has not been re-queued.Impact
🩹 Heal Prodfails the same way, so the automatic restore-mainpath is also unavailablewhile a window is open. In this run publish was skipped, so no speculative artifact reached the
mutable tag and prod stayed on its previous revision — the safe direction, but by luck of
ordering rather than by design.
Ask
Finishing or rolling back the rollout is a cluster mutation (stepping the 9 agent pods), which is
outside this engineer's authority — it needs a maintainer. Until then, expect further evictions.
Two follow-ups are worth separating from that decision, and both are in-authority engineering:
check, instead of reporting only the image-verification handoff. Tracing this took a live
four-resource walk that the log could have short-circuited.
the window is being outrun in a way that damages unrelated delivery.
devantler commentedon Aug 6, 2026
The un-adopted delta is no longer just this component's fields — rollback is no longer free
Re-verified live on 2026-08-06 (read-only). Two things changed since this issue was filed, and
together they invalidate the rollback shortcut the component's own runbook offers.
1. Drift is widening, not static
The issue recorded the newest
ControllerRevisionas 24 (2026-08-03). It is now 27(
2026-08-05T20:45:20Z). The fleet is still entirely on revision 19 (2026-07-25), so a newrevision has been cut since this issue was opened and adopted by nothing.
2. The delta now contains more than the device-set change
Diffing the running template (rev 19) against the desired one (rev 27) shows the un-adopted change
is not limited to this component's
devices/nodePort/updateStrategypatches:v1.20.0-rc.0→v1.20.0. Verified against the running pods, not just therevision object: all 9/9 agents report the
-rc.0image, whiledaemonset/cilium's desiredspec carries the GA digest. So the fleet is held on a pre-release build of the CNI purely
because
OnDeleteprevents any pod from adopting the newer template.hostPathvolume and its container mount were removed in the desiredtemplate and are still present on every running agent.
Why this matters for the decision
The component's ROLLBACK note says re-commenting the reference rolls nothing, because no node has
been stepped. That is no longer true. Removing the component also restores
RollingUpdatealongside
rollOutCiliumPods: true, and the template the fleet would then roll to is rev-27-derived— so the "rollback" would perform a fleet-wide CNI image upgrade and a mutual-auth volume change
as a side effect, on all nine nodes, unattended.
This is exactly the hazard step 3 of the runbook anticipates ("If ANY desired Cilium pod template
value changes while this gate is active … re-step and re-verify every stale node"). It has now
happened, twice.
Consequence
Both available paths are now operator-stepped work in a low-traffic window, not a one-line change:
device-set change and the image upgrade and the volume removal simultaneously, so the per-node
verification must cover all three, and a canary result from before rev 20 approves nothing.
and sequence it accordingly, rather than as the no-op the note describes.
I have not taken either path: both require cluster mutation, which is maintainer-only.
Blast radius while it stays open: the gate continues to skip the pipeline's only Talos
machine-config sync and its only
ksail cluster update, so #2922 / #2938 / #2627 remainstructurally unpassable, and platform#3001 (pentad-clear, promoted) cannot clear the merge queue —
its
merge_groupCI fails on the resulting Flux non-convergence, which is #3002.devantler commentedon Aug 6, 2026
The stall has changed character since this was filed — it is now swallowing the CNI GA upgrade
Re-read live from prod today (read-only, 2026-08-06 ~17:2xZ). The mechanism is unchanged, but what
is queued behind it is not.
Running fleet — unchanged since 2026-07-28:
controller-revision-hash7f7c864994= ControllerRevision 19(created
2026-07-25T22:59:29Z, imagequay.io/cilium/cilium:v1.20.0-rc.0).2026-07-28; 0 restarts.daemonset/cilium:updateStrategy=OnDelete,desired=9,ready=9,available=9,updatedNumberScheduledabsent (0),generation=24,observedGeneration=24.Desired state — moved three more times, and crossed a release boundary:
v1.20.0-rc.0← what all 9 nodes actually runv1.20.0-rc.0v1.20.0-rc.1v1.20.0-rc.1v1.20.0(GA) ← current desiredThe DaemonSet's pod template today names
v1.20.0GA on the agent container and all five initcontainers. Revision 27 landed ~16 hours after this issue was filed, so the framing here — a
stalled device-selection rollout — now understates it:
production has run a release-candidate CNI (
rc.0) for 12 days, andrc.1,rc.1and thev1.20.0GA release have each queued unadopted behind the sameOnDeletegate.Why this changes the priority, not just the numbers
GA release of the CNI. Every fix that shipped between
rc.0and GA is declared-but-not-running.updatedNumberScheduledis 0/9 by construction underOnDelete, soeach new upstream release widens the gap rather than closing it. Four revisions have accumulated;
nothing in the system will ever adopt one.
flux-system/infrastructure-controllersisReady=Unknown reason=Progressing— "Running health checks … with a timeout of 25m0s" — withwait: trueandhealthChecks: null, so Flux waits on kstatus for every applied resource, andthe DaemonSet can never satisfy
updated == desired. That cascades:infrastructure→DependencyNotReady,apps→DependencyNotReady.lastAppliedRevisionandlastAttemptedRevisionhave diverged.So the consequences listed in the original body are not just still live — the set of things
declared-but-not-running now includes the CNI version itself.
Unchanged: the stepping is an operator action
Nothing here proposes automating it. Stepping 9 agents through a CNI upgrade against prod networking
is deliberately a human action, and this run holds read-only cluster access by design. The two halves
remain:
the component off
OnDelete.require_cilium_fleet_currentalready fail-closes gate removal.inside a green one — so the next stall surfaces in hours, not twelve days.
Correction to my own earlier evidence
The original body recorded the newest ControllerRevision as 24 created
2026-08-03T08:09:07Z.Reading the same object today, that timestamp belongs to revision 26. The timestamps and the
conclusion are unaffected; only that ordinal was wrong.
devantler commentedon Sep 19, 2026
Closing: the rollout completed and the gate is gone
Live read from prod, 2026-09-19 (read-only):
daemonset/ciliuminkube-system:updateStrategy=RollingUpdate(no longerOnDelete),desired=8,updated=8,ready=8.quay.io/cilium/cilium:v1.20.1@sha256:ae9ea21f…. That's past thev1.20.0GA this issue found stuck behind the gate.Both halves of the problem are resolved. The stalled rollout finished, and the "gate suppresses work without limit" risk now has a guard: the rollout-gate check in
deploy-prodreads as active only while thehomogeneous-devicescomponent is referenced withtype: OnDelete.scripts/report-cilium-rollout-gate-suppression.shthen warns after 7 days and fails the deploy after 14, measured from the gate's activation marker. So a future gated rollout can't silently stall for weeks.