Skip to content

fix(chart): jobs-manager Recreate + image-refresh no-restart-storm (RWO-PVC prod incident) - #549

Merged
shujaatTracebloc merged 2 commits into
developfrom
fix/jobs-manager-recreate-strategy
Aug 3, 2026
Merged

fix(chart): jobs-manager Recreate + image-refresh no-restart-storm (RWO-PVC prod incident)#549
shujaatTracebloc merged 2 commits into
developfrom
fix/jobs-manager-recreate-strategy

Conversation

@shujaatTracebloc

@shujaatTraceblocshujaatTracebloc commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fix a production RWO-PVC deadlock: jobs-manager → Recreate

Incident: a client's hi-jobs-manager sat Pending for 4+ hours, blocking dataset ingestion. Root cause is entirely ours — the client's command and YAML were correct.

Root cause

jobs-manager mounts two ReadWriteOnce PVCs (client-pvc, client-logs-pvc; local-path, WaitForFirstConsumer) but used strategy: RollingUpdate with maxSurge: 1 — a second pod is created before the old one goes away.

  • Healthy single node: works (RWO permits two pods on the same node), so it ran for days.
  • After a routine WSL2/Docker restart the PVCs re-provision, and the rollout now presents two unscheduled consumers → WaitForFirstConsumer can't pick one → scheduler VolumeBinding PreBind times out (context deadline exceeded) → never schedules.
  • mysql already uses Recreate for the identical RWO-PVC reason; jobs-manager was an oversight.
  • image-refresh compounds it: it records its digest annotation only after rollout status succeeds — which now never happens — so it re-restarts every 15 min forever (19 revisions), starving mysql too. jobs-manager never Ready → Service has no endpoints → ingestor curl jobs-manager:8080 fails → ingestion post-hook fails.

Fix

  • jobs-managerstrategy: Recreate (drop maxSurge): exactly one RWO consumer at a time, so provisioning + restarts always converge, even after a node restart.
  • Regression guard:jobs_manager_test.yaml now asserts strategy.type == Recreate and no rollingUpdate block.
  • Chart 1.9.10 → 1.9.11 (content change must bump to reach installs).

Validation

  • helm unittest ./client -f tests/jobs_manager_test.yaml28/28 pass (incl. the new Recreate guard); helm template renders type: Recreate.
  • (4 unrelated suites fail on my local helm v4.2.0 — stricter values.schema.json than CI's pinned v3.15.4 — confirmed identical on clean develop, not from this change.)

Follow-ups (compounding factors, filed separately)

Closes#545


Note

Medium Risk
Changes rollout behavior for the core jobs-manager Deployment and image-refresh on every install; low blast radius for multi-node clusters but high operational impact where RWO + image-refresh loops were failing ingestion.

Overview
Fixes a production RWO-PVC deadlock by changing jobs-manager from RollingUpdate (with maxSurge: 1) to strategy: Recreate, so only one pod mounts the ReadWriteOnce client-pvc / client-logs-pvc at a time—matching mysql and avoiding two consumers wedging WaitForFirstConsumer volume binding on single-node local-path clusters.

The image-refresh script now skips a tick when kubectl rollout status does not succeed within 10s (rollout in progress or pod not Ready), preventing repeated rollout restart storms while the deployment is stuck.

Chart version 1.9.10 → 1.9.11. Helm unittest regression guards lock Recreate on jobs-manager and the “not settled … skipping” path in image-refresh.

Reviewed by Cursor Bugbot for commit b8ffc53. Bugbot is set up for automated code reviews on this repo. Configure here.

…C deadlock)
Production incident: a client's hi-jobs-manager sat Pending for 4+ hours, blocking dataset
ingestion. Root cause is entirely ours:
jobs-manager mounts two ReadWriteOnce PVCs (client-pvc, client-logs-pvc; local-path,
WaitForFirstConsumer) but used strategy RollingUpdate with maxSurge:1. maxSurge brings up
a SECOND pod before the old one is gone. On a healthy single node this happens to work
(RWO permits two pods on the same node), so it ran for days. After a routine WSL2/Docker
restart the PVCs must re-provision, and now the rollout presents TWO unscheduled consumers
to local-path — WaitForFirstConsumer can't pick one, so the scheduler's VolumeBinding
PreBind times out ("context deadline exceeded") and the pod never schedules. The
image-refresh CronJob then compounds it: it writes its "last-refreshed digest" annotation
only after `kubectl rollout status` SUCCEEDS, which now never happens, so every 15 min it
sees recorded != latest and restarts again — a self-perpetuating loop (19 revisions).
mysql-client starves on the same wedged provisioner. jobs-manager never goes Ready, so its
Service has no endpoints and the ingestor submit job's curl to jobs-manager:8080 fails.
Fix: strategy Recreate (drop maxSurge) — exactly one RWO consumer at a time, so
provisioning and restarts always converge, even after a node restart. This matches
mysql-deployment, which already uses Recreate for the identical RWO-PVC reason.
Regression guard: jobs_manager_test.yaml now asserts strategy.type == Recreate and no
rollingUpdate block, so this can't silently regress.
Chart 1.9.10 -> 1.9.11 (chart content change must bump the version to reach installs).
Follow-ups filed for the compounding factors: image-refresh restart loop, K8S version pin
reaching clients, and node-restart survival.
@shujaatTraceblocshujaatTracebloc self-assigned this Aug 3, 2026
@shujaatTracebloc
shujaatTracebloc marked this pull request as ready for review August 3, 2026 12:10
saadqbal
saadqbal previously approved these changes Aug 3, 2026
…d (no restart storm)
Second half of the RWO-deadlock incident (#546, folded in to avoid a chart-version
collision with the Recreate fix — same incident, same 1.9.11 bump).
image-refresh records its digest annotation only after `kubectl rollout status` succeeds.
When a rollout can't complete (the RWO deadlock, or any stall), the annotation never
lands, so every 15-min tick sees recorded != latest and restarts AGAIN — a sequential
restart storm (the incident showed 19 restarts). concurrencyPolicy: Forbid only stops
OVERLAPPING jobs, not this.
Fix: before touching the deployment, `kubectl rollout status --timeout=10s`; if it isn't
settled (rollout in progress, or pod stuck Pending on volume binding), log and exit 0 —
a restart can't help an unschedulable pod and only churns ReplicaSets, which on a
single-node local-path cluster wedges provisioning further. Retry on the next tick once
settled.
Regression guard in image_refresh_test.yaml asserts the skip is present in the rendered
script. (image_refresh_test's 2 pre-existing failures are local helm v4 schema strictness
— identical on clean develop; CI's pinned helm v3.15.4 passes the suite.)
Closes#546
@shujaatTraceblocshujaatTracebloc changed the title fix(chart): jobs-manager must use Recreate, not RollingUpdate (RWO-PVC deadlock, prod incident)fix(chart): jobs-manager Recreate + image-refresh no-restart-storm (RWO-PVC prod incident)Aug 3, 2026
@shujaatTracebloc

Copy link
Copy Markdown
ContributorAuthor

Folded in the #546 fix (image-refresh skip-when-unsettled) — same incident, and folding avoids a chart-version collision (both need the 1.9.10→1.9.11 bump). This PR now Closes #545 and #546:

  • jobs-manager → Recreate (the deadlock fix)
  • image-refresh skips a tick when the deployment isn't settled (stops the 19-restart storm that compounded it)

Both have helm-unittest regression guards. Only the primary incident (#545) + #546 remain in scope here; #547 (k3s drift warning) and #548 (node-local durability) stay separate — see below.

@shujaatTracebloc
shujaatTracebloc merged commit 1329e00 into developAug 3, 2026
21 checks passed
@shujaatTracebloc
shujaatTracebloc deleted the fix/jobs-manager-recreate-strategy branch August 3, 2026 12:33
@LukasWodka

Copy link
Copy Markdown
Contributor

/fr-pass

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@shujaatTracebloc@LukasWodka@saadqbal