Kubernetes Jobs run short-lived workloads to completion; CronJobs schedule those Jobs on a recurring timetable. When either fails, you need a repeatable way to find the root cause fast. This guide shows how retries and backoffLimit work, where to look for failed Pods, how to read logs and events, and how to troubleshoot safely in production-like clusters. You will also get copy-paste manifests and commands you can adapt today.
Workflow Overview
Use this path to diagnose most Job and CronJob issues:
- Observe the controller
kubectl get jobkubectl get cronjob -o wide- Note status fields:
Succeeded,Failed,Active,Last Schedule,Suspend,ConcurrencyPolicy
- Find the Pods
- For a Job:
kubectl get pods -l job-name=<job-name> - For a CronJob run: list Jobs owned by the CronJob (
kubectl get jobs --selector=job-name=<cronjob-name>-or by name pattern), then list their Pods.
- Describe for events and reasons
kubectl describe job/<name>kubectl describe pod/<pod-name>(look forReason/Messagesuch asImagePullBackOff,OOMKilled,Error,FailedScheduling)- Read logs:
kubectl logs <pod-name> --all-containers=true- If the container crashed, add
--previous - For all Pods of a Job:
kubectl logs -l job-name=<name> --all-containers=true
- Check spec pitfalls
- Jobs:
backoffLimit,activeDeadlineSeconds,parallelism,completions,restartPolicy - CronJobs:
schedule,startingDeadlineSeconds,concurrencyPolicy,suspend,successfulJobsHistoryLimit,failedJobsHistoryLimit
- Act safely
- Pause CronJob:
kubectl patch cronjob/<name> -p '{"spec":{"suspend":true}}' - Reproduce by creating an ad-hoc Job from a CronJob:
kubectl create job --from=cronjob/<name> <tmp-job> - Roll forward with a small change, watch events and logs, then re-enable.
Jobs vs CronJobs in Practice
Jobs orchestrate Pods until the desired number of completions succeed. CronJobs create Jobs on a schedule. The key troubleshooting differences:
- Jobs: focus on Pod lifecycle, exit codes,
backoffLimit, and resource issues. - CronJobs: add schedule evaluation,
concurrencyPolicy, missed runs (startingDeadlineSeconds), and history cleanup. You often debug the spawned Job first, then come back to the CronJob spec if scheduling is the problem.
Troubleshooting a Failing Job
- Confirm state
kubectl get job <name> -o wide
Look at Succeeded, Failed, Active.
- Check Pods
kubectl get pods -l job-name=<name>
If none exist, check FailedScheduling or quota issues with kubectl describe job and kubectl get events.
- Inspect events and status
kubectl describe pod <pod-name>
Common signals:
ImagePullBackOff: bad image or missing pull secret.ErrImagePull: registry or auth error.OOMKilled: memory limit too low for the process.Error/Completed: checkexitCodein container status.
- Read logs
kubectl logs <pod-name> --all-containers=true
If the container restarts within the same Pod (restartPolicy: OnFailure), add --previous.
- Understand retries and
backoffLimit
backoffLimit: maximum Pod retries before the Job is marked Failed. Example:backoffLimit: 2allows two retries after the first failure (three attempts total).activeDeadlineSeconds: hard time limit for the Job; exceeding it fails the Job.
- Fix patterns
- Image errors: correct the image tag or add
imagePullSecrets. - Resource pressure: raise memory/CPU limits or optimize the workload.
- Process exit code non-zero: handle errors in the app, add retries, or adjust inputs.
- Slow jobs: extend
activeDeadlineSecondsor reduceparallelismto ease pressure.
Troubleshooting a Failing CronJob
- List CronJobs
kubectl get cronjob -o wide
Check SCHEDULE, SUSPEND, ACTIVE, LAST SCHEDULE.
If ACTIVE stays 0 and LAST SCHEDULE does not change, the controller may be blocked or schedule filters apply. Inspect:
- Is it creating Jobs?
suspend: truemeans no runs will be created.startingDeadlineSeconds: missed schedules older than this are skipped.concurrencyPolicy:Forbidprevents a new run if the prior run is still active;Replacecancels the active run.
- Inspect the latest spawned Job
kubectl get jobs --sort-by=.metadata.creationTimestamp
Take the newest Job owned by the CronJob and debug it like any other Job (events, Pods, logs).
- Safe operations
- Temporarily pause:
kubectl patch cronjob/<name> -p '{"spec":{"suspend":true}}' - Test changes by creating a one-off Job from the template:
kubectl create job --from=cronjob/<name> <tmp-job> - Resume after verifying:
kubectl patch cronjob/<name> -p '{"spec":{"suspend":false}}'
Practical Examples
Example 1: A Job that fails twice then succeeds
apiVersion: batch/v1
kind: Job
metadata:
name: demo-backoff
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: worker
image: bash:5
command: ["bash", "-lc", "if [ ! -f /tmp/ok ]; then echo first or second fail; touch /tmp/ok; exit 1; else echo success; fi" ]
What to observe:
- First Pod exits
1; controller retries up tobackoffLimit. - Second attempt also exits
1; third attempt runs and printssuccess. kubectl describe job/demo-backoffshowsFailedincrements beforeSucceeded.
Example 2: CronJob blocked by concurrencyPolicy=Forbid
apiVersion: batch/v1
kind: CronJob
metadata:
name: report-job
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 2
jobTemplate:
spec:
backoffLimit: 1
template:
spec:
restartPolicy: OnFailure
containers:
- name: reporter
image: bash:5
command: ["bash", "-lc", "echo starting; sleep 400; echo done" ]
Why it fails to schedule every 5 min:
- Each run takes ~6.6 min (400 s). With
Forbid, the 5‑minute trigger that arrives while a run is active will be skipped.LAST SCHEDULEmay not advance every tick. - Fix options: set
concurrencyPolicy: Allow, reduce runtime, or increase the interval.
Example 3: ImagePullBackOff in a Job
apiVersion: batch/v1
kind: Job
metadata:
name: bad-image
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: c
image: example.com/private/app:does-not-exist
Troubleshoot:
kubectl describe podshowsErrImagePull/ImagePullBackOff.- Fix the tag or configure
imagePullSecretsand correct registry URL.
Example 4: OOMKilled in a Job
apiVersion: batch/v1
kind: Job
metadata:
name: mem-tight
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: c
image: python:3.11
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "64Mi"
cpu: "200m"
command: ["python", "-c", "a='x'*200_000_000; print(len(a))
Troubleshoot:
kubectl describe podshowsOOMKilledin last state.- Raise memory limit (for example
256Mi) or reduce memory use in the program.
Local Pilot Plan
Start small and measurable:
- Pick one Job with clear success criteria
- Example: transform a small input file and write a checksum.
- Define done as exit code
0and presence of an output line.
- Make it easy to inspect
- Add structured logs and a final success message.
- Set
backoffLimitto1andactiveDeadlineSecondsto a safe upper bound.
- Validate in a local cluster
- Apply the Job, watch events, and read logs end-to-end.
- Record the run time and resource use.
- Wrap it in a CronJob (optional)
- Choose a conservative schedule first (for example every hour).
- Set
concurrencyPolicytoForbiduntil you prove idempotency.
- Expand gradually
- Increase input size, then
parallelism. - Add alerts on Job failures and missed schedules.
This approach reduces risk and shortens the feedback loop.
Conclusion
Jobs and CronJobs fail for understandable reasons: bad images, resource pressure, non-zero exits, or scheduling rules. The fastest path is consistent: check controller status, find the Pods, read events, inspect logs, and validate spec fields such as backoffLimit, activeDeadlineSeconds, schedule, and concurrencyPolicy. Use suspend and ad-hoc Jobs to test safely, then roll forward in small steps. Keep runs short, outputs clear, and success criteria obvious. That is how you troubleshoot with confidence.