Thang Kieu
11/27/2025, 5:26 AMMarvin
11/27/2025, 5:26 AMMarvin
11/27/2025, 5:29 AM<http://prefect.io/flow-run-id|prefect.io/flow-run-id> on Jobs to see this.
- Kubernetes retries: If your Job template sets backoffLimit > 0, K8s may create additional pods for retries within the same Job, inflating pod counts.
What to do
1) Enable automatic cleanup with a TTL
- In your Kubernetes work pool, set “Finished Job TTL” (e.g., 3600 seconds). This injects spec.ttlSecondsAfterFinished into the Job so Kubernetes deletes it (and its pods) after the TTL.
- If you use a custom Job template, make sure it includes:
spec:
ttlSecondsAfterFinished: "{{ finished_job_ttl }}"
Then set finished_job_ttl on the work pool to a reasonable value (e.g., 3600).
2) Consider Kubernetes retry behavior
- If you want Prefect to handle retries/reschedules, set backoffLimit: 0 in your Job template. That avoids extra pods created by K8s retries.
3) Verify what’s accumulating
- See statuses of Jobs/Pods:
kubectl get jobs,pods -n <your-namespace>
- Check how many Jobs exist per Prefect flow run:
kubectl get jobs -n <your-namespace> -l <http://prefect.io/flow-run-id|prefect.io/flow-run-id> -o custom-columns=NAME:.metadata.name,FLOW_RUN_ID:.metadata.labels.prefect\.io/flow-run-id,SUCCEEDED:.status.succeeded,FAILED:.status.failed,ACTIVE:.status.active
If you see many completed Jobs for the same flow-run-id, that’s reschedules or retained history. Setting TTL will clean these up going forward.
Helpful docs
- Kubernetes integration overview: prefect-kubernetes docs
- Work pools overview: Work pools in Prefect 3
A couple of clarifying questions to make sure we zero in on your setup:
- Which Prefect version are you on (assuming 3.x)?
- Do you have “Finished Job TTL” set in your Kubernetes work pool?
- What’s your Job template backoffLimit?
- Are the 500 pods/jobs mostly in Completed/Failed status, or many are still Running?Thang Kieu
11/27/2025, 5:56 AMMarvin
11/27/2025, 5:56 AMMarvin
11/27/2025, 5:58 AMspec.ttlSecondsAfterFinished to new Jobs so Kubernetes cleans them up after an hour.
- If you use a custom job template, make sure it includes:
spec:
ttlSecondsAfterFinished: "{{ finished_job_ttl }}"
Then set finished_job_ttl on the work pool.
CLI option (if you prefer editing the base job template)
# Fetch default K8s template
prefect work-pool get-default-base-job-template --type kubernetes > k8s-job-template.json
# Edit k8s-job-template.json to include:
# "spec": { "ttlSecondsAfterFinished": "{{ finished_job_ttl }}", ... }
# Apply to an existing pool
prefect work-pool update "YOUR_POOL_NAME" --base-job-template k8s-job-template.json
Note: TTL only applies to newly created Jobs. Old Jobs won’t be cleaned up retroactively.
One-time cleanup of old Jobs
- If the namespace is dedicated to Prefect (or you’re sure no runs are currently active), a simple, broad cleanup:
kubectl delete job -n <namespace> -l <http://prefect.io/flow-run-id|prefect.io/flow-run-id>
- If you want to only delete finished Jobs and keep any active ones, use a safer filter (requires jq):
kubectl get jobs -n <namespace> -l <http://prefect.io/flow-run-id|prefect.io/flow-run-id> -o json \
| jq -r '.items[] | select((.status.active|tonumber) != 1 and ((.status.succeeded|tonumber) >= 1 or (.status.failed|tonumber) >= 1)) | .metadata.name' \
| xargs -r -n1 kubectl delete job -n <namespace>
Deleting a Job will also delete its controlled pods.
Recommendations
- Keep backoffLimit at 0 (let Prefect handle retries/reschedules).
- Set Finished Job TTL (e.g., 3600–86400) to prevent future accumulation.
Docs
- Kubernetes integration: prefect-kubernetes docs
- Work pools in Prefect 3: Work pools overview
If you share your pool name/namespace, I can tailor a cleanup command that won’t touch any currently running Jobs.