Skip to content

feat(k8s): graceful-lifecycle hardening -- startupProbe, preStop, drain-readiness (P0-2/3/4/5)#745

Merged
ELares merged 2 commits into
mainfrom
feat/k8s-p0-graceful-lifecycle
Jul 24, 2026
Merged

feat(k8s): graceful-lifecycle hardening -- startupProbe, preStop, drain-readiness (P0-2/3/4/5)#745
ELares merged 2 commits into
mainfrom
feat/k8s-p0-graceful-lifecycle

Conversation

@ELares

@ELares ELares commented Jul 24, 2026

Copy link
Copy Markdown
Owner

The remaining k8s-readiness P0 fixes (after #743's OOM guard). Plan: deploy/K8S_READINESS_PLAN.md.

Changes

  • P0-5 (server, metrics_http.rs + main.rs): /readyz returns 503 the moment graceful shutdown begins, so k8s deprograms this pod's endpoint before the drain -- no client routed to a draining node. MetricsState gains an optional shutdown flag (set.shutdown_flag(), the bug: graceful shutdown can hang joining a shard with a parked subscriber connection (flaky keyspace_notifications teardown) #543 Arc<AtomicBool>); readiness() checks it first. Unit-tested (ready -> drain flips /readyz 200 -> 503 draining).
  • P0-2 (chart): startupProbe on /readyz suspends liveness+readiness until load-on-boot completes -- a slow multi-GB snapshot RELOAD no longer trips the ~65s liveness budget and CrashLoopBackOffs a healthy node. failureThreshold*periodSeconds (default 60*10 = 10 min) is a values knob. C3 verified: the metrics listener binds while load-on-boot runs in the shard tasks, so /readyz reports load-incomplete during the reload.
  • P0-3 (chart): preStop lame-duck via the native SleepAction (k8s 1.29+) -- NOT exec sleep, which would fail on the shell-free distroless image. Gated on preStop.sleepSeconds > 0 (0 disables it on older k8s -> rely on P0-5). kubeconform -strict @ KUBE_VERSION 1.30 accepts it (SleepAction GA).
  • P0-4 (chart): terminationGracePeriodSeconds documented to cover preStop + drain + save-on-exit, sized from the same dataset estimate as the startupProbe budget.

Applied to BOTH the Helm chart (values-tunable) and deploy/k8s/ironcache.yaml (static, parity).

Tests

/readyz-drain unit test + all metrics (17) & shutdown (9) tests green, clippy -D warnings, fmt, dash clean. deploy-lint (helm template | kubeconform -strict @ 1.30) exercises the rendered chart.

🤖 Generated with Claude Code

ELares and others added 2 commits July 23, 2026 20:32
…in-readiness (P0-2/3/4/5)

The remaining k8s-readiness P0 fixes (plan in deploy/K8S_READINESS_PLAN.md):

- P0-5 (server): /readyz returns 503 the moment graceful shutdown begins, so k8s
  deprograms this pod's Service endpoint BEFORE the drain -- no client routed to a
  draining node. MetricsState gains an optional shutdown flag (from set.shutdown_flag(),
  the #543 Arc<AtomicBool>); readiness() checks it FIRST. Unit-tested (ready -> drain
  flips /readyz 200 -> 503 draining). Belt-and-suspenders with the preStop below, and
  the fallback where SleepAction is unavailable.

- P0-2 (chart): startupProbe on /readyz suspends liveness+readiness until load-on-boot
  completes, so a slow multi-GB snapshot RELOAD does not trip the ~65s liveness budget
  and CrashLoopBackOff a healthy node. Liveness/readiness lose their now-redundant
  initialDelaySeconds. failureThreshold*periodSeconds (default 60*10 = 10 min) is a
  values knob. C3 verified: the metrics listener binds while load-on-boot runs in the
  shard tasks, so /readyz reports load-incomplete during the reload.

- P0-3 (chart): preStop lame-duck via the NATIVE SleepAction (k8s 1.29+) -- NOT
  exec sleep, which would fail on the shell-free distroless image. Gated on
  preStop.sleepSeconds > 0 (0 disables it on older k8s -> rely on P0-5). kubeconform
  -strict against KUBE_VERSION 1.30 accepts the field (SleepAction GA).

- P0-4 (chart): terminationGracePeriodSeconds documented to cover
  preStop + drain + save-on-exit, sized from the same dataset estimate as the
  startupProbe budget; guidance to raise it for large datasets.

Applied to BOTH the Helm chart (values-tunable) and deploy/k8s/ironcache.yaml (static,
for parity). All metrics + shutdown tests, clippy -D warnings, fmt, dash clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfFZ8gkkNhDBASuntB72HR
Both reviewers SHIP. Reviewer 1 (server) verified /readyz reads the SAME
Arc<AtomicBool> that SIGTERM/shutdown_and_join/committed-cutover all set (traced
from its single creation in bootstrap.rs) -- P0-5 is live, not inert; /livez stays
untouched (no SIGKILL risk); the C3 async-load claim holds. No defects.

Reviewer 2 (chart) findings folded in:
- [MEDIUM] preStop defaulted ON would be rejected by the API server on k8s < 1.29.
  Gate the lifecycle block on semverCompare ">=1.29-0" .Capabilities.KubeVersion so
  the chart SELF-PROTECTS on old clusters (silently degrades to the /readyz-on-drain
  fallback) instead of a broken install. deploy-lint's `helm template` now pins
  --kube-version "${KUBE_VERSION}" (1.30) so the gated block deterministically renders
  and is kubeconform-validated in CI.
- [LOW] values.yaml notes the startupProbe budget must also cover leader-election time
  in raft mode (cold cluster-wide restart), not just reload time.
- Corrected the SleepAction version phrasing (alpha 1.29 / beta+default-on 1.30 / GA 1.34).

Chart-only + workflow; no code change from the reviewed server logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfFZ8gkkNhDBASuntB72HR
@ELares

ELares commented Jul 24, 2026

Copy link
Copy Markdown
Owner Author

2-lens review: both SHIP; findings folded in

Lens 1 (server correctness): SHIP, no defects. Decisively confirmed the load-bearing question -- /readyz reads the SAME Arc<AtomicBool> that SIGTERM (apply_signal_flag->flag.store(true)), shutdown_and_join, and committed-cutover all set (single creation at bootstrap.rs:295), so P0-5 is live, not inert. Also verified: flag set at the very start of shutdown (ahead of the drain); /livez untouched so no SIGKILL risk; C3 async-load claim holds (metrics binds while load runs in the shard tasks); the drain test is a real assertion.

Lens 2 (chart/k8s): SHIP. kubeconform-pass verified (1.30 schema has SleepAction). Findings folded in:

  • [MEDIUM] preStop defaulted ON would be API-server-rejected on k8s < 1.29. Now gated on semverCompare ">=1.29-0" .Capabilities.KubeVersion so the chart self-protects (degrades to the /readyz-on-drain fallback) instead of a broken install. deploy-lint's helm template now pins --kube-version "${KUBE_VERSION}" so the gated block deterministically renders + is kubeconform-validated.
  • [LOW] values.yaml notes the startupProbe budget must cover leader-election time in raft mode too.
  • Corrected the SleepAction version phrasing (alpha 1.29 / beta+default-on 1.30 / GA 1.34).

All metrics (17) + shutdown (9) tests, clippy -D warnings, fmt, dash clean.

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown

perf-gate (A5)

Same-runner ratchet of HEAD against the merge-base (both rebuilt and measured in this job).
PASS = within the noise band, WARN = a real move inside budget (does not fail), FAIL = past budget in the bad direction.

metric base head delta% band budget verdict
qps_median (peak) 82612.48 82183.20 -0.52% +/-5.00% drop <= 15% PASS
bytes_per_key int 46.88 47.50 1.32% det rise <= 5% WARN
bytes_per_key embstr 61.38 60.77 -0.99% det rise <= 5% PASS
bytes_per_key raw 332.73 333.43 0.21% det rise <= 5% PASS

Overall: WARN

  • qps: noisy on shared CI, so the band comes from the base reps spread (floored at 5%); a drop is only a regression past the 15% budget.
  • bytes_per_key: deterministic (allocator-true memmodel), so a tight 5% rise budget; any rise beyond it FAILs.
  • Open-loop tails / criterion micro-benches are reported-not-failed (tail noise is high) and are not part of this ratchet.
  • An intentional perf trade is landed by raising the relevant budget in this PR with a documented reason (CI never auto-commits a baseline).

@ELares
ELares merged commit a82dc91 into main Jul 24, 2026
24 checks passed
@ELares
ELares deleted the feat/k8s-p0-graceful-lifecycle branch July 24, 2026 03:45
ELares added a commit that referenced this pull request Jul 24, 2026
…ss plan (#754)

Adds a "1a. Implementation status" block after the executive summary summarizing the shipped
P0 (#743/#745) + full P1 tier (13/13, #746-753) with PR refs, so the plan no longer reads as
if that work is still MISSING/PARTIAL (the inline table markers are kept as the pre-work
baseline for context). Also records the two draft claims that code fact-checks corrected during
implementation (helm-upgrade does NOT silently corrupt a reshard; REBALANCE APPLY is
implemented). Notes the chart is now production-safe for fixed-size clusters; only P2 packaging
polish + the day-2 Operator remain.


Claude-Session: https://claude.ai/code/session_01FfFZ8gkkNhDBASuntB72HR

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to 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.

1 participant