Schedule the deletion drain safely - #7827
TheSentinel454 wants to merge 3 commits into
Conversation
d0c2af1 to
6549909
Compare
6549909 to
b79bd19
Compare
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
No new blocking defect found in the incremental scheduler at b79bd19c73b188d9fb785cbcd5ba50ccad74b295, against acf561219f4223bc6090ce5c82555ee294d11ea4. This is a source review, not an approval.
The disabled-by-default, fixed-command job preserves the existing approval boundary and PostgreSQL lease/checkpoint authority without exposing a generic command or signing-secret environment. Two non-blocking runbook corrections are inline; no engine redesign is requested.
Validation: existing chart CI passed all 11 suites / 54 tests, including the deletion-drain suite. Its checkout 39edc1151804cc7fcece70e82d32000f5f66eb47 has the same tree as this head. Reviewers did not check out, render, build, or execute PR code. Live Kubernetes interruption, workload identity, and rollout behavior were not exercised.
This does not clear the separate base-layer #7818 recovery blocker.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Thanks for this. I reviewed head b79bd19c against acf56121 (the head of #7818), covering only this PR's two commits. I found no blockers.
The job is scoped tightly:
- It runs one typed command,
buzz-admin deletions drain. - It gets only the eight env entries the deletion engine reads.
- It has no
envFromand no relay signing key. - It runs with
Forbid,restartPolicy: NeverandbackoffLimit: 0. additionalProperties: falsekeeps arbitrary command/env settings out of the values schema.
PostgreSQL keeps the retry and approval authority. Claim requires an approved request, and submitted/inventoried requests are never claimable, so the note that this can't make a new owner request destructive is accurate.
We also ran it locally:
- The chart suite passed 54/54 with Helm 3.16.4 and helm-unittest 0.8.2. The enabled quickstart render passes strict kubeconform for Kubernetes 1.31 (18/18 resources).
- We broke the chart five ways and the suite failed each time: removing the reserved-label
fail, settingconcurrencyPolicy: Allow, settingbackoffLimit: 6, dropping the Redis validation, and adding an extra secret env. - A local build of
buzz-admin deletions drainran against fresh Postgres/Redis with exactly the chart's eight env vars and an unwritableTMPDIR. On an empty queue it exited 0 with no output.
Non-blocking:
- CronJob name length.
{{ include "buzz.fullname" }}-deletion-drainadds 15 characters to a name capped at 63. Kubernetes rejects CronJob names over 52, so any release whose fullname is over 37 characters fails at install once the job is enabled. The failure is loud, the job is opt-in, and the existing-storage-accountingCronJob has the same limit with an even longer suffix. So I'm not blocking on this, but I think one small helper that truncates the base to fit before adding the suffix would fix both CronJobs. A long-release-name test would pin it. - Service account and cloud IAM. When
serviceAccountNameis empty, the job uses the relay's service account and so inherits the relay's cloud role.automountServiceAccountToken: falseonly removes the Kubernetes API token; admission-injected workload identity, such as the EKS pod-identity webhook's projected token and AWS env, still gets added. So the README line saying the job doesn't receive a service-account token is broader than what actually happens. I'd say explicitly that the default inherits the relay's IAM, and recommend a pre-created dedicated account when IAM isolation matters. - Deadline and shutdown wording.
activeDeadlineSeconds: 3600is more than the 60-second renewable lease, but it isn't a limit on total work, since a large queue can outrun it. SIGTERM cancels stage execution and tries to release the lease, but not every claim/heartbeat/release await is bounded by cancellation, andstatement_timeoutdefaults to zero. So exiting within the 30-second grace period isn't guaranteed, and a SIGKILL leaves the lease until it expires and gets reclaimed. That recovery path works; it would just be good for the runbook to describe it that way rather than promise a graceful finish. It should also mention that a stage that can't fit inside the deadline gets restarted by each successive Job without a recorded error. - Test coverage gaps.
- The reserved-label test only covers
app.kubernetes.io/component. - Removing the
failis caught by a YAML duplicate-key error, not by the guard's message, sonameandinstancehave no coverage of their own. - No test asserts the pod's own labels (only the CronJob metadata), the default
sidecar.istio.io/inject: "false"annotation, thes3.endpointrequired, the unsupported-typefail, or schema rejection of unknown keys.
- The reserved-label test only covers
CI is green at this head: 11 pass, 28 skipped.
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: tornquist <tornquist@squareup.com>
Co-authored-by: Codex <noreply@openai.com> Signed-off-by: tornquist <tornquist@squareup.com>
The runbook told operators to target `cronjob/<release>-buzz-deletion-drain`. `buzz.fullname` collapses to the release name when it already contains the chart name, so the documented name is wrong for the documented install: `helm install buzz ...` renders `buzz-deletion-drain`. Discover the CronJob by its component label instead, and describe the name rule rather than a single guessed spelling. Three more corrections: `activeDeadlineSeconds` was presented as if a timed-out run were just another retry. It is not recorded as one — shutdown releases the claim without recording a retry, and only the object-store drain resumes mid-stage — so a deadline landing repeatedly inside a non-resumable stage loops forever with a rising `attempts`, a flat `retry_count`, and no block. Document how to spot that from both the request and Kubernetes, how to size the deadline, and how to recover. `terminationGracePeriodSeconds` was presented as a clean handoff. Document that a pod still working at the end of the window is SIGKILLed holding its lease, and that recovery is lease expiry plus reclaim under a new generation. An empty `serviceAccountName` was described as a neutral default. It inherits the relay's service account; `automountServiceAccountToken: false` hides the projected token but does not detach cloud IAM bindings resolved through the node metadata path. Recommend a dedicated pre-created account when the executor's IAM blast radius should be smaller than the relay's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: tornquist <tornquist@squareup.com>
b79bd19 to
494d574
Compare
|
Addressed the runbook feedback at head
No chart behavior changed. The pre-existing shared CronJob name-length convention and broader optional chart-test expansion remain out of this review-fix scope. Helm validation passed 54/54 plus lint and the render matrix. Both inline threads have substantive replies and are resolved. AI-generated implementation reply (Elrond). |
wesbillman
left a comment
There was a problem hiding this comment.
Carl, an automated reviewer, commenting via Wes’s GitHub account.
Review clear: no new blocking defect found. The prior runbook findings are addressed: deadline interruption is distinguished from durable dependency retries, non-resumable-stage recovery is documented, and manual runs discover the actual CronJob name. The separately reviewed base #7818 recovery fix is present. The opt-in typed job preserves the existing approval and PostgreSQL lease/checkpoint boundaries; no generic command/environment mechanism is introduced.
Reviewed HEAD 494d5744360f2d39c464e8ac8b83ae2276ccf0f6 against BASE 8ebe3b881c98f19cfc93da7c1d00d4b0618a356e, including chart/credential profiles, CLI/image/env concordance, shutdown/reclaim, and operator guidance. All assigned lanes are complete. Source-only on pinned Blox; no checkout, render, build, or execution by reviewers.
Existing chart CI passed 11 suites / 54 tests plus its fixture render matrix. It tested merge 2086b18b84fe22e67ce1eb16d15619c83d78d623, whose tree equals this head. The enabled job is exercised by helm-unittest, not the fixture matrix; the inline coverage follow-up is non-blocking. Live Kubernetes interruption/IAM behavior remains untested. Broad CI has a Desktop Smoke E2E (4) failure, not attributed here; required CI remains a merge gate. This comment is not approval.
| @@ -0,0 +1,232 @@ | |||
| suite: typed deletion drain operator job | |||
There was a problem hiding this comment.
Non-blocking coverage follow-up: the new enabled workload is covered by helm-unittest, but no values file under ci/ or tests/fixtures/ enables it, so the separate fixture render matrix never exercises this CronJob. Add an enabled existing-Secret fixture and, preferably, a bundled MinIO/Redis variant. Pin the missing-S3-endpoint rejection for the existing-Secret profile as well. This strengthens supported-profile coverage; no current rendering failure was established.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 Re-review at 494d5744, which sits on the current #7818 head (8ebe3b88). git range-diff shows your two existing commits are unchanged from b79bd19c, so the only new change is the runbook/values commit. I don't see anything blocking in it.
I checked the new runbook claims against source and they hold:
- Shutdown releases the claim without touching
retry_count/last_error, while a claim incrementsattempts. So "rising attempts, flat retry count, never blocked" is the right thing to tell operators to look for. freeze_destructive_manifestclears its partial chunks and re-enumerates from scratch, so only the object drain resumes mid-stage.DEFAULT_LEASE_DURATIONis 60s andHEARTBEAT_INTERVALis 10s, which matches the SIGKILL recovery text.jobTemplatehas no metadata labels while the pod template does, so finding attempts via pod labels is correct.- Discovering the CronJob by component/instance label fixes the
buzz-buzz-deletion-drainname confusion. - The deadline and termination sections now describe recovery honestly instead of promising a clean handoff.
One small wording nit: the service-account paragraph says IRSA bindings are "resolved by the node/metadata path". That's accurate for GKE Workload Identity, but on EKS it's the pod-identity webhook injecting its own projected token volume plus AWS_* env, which automountServiceAccountToken: false doesn't suppress. The conclusion that the pod inherits the relay's cloud role is right either way, so maybe just say "injected by the platform's workload-identity mechanism".
Still open from my last review, both non-blocking:
- CronJob name length.
<fullname>-deletion-drainstill fails install for fullnames over 37 characters, same as-storage-accounting. - Chart test gaps.
name/instancereserved labels, pod labels, the istio annotation, thes3.endpointrequired, and schema rejection of unknown keys. This overlaps with Carl's note about adding an enabled fixture underci/.
CI: everything passes except Desktop Smoke E2E (4) and its two aggregate checks. The failing specs are sidebar-snapshot, video-attachment and workflows, and this PR only touches the chart, docs and ARCHITECTURE.md. Since this is stacked on #7818, it also inherits the lock-order blocker I just raised there.
|
Desktop-smoke CI investigation pinned to The test uses A throwaway test-only experiment located each player under |
Why
The deletion engine already exposes a one-shot
buzz-admin deletions draincommand, but operators have no safe way to schedule it. A generic workload wrapper could expose arbitrary commands or inherit relay credentials, which would add a second control plane and widen the blast radius.What
This PR adds an opt-in Kubernetes CronJob for the typed deletion drain command. It is stacked on #7818, which adds owner-request admission.
How
The chart keeps the job disabled by default. The CronJob uses
Forbid, zero Kubernetes retries, bounded runtime and history, and only the database, Redis, and object-store settings required by the executor. PostgreSQL leases, checkpoints, and retry timing remain the sole durable retry authority.The closed
_operator-jobs.tplhelper centralizes safe CronJob mechanics without creating an arbitrary command or environment registry. This leaves the touched system simpler than a general job framework would.Risk
The deployment blast radius is low because the job is disabled by default. When enabled, it can run destructive deletion work that the database has already approved, so the chart limits concurrency, retries, credentials, and execution time.
Testing
Rendered every
deploy/charts/buzz/ci/*.yamlfixture withhelm template; all passed ate68cb753bb6673aa1baa60046969bdb2d4738b62.Ran the full independent Desktop, Tauri, and web lanes; all passed at
e68cb753bb6673aa1baa60046969bdb2d4738b62. Flutter package resolution made no progress within three minutes, so the mobile lane is not claimed green.Bigger picture
A later stack layer must inventory and approve admitted owner requests. Until then, this scheduler cannot make a new owner request destructive.
Generated with Codex