## Why The deletion engine already exposes a one-shot `buzz-admin deletions drain` command, 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.tpl` helper 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/*.yaml` fixture with `helm template`; all passed at `e68cb753bb6673aa1baa60046969bdb2d4738b62`. 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 --------- Signed-off-by: tornquist <tornquist@squareup.com> Signed-off-by: Codex <noreply@openai.com> Signed-off-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Elrond <28d6302a099e5225b02c4155ac4236e4912603df2ab08dbfc2f4fef08ce598c8@buzz.block.builderlab.xyz>
7.3 KiB
Community Deletion Operator Job
Buzz executes whole-community deletion through the typed, one-shot
/usr/local/bin/buzz-admin deletions drain command. The Helm chart can schedule
that command as a Kubernetes CronJob; it does not call relay HTTP and it does
not add another queue or retry service.
Postgres remains the handoff and source of truth. A run claims only requests
that the deletion store considers runnable, heartbeats the existing lease, and
resumes from durable checkpoints. concurrencyPolicy: Forbid prevents scheduled
pod overlap, backoffLimit: 0 prevents Kubernetes Job retries, and the deletion
store remains authoritative when a pod exits, reaches its deadline, or is
replaced.
Enablement
The CronJob is disabled by default. Production deployments should use an existing Secret and a dedicated, pre-created service account:
secrets:
existingSecret: buzz-operator-secrets
operatorJobs:
deletionDrain:
enabled: true
schedule: "*/5 * * * *"
activeDeadlineSeconds: 3600
terminationGracePeriodSeconds: 30
serviceAccountName: buzz-deletion-drain
podLabels:
tags.datadoghq.com/service: buzz-deletion-drain
podAnnotations:
sidecar.istio.io/inject: "false"
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
Set s3.endpoint, s3.bucket, s3.region, and s3.addressingStyle in chart
values. The selected Secret must contain DATABASE_URL and REDIS_URL; it may
contain BUZZ_S3_ACCESS_KEY and BUZZ_S3_SECRET_KEY when the object store uses
static credentials. The pod receives only those connection values and the four
non-secret S3 settings. It does not receive BUZZ_RELAY_PRIVATE_KEY,
BUZZ_GIT_HOOK_HMAC_SECRET, RELAY_URL, or the full Secret through envFrom.
The pod also disables service-account token automounting and Kubernetes service
link environment injection because the executor does not call the Kubernetes
API or discover cluster Services. This disables the ordinary Kubernetes API
token mount, not credentials injected by a platform workload-identity
mechanism.
Leaving operatorJobs.deletionDrain.serviceAccountName empty falls back to the
relay's own service account, including cloud IAM attached through the platform's
workload-identity mechanism. For example, the EKS IRSA admission webhook can
inject its projected web-identity token and AWS environment variables despite
automountServiceAccountToken: false; other platforms provide their own
identity mechanism. The drain pod therefore inherits the relay's cloud role by
default. Create a dedicated service account with only the object-store
permissions listed below and name it explicitly if you want the executor's IAM
blast radius to be smaller than the relay's.
The S3 principal needs the relay's normal object permissions plus bucket-level
s3:ListBucketVersions and object-level s3:DeleteObjectVersion for every
tenant-owned prefix. This also applies to never-versioned buckets because S3
reports their objects with the null version id.
Runbook
-
Confirm database migrations are current and the deletion request has crossed the explicit inventory and approval boundary with
buzz-admin deletions inspect <request-id>. -
Confirm the selected Secret contains the required keys and the S3 principal has version-list and exact-version delete permissions.
-
Enable the CronJob and inspect its rendered command and environment before rollout.
-
Locate the rendered CronJob by label rather than by guessing its name:
kubectl get cronjob -n <namespace> \ -l app.kubernetes.io/component=deletion-drain,app.kubernetes.io/instance=<release>The name is
<bounded-fullname>-deletion-drain. The chart truncates only the fullname portion when needed so the suffix remains stable within Kubernetes' 52-character CronJob name limit.buzz.fullnamecollapses to the release name when the release name already contains the chart name, sohelm install buzz ...rendersbuzz-deletion-drain, notbuzz-buzz-deletion-drain. -
Start one staffed manual run with
kubectl create job --from=cronjob/<cronjob-name> <job-name>. -
Follow pod logs and re-run
buzz-admin deletions inspect <request-id>to verify lease, checkpoint, retry, blocked, and terminal state. -
If a run fails or times out, fix the recorded dependency or permission failure. Do not add Kubernetes retries: the next scheduled drain consults the durable retry/checkpoint state and resumes only when the store allows it.
Deadlines, termination, and the retry budget
activeDeadlineSeconds is a Kubernetes-side limit, and the deletion store does
not learn why a pod went away. Two consequences matter when reading state:
- A
DeadlineExceededJob is not recorded as a deletion retry. Shutdown releases the claim without recording one, soretry_countandblocked_atdo not advance. Only the object-store drain checkpoints per manifest chunk and resumes mid-stage; every other stage restarts from its beginning on the next run. A deadline that keeps landing inside one of those non-resumable stages therefore repeats indefinitely: each run incrementsattemptsand burns the window again while the retry budget never moves and the request is never blocked. - Diagnose this from both sides.
buzz-admin deletions inspect <request-id>shows a risingattemptswith a flatretry_countand nolast_error; Kubernetes holds the reason. The chart labels the CronJob and the drain pods, but not the generated Jobs, so find the attempts withkubectl get pods -n <namespace> -l app.kubernetes.io/component=deletion-drainand read the condition withkubectl describe job <job-name>.
Size activeDeadlineSeconds for the longest single stage this community will
run, not for the average run. To recover, either raise the deadline and let the
schedule pick the request back up, or take one staffed run with
buzz-admin deletions run <request-id> outside the CronJob's deadline.
terminationGracePeriodSeconds is a best-effort window, not a guarantee. The
drain command handles SIGTERM and releases its lease cleanly when it wins the
race, but a pod that is still working when the grace period expires is
SIGKILLed with the lease still held. Nothing is lost: the durable lease simply
expires (60s by default, heartbeated every 10s) and the next run reclaims the
request with a fresh lease generation, which fences any straggler write from the
killed process. Expect up to roughly a lease duration of delay before the
request is runnable again; do not raise the grace period expecting a clean
handoff.
The current owner self-serve relay admission records an owner-origin request at
submitted and intentionally performs no inventory or approval synchronously.
The deletion engine rejects submitted and inventoried requests at its
explicit approval boundary. Automating the privileged inventory/approval step
is therefore a separate control-plane slice; enabling this CronJob alone does
not make a newly accepted owner request destructive.
The chart has no existing PrometheusRule or provider-neutral CronJob alert integration. Operators must alert on failed/missed Jobs and long-running active Jobs in their deployment platform. Adding a chart-native alert abstraction is debt, not part of this job contract.