Repository navigation
sourcegraph-executor/k8s: opt-in CronJob to clean up orphaned executor job resources - #923
Draft
marcleblanc2 wants to merge 2 commits into
Draft
marcleblanc2 wants to merge 2 commits into
marcleblanc2 wants to merge 2 commits into
Conversation
… resources The executor deletes each job's Job, Secret, and PVC in Teardown, but Teardown never runs when the executor is killed mid-job (OOM kill, node scale-down, rollout). Nothing garbage-collects those resources: the Secrets have no ownerReferences and the Jobs no ttlSecondsAfterFinished, so they accumulate forever (one real cluster reached 699 orphaned sg-executor-job-* secrets). Add an opt-in CronJob (executor.cleanup.enabled) that deletes sg-executor-job-* Jobs, Secrets, and PersistentVolumeClaims older than executor.cleanup.minimumAgeSeconds. The template fails rendering if that age does not exceed executor.kubernetesJob.deadline, so resources of running jobs are never deleted. Reuses the sg-executor ServiceAccount; the executor Role additionally gets get/list on jobs. Amp-Thread-ID: https://ampcode.com/threads/T-01a01d55-b956-718b-a863-ef9c8c5648be Co-authored-by: Amp <amp@ampcode.com>
The cleanup CronJob runs as sg-executor and lists Jobs, Secrets, and PVCs, and kubectl delete gets each one first. #1017 removes get and list on Secrets and PVCs from the executor's Role, since the executor never calls them, which would leave the CronJob with 403s. Grant get and list on all three in a rule rendered only with executor.cleanup.enabled, instead of on Jobs for every install. kubectl delete also waits by default, which lists and watches an object that lingers, such as a PVC held by its protection finalizer. The Role has no watch, so delete with --wait=false. Amp-Thread-ID: https://ampcode.com/threads/T-01a1d269-2788-7075-b66b-b87e3087047d Co-authored-by: Amp <amp@ampcode.com>
marcleblanc2
marked this pull request as draft
November 13, 2026 11:00
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Open issues
Found in review on 2026-11-13 (review thread), at
ce2cf93. The cleanup works and is off by default, and the chart defaults are safe; this PR is a draft until the first two are fixed.deadline: 100andminimumAgeSeconds: 120, which passes the check: a Job whose pod started 63 seconds after the Job was created was deleted 71 seconds into its 100 second run. The check also lets equal values through, against its own error message, and ignoresmaximumRuntimePerJob, which bounds how long a multi-step job's Secret and PVC live. Fix: skip Jobs that still have active pods and the Secrets and PVCs that belong to them, and make the check strictpipefail, so a failedkubectl getis ignored. Reproduced on EKS: run as a ServiceAccount with no permissions, the pod logged threeForbiddenerrors, exited 0, and the Job showed Complete. A release withexecutor.configureRbac: falseand cleanup enabled would do this on every run. Fix: fail the script whenkubectl getfailsnodeSelector,tolerations,affinity, security contexts, or pod annotations from values, and the image runs as root (uid 0 on EKS). A namespace enforcing therestrictedPod Security Standard rejects it, and a tainted executor node pool leaves it Pending. Neither was triedcharts/sourcegraph-executor/CHANGELOG.md, and the RBAC table in the chart README does not list the verbs cleanup addsalpine/kubectl:1.33.4is a community image pinned by tag only, in a pod that can read every Secret in the namespace and create Jobs; pin a digestminimumAgeSecondsof 1000000 or more renders in exponent form, which the script's age comparison does not handle (rendered only, not run)Tested in this review on the EKS test instance
m5, by upgrading the executor release frommainto this branch merged ontomain:mainand no CronJob existsgetandlistrules, and did not restart the executorsg-executor-job-*, and kept a 2 second old Secret with that prefix and an old Secret without it.Not tested: a scheduled run, a PVC held by its protection finalizer, hundreds of leftovers within the 600 second and 128Mi limits, and the real executor leaving objects behind.
Problem
The executor deletes each job's Job, Secret, and (optional) PVC in
Teardown(kubernetes.go), butTeardownnever runs when the executor pod is killed mid-job — OOM kill, node scale-down/spot reclaim, or a deployment rollout. Nothing in Kubernetes garbage-collects the leftovers: the Secrets carry noownerReferencesand the Jobs nottlSecondsAfterFinished, so they accumulate forever. One real cluster reached 699 orphanedsg-executor-job-*Secrets.The proper fix (ownerReferences from Secret/PVC → Job plus
ttlSecondsAfterFinished) belongs in the executor code in sourcegraph/sourcegraph; until then the chart can sweep up.Change
executor.cleanup.*values deploying a CronJob that deletessg-executor-job-*Jobs, Secrets, and PersistentVolumeClaims older thanexecutor.cleanup.minimumAgeSeconds(default 3600s, every 30 min).minimumAgeSecondsdoes not exceedexecutor.kubernetesJob.deadline(default 1200s), so resources of running jobs are never touched — any executor job older than the deadline has already been terminated byactiveDeadlineSeconds.sg-executorServiceAccount. Only withexecutor.cleanup.enabled, the executor Role gainsgetandlistonjobs,secrets, andpersistentvolumeclaims(it already hasdeleteon all three). fix/resources: Drop unused ports, Services, Role verbs, and settings #1016 removesgetandliston Secrets and PVCs from the default Role, because the executor never calls them.kubectl deleteruns with--wait=false: waiting lists and watches objects that linger, like a PVC held by its protection finalizer, and the Role has nowatch.alpine/kubectl(kubectl + shell;bitnami/kubectlis deprecated andregistry.k8s.io/kubectlis distroless, no shell). Fully overridable for air-gapped mirrors.Testing
helm lintandhelm templatecleanhelm unittest: 11/11 pass, including newtests/cleanup_test.yaml(default off, renders when enabled, fails render on unsafeminimumAgeSeconds, Role grantsget/listwhen enabled)git merge-tree): no conflict, 11/11 pass, and the Role grants the cleanup verbs only when cleanup is enabledalpine/kubectl:1.33.4on a live EKS cluster: busyboxdate -DparsescreationTimestampcorrectly and kubectl is presenthelm-docsregenerated (README diff contains only the four new value rows)