Job in Kubernetes
How to run a task once, run it many times in parallel, or run it every night, and be sure it really finishes.
Most of the programs we talk about in Kubernetes are meant to run forever. A website, an API, and a chat service should all stay up day and night. But a huge amount of useful work looks completely different. Resize a million photos. Send this month’s invoices. Copy last night’s data into a backup. Train a machine-learning model. Each of these tasks has a clear start and a clear end, and when the work is done, the program should simply stop and go away.
Kubernetes has a special tool for exactly this kind of work, called a Job. A Job starts the work, watches it, retries it when something breaks, and records whether it finally succeeded. In this guide you will learn what a Job is, how it behaves from the first second to the last, how to run many copies in parallel, how to run Jobs on a schedule, and how to avoid the mistakes that catch most teams.
1What Is a Job?
Let us start with the plain meaning of a Job and the words we need to describe it.
The short answer
A Job is a Kubernetes object that runs one or more pods until a chosen number of them finish successfully. A pod is the smallest unit Kubernetes runs; it wraps one or more containers, which are lightly packaged boxes holding a program and everything it needs. Kubernetes runs pods on nodes, the machines that make up the cluster. A Job sits above the pods and takes responsibility for them: if a pod fails, the Job starts another. When enough pods have succeeded, the Job marks itself complete and stops creating new ones.
A website is like a permanent employee who comes to work every day and never goes home. A Job is like a courier hired to deliver one parcel. The courier goes out, tries to deliver, and if the door is locked, tries again. Once the parcel is delivered, the courier signs the receipt and the job is finished. Nobody expects the courier to stay around afterwards.
What Jobs are good for
Migrations and imports
Change a database structure, load a file into a table, or move data between systems, once, with a clear result.
Batch processing
Crunch through a large pile of records, such as invoices, logs, or transactions, and then stop.
Backups
Copy data to safe storage on a regular basis and confirm that the copy succeeded.
Training and preparation
Prepare data sets and train models, often across many pods at the same time.
Conversion work
Resize images, transcode video, or generate thumbnails, one item or one batch at a time.
Cleanup and maintenance
Remove old files, rebuild search indexes, or refresh caches when needed.
The word “job” is used loosely in everyday speech. In this guide, a capital-J Job always means the Kubernetes object, not a step in some pipeline tool or a scheduled task on an old server.
2Job vs Deployment vs Bare Pod
Choosing the right tool starts with one question: is this work supposed to end?
| Workload | Meant for | Does it end? | Typical example |
|---|---|---|---|
| Job | A task that must finish successfully | Yes, when enough pods succeed | Database migration |
| CronJob | A Job that repeats on a schedule | Each run ends; the schedule continues | Nightly backup |
| Deployment | A service that must stay up | No, pods are replaced forever | Web API |
| StatefulSet | A service that needs stable names and storage | No | Database cluster |
| DaemonSet | One helper on every node | No | Log collector |
| Bare pod | Quick experiments | Yes, but nobody retries it | Trying out an image |
Why not use a Deployment?
A Deployment believes that a finished program is a broken program. It keeps its pods running with a restart rule called Always, so when your task finishes and exits cleanly, the Deployment happily starts it again, and again, without end. That is perfect for a web server and wrong for a nightly report.
Why not use a bare pod?
A lone pod does run your task, but nobody is responsible for it. If its node crashes halfway through, the pod is simply lost. No one retries it, and no one records whether the work was completed. A Job adds exactly the missing pieces: retries, counting, and a final verdict.
Some teams run a Deployment with a program that sleeps forever and waits for work. That is not a Job. It costs money all day, it needs its own scaling logic, and it can never report “done”. If the work has an end, model it as a Job.
3Anatomy of a Job
A Job is described by a small document with a handful of settings. Learn these settings and you can read almost any Job.
Like other Kubernetes objects, a Job is written in a simple structured text format called YAML and submitted to the cluster. The description has two main parts. The first is the pod template, a recipe for the pods the Job will create: which container image to run, what command to give it, how much CPU and memory it needs, and where to find its settings. The second part is a set of rules that control how many pods run, how many must succeed, and what to do when things go wrong.
completions
How many pods must finish successfully for the whole Job to count as done. If you say nothing, the answer is one.
parallelism
How many pods may run at the same moment. The default is one, so pods run one after another.
backoffLimit
How many failures are tolerated before the Job is declared failed. The default is six.
activeDeadlineSeconds
A time limit for the entire Job. When it runs out, everything is stopped and the Job fails, no matter how many retries remain.
ttlSecondsAfterFinished
How long to keep a finished Job before Kubernetes deletes it automatically. If unset, it stays until someone removes it.
restartPolicy
Set inside the pod template. For a Job it must be OnFailure or Never. Always is not allowed.
flowchart TB
CJ["CronJob (optional)"] -->|"creates on schedule"| JOB["Job"]
USER["Person or pipeline"] -->|"creates directly"| JOB
JOB -->|"creates and tracks"| P1["Pod 1"]
JOB --> P2["Pod 2"]
JOB --> P3["Pod 3"]
P1 --> C1["Container runs the task"]
P2 --> C2["Container runs the task"]
P3 --> C3["Container runs the task"]
JOB -->|"records"| RES["Result: succeeded and failed counts"]
The diagram shows the chain of ownership. A person, a pipeline, or a CronJob creates the Job. The Job creates and tracks pods. Each pod runs the container that does the real work. The Job keeps a running count of how many pods succeeded and how many failed, and this count decides when it is finished. Because the Job owns its pods, deleting the Job also deletes them.
Two details worth remembering
First, Kubernetes creates the Job’s labels and selector automatically, so that the Job can tell its own pods apart from everyone else’s. You should almost never edit them by hand. Second, the pod template of a Job cannot be changed after the Job is created. If you need a different image or command, create a new Job. This rule protects you from a Job whose pods are half old and half new.
4The Job Lifecycle, Step by Step
Let us follow one Job from the moment it is created until it disappears.
sequenceDiagram
participant U as User
participant A as kube-apiserver
participant J as Job controller
participant S as kube-scheduler
participant K as kubelet on a node
participant T as TTL controller
U->>A: Create Job
A->>J: New Job noticed
J->>A: Create Pod
S->>A: Bind Pod to a node
K->>A: Notice Pod on my node
K->>K: Run the container
K->>A: Report Pod Failed
J->>J: Count the failure and wait for the backoff delay
J->>A: Create replacement Pod
K->>A: Report Pod Succeeded
J->>A: Mark Job Complete
T->>A: Delete Job after the TTL expires
The Job is created
You submit it to the API server, the front door of the cluster. The Job is saved in the cluster’s database, etcd.
The Job controller notices
A controller is a small program that compares what you asked for with what exists. The Job controller sees a Job with no pods and creates the first ones, up to the allowed parallelism.
Pods get homes and start
The scheduler picks a node for each pod. The node’s kubelet, its local manager, starts the containers.
The work runs
The container does its task and exits. An exit code of zero means success. Any other number means failure.
The controller counts
Each success moves the Job closer to the goal. Each failure is counted against the retry limit, and a replacement pod is created after a short, growing delay.
The Job finishes
Once enough pods have succeeded, the Job becomes Complete. If the retry limit or time limit is reached first, it becomes Failed.
Cleanup
Finished pods are kept so that you can read their logs. When the time-to-live expires, the Job and its pods are deleted.
Finished pods do not use CPU or memory, but they still exist as records in the cluster. That is why cleanup matters when you run thousands of Jobs.
5Job States and Conditions
A Job reports how it is doing through a few counters and a short list of conditions.
stateDiagram-v2
[*] --> Active: Job created
Active --> Suspended: suspend set to true
Suspended --> Active: suspend set to false
Active --> Complete: Enough pods succeeded
Active --> Failed: Retry limit or deadline reached
Complete --> [*]
Failed --> [*]
An active Job is still working: pods are running or being created. A suspended Job is paused; no pods run until you resume it. A complete Job reached its goal. A failed Job gave up. Complete and Failed are final: a finished Job never wakes up again.
| Field or condition | What it tells you |
|---|---|
| active | How many pods are running or waiting to run right now |
| succeeded | How many pods have finished successfully so far |
| failed | How many pods have failed so far |
| Complete condition | The Job reached its target number of successes |
| Failed condition | The Job gave up. The reason is BackoffLimitExceeded when it used up its retries, or DeadlineExceeded when it ran out of time |
| Suspended condition | The Job is paused and not running any pods |
| startTime and completionTime | When the Job began and when it finished, so you can measure how long it took |
Think of the suspend setting as a pause button on a music player. The song, meaning the Job, is still in the queue and nothing is lost, but no sound plays. Press play, and the Job starts creating pods. Press pause while pods are running, and those pods are stopped politely.
You can create a Job already suspended. Queueing tools use this to hold a Job back until enough cluster capacity is free, and then release it.
6Retries, Backoff, and Failure Handling
Real tasks fail. A network hiccup, a busy database, or a node crash should not ruin a night of work. Retrying is the heart of a Job.
Two ways a failure can be retried
The restart policy in the pod template decides how a failure is handled.
| Setting | What happens on failure | Good for |
|---|---|---|
| OnFailure | The failed container is restarted inside the same pod, on the same node | Tasks that can safely continue in the same place, and where you want fewer pods created |
| Never | The pod stays as a failed record and the Job creates a brand-new pod | Tasks where you want the failed pod kept for inspection, or need fresh nodes and clean state |
The backoff limit and the growing delay
The backoff limit is the number of failures the Job will tolerate, six by default. Once that many failures have been counted, the Job is marked Failed. Between retries, the Job controller waits a little longer each time: about 10 seconds, then 20, then 40, and so on, up to a ceiling of six minutes. This is called exponential backoff. It gives a struggling system, such as an overloaded database, time to recover instead of hammering it with instant retries.
Imagine a delivery driver who finds nobody home. The first retry comes after a short wait, the next after a longer one, and so on. After six missed attempts, the company stops trying and sends you a notice instead.
A time limit for the whole Job
A Job that retries forever can hide a real problem. Set activeDeadlineSeconds to cap the total time. When the deadline arrives, every running pod is stopped and the Job fails with the reason DeadlineExceeded. The deadline wins over the backoff limit, so even if retries remain, the Job stops.
Smarter rules for failures
Not all failures deserve the same reaction. If a node is drained for maintenance and your pod is evicted, that is not your program’s fault, yet by default it still counts against the retry limit. A pod failure policy lets you write rules based on the container’s exit code or on why the pod was removed. For example, you can say: “if the exit code is 42, which my program uses for a bad input file, fail the whole Job immediately because retrying is pointless” and “if the pod was evicted by the cluster, ignore that failure and do not count it”. Pod failure policies work with the restart policy Never.
Retries mean your task may run more than once for the same piece of work. Design it to be safe when repeated. This idea, called idempotency, is covered later in this guide.
7Parallel Jobs and Completion Patterns
One pod at a time is fine for small tasks. Large tasks need many workers. Jobs support several patterns, chosen by two numbers.
The two numbers are completions, the total successes needed, and parallelism, the number of pods allowed to run together. Depending on how you set them, a Job behaves in one of four ways.
| Pattern | Settings | How it works | Example |
|---|---|---|---|
| Single run | completions 1, parallelism 1 | One pod does the whole task; a replacement is created only on failure | Database migration |
| Fixed count, parallel | completions N, parallelism P | The Job runs P pods at a time until N have succeeded | Process 50 separate files |
| Work queue | parallelism P, completions unset | Pods pull tasks from a shared queue; when one succeeds, the rest wind down | Workers reading a message queue |
| Indexed | completions N, indexed mode | Each pod receives its own number from 0 to N minus 1 and handles that slice | Split a big data set into 100 slices |
Picture a supermarket with ten checkout counters. A fixed-count Job is like ten cashiers working through a known list of 50 trolleys. A work-queue Job is like cashiers who serve whoever is next in the line, until the line is empty. An indexed Job is like exam papers numbered 0 to 99, where each teacher is handed one number and marks only that paper.
How indexed Jobs work
In indexed completion mode, Kubernetes gives every pod a unique number, available to the program as an environment variable called JOB_COMPLETION_INDEX. Your program reads its number and decides which slice of the data to handle. This avoids needing a queue, because the split is decided in advance. If pod 17 fails, only slice 17 is retried. Newer Kubernetes versions can also count retries separately for each index, so one stubborn slice does not use up the retry budget of the whole Job.
Use case: resizing a million photos
A photo service stores images in cloud storage. An indexed Job with 100 completions and a parallelism of 20 starts 20 pods at a time. Pod number 7 reads the seventh slice of ten thousand file names, resizes those photos, and exits. If a pod crashes, only its slice is redone. Twenty workers shrink a job that would take hours on one machine down to a fraction of the time.
Parallelism is a ceiling, not a promise. If the cluster is short of CPU or memory, some pods wait in Pending. The Job simply takes longer; it does not fail.
8Running Jobs at Scale
A single Job is simple. Hundreds of Jobs and thousands of pods need a little more planning.
Tell the cluster what you need
Every pod in a Job should declare its resource requests, the minimum CPU and memory it needs, and limits, the maximum it may use. The scheduler uses requests to decide where a pod fits. Without them, it may pile too many pods onto one node, and the node may run out of memory and start killing pods. Batch work is often hungry, so honest numbers are especially important.
Hold Jobs back until there is room
If you submit 500 heavy Jobs to a small cluster, most pods will sit in Pending while a few run. That is wasteful and noisy. The better approach is a queue. Kubernetes lets you create Jobs in the suspended state, and projects such as Kueue, which is part of the Kubernetes community, watch the cluster’s free capacity and unsuspend Jobs in a fair order. Another project, Volcano, adds batch-oriented scheduling features such as scheduling a whole group of pods at once, which matters when a training job needs all its workers to start together or not at all.
Autoscale the nodes
A node autoscaler adds machines when pods cannot be scheduled, then removes them when the work is done. You pay only while Jobs run.
Use cheaper spot machines
Batch work that can be retried is a good match for spot or preemptible machines, which cost less but may be taken away at short notice.
Give Jobs a priority class
Make sure a low-priority nightly report never crowds out the pods that serve customers.
Clean up automatically
Set a time-to-live so finished Jobs do not pile up and slow down the cluster’s database.
Each pod is a record in etcd, and the control plane must track it. Tens of thousands of finished pods can slow the whole cluster. Prefer one Job with many completions over thousands of separate tiny Jobs, and always clean up.
A queueing system is like a doorman at a busy restaurant. Instead of letting everyone squeeze in and blocking the aisles, the doorman lets in parties as tables free up. Everyone is served, and the kitchen is never overwhelmed.
9CronJob: Running Jobs on a Schedule
Many tasks repeat: every night, every hour, every first day of the month. That is the job of a CronJob.
A CronJob is a Kubernetes object that creates a new Job whenever its schedule says so. It contains the same Job recipe you already know, plus a schedule. The name comes from cron, the classic scheduler on Unix computers.
Reading a schedule
A schedule is written as five fields separated by spaces: minute, hour, day of the month, month, and day of the week. A star means “every”.
| Schedule | Meaning |
|---|---|
| */5 * * * * | Every five minutes |
| 0 * * * * | At the start of every hour |
| 30 2 * * * | Every day at 2:30 in the morning |
| 0 9 * * 1 | Every Monday at 9:00 |
| 0 0 1 * * | At midnight on the first day of each month |
By default, the schedule follows the time zone of the cluster’s control plane, which is often UTC. Newer versions let you set a timeZone field such as a named region, so “9:00 in the morning” means the same thing for your team everywhere and daylight saving changes are handled for you. The shortest interval is one minute.
What if the last run is still going?
Suppose a report takes 70 minutes but is scheduled every hour. When the next hour arrives, the previous Job is still running. The concurrency policy decides what happens.
flowchart TB
T["Scheduled time arrives"] --> Q{"Is the previous Job still running?"}
Q -->|"No"| N["Create a new Job"]
Q -->|"Yes, policy Allow"| N
Q -->|"Yes, policy Forbid"| SK["Skip this run"]
Q -->|"Yes, policy Replace"| R["Stop the old Job then create a new one"]
R --> N
N --> H["Old finished Jobs trimmed by history limits"]
Allow
Start the new Job even if the old one is running. Overlapping runs are possible.
Forbid
Skip the new run if the old one is not finished. Good when two runs would clash.
Replace
Cancel the old run and start a new one. Good when only the newest result matters.
startingDeadlineSeconds
How late a run may start after its scheduled time. If it cannot start in time, that run is skipped.
History limits
By default a CronJob keeps the last three successful and the last one failed Job, so you can read logs but not fill the cluster.
suspend
Stops future runs without deleting the CronJob. Runs already started carry on.
A CronJob promises “at least once” behaviour, not “exactly once”. In rare situations a run may be created twice, or missed. Write your task so that running it twice does no harm. Also note that a CronJob’s name may not be longer than 52 characters, because Kubernetes adds a suffix when it creates each Job.
A CronJob is like a repeating reminder on your phone. Each time the alarm rings, a new to-do item (a Job) is created and handled. The reminder itself keeps ringing on schedule, and the old to-do items are tidied away after a while.
10How Jobs Work Inside the Cluster
Behind every Job there is a small team of controllers quietly doing their rounds.
All the smarts of Jobs live in the kube-controller-manager, a program in the control plane that hosts many controllers. Each controller runs a simple, endless loop known as reconciliation: look at the desired state, look at the actual state, and act to close the gap.
| Component | Its role in a Job |
|---|---|
| API server | Receives the Job, validates it, and stores it |
| Job controller | Creates pods, counts successes and failures, applies retry and deadline rules, and sets the final condition |
| CronJob controller | Creates Jobs at the scheduled times and trims old ones |
| Scheduler | Chooses a node for each pod |
| Kubelet | Runs the containers on the node and reports how they ended |
| TTL controller | Deletes finished Jobs after their time-to-live |
| Garbage collector | Removes pods whose owner Job has been deleted |
Keeping an honest count
How does the Job controller know exactly how many pods succeeded, even if some pods are deleted or the controller itself restarts? It attaches a small marker called a finalizer to each of its pods. A finalizer tells the cluster “do not fully delete this pod until I have recorded its result”. Only after the Job controller has added the pod to its count does it remove the marker. This prevents a success from being counted twice, or a failure from being forgotten.
Imagine a teacher collecting homework. Before a student may throw away their draft, the teacher must first tick their name on the class list. The tick guarantees that no homework is lost or counted twice, even if the teacher takes a break in the middle.
Because the control plane, not your pod, keeps the score, a Job survives a lot of turbulence: nodes can die, the controller can restart, and the counts stay correct.
11Data, Storage, and Idempotency
A Job’s pods are temporary. The results of the work must go somewhere that lasts.
Where do the results go?
When a pod ends, everything written inside its own filesystem is lost when the pod is removed. So a Job should send its output to a place outside the pod.
Object storage
Services like Amazon S3 or Google Cloud Storage suit files, exports, and reports. Any pod on any node can reach them.
Databases
Write records to a database or publish messages to a queue for other systems to pick up.
Persistent volumes
A volume that outlives pods. Parallel pods can share one only if the storage type supports several writers.
Temporary volumes
Good for working files while the pod runs, or for sharing files between an init container and the main container. They vanish with the pod.
Settings and credentials should come in through ConfigMaps and Secrets rather than being baked into the image. And do not forget logs: they disappear when finished pods are deleted, so a log collector should copy them elsewhere.
Idempotency: safe to repeat
A task is idempotent if doing it twice has the same effect as doing it once. Retries, node failures, and the “at least once” nature of CronJobs mean your task will sometimes run again over work that is already partly or fully done.
Pressing the “on” button of a light switch twice leaves the light on. That is idempotent. Pressing a “toggle” switch twice turns the light off again. That is not. Design your Jobs like the on button.
Safe to repeat
- Write a result file under a name based on the input, replacing any old copy.
- Insert or update a record using a unique key, so duplicates are impossible.
- Record progress and skip items already done.
- Use a temporary name while writing and rename only when finished.
Dangerous to repeat
- Adding a fresh row every run with no unique key.
- Sending an email or payment each time without checking if it was sent.
- Appending to a shared file with no checks.
- Incrementing a counter as the only record of progress.
12Advantages, Disadvantages, and Trade-offs
Jobs are excellent at what they do, and they are not meant to do everything.
Advantages
- Built-in retries: Failures are handled automatically, with sensible delays.
- Clear verdict: Complete or Failed, with counts and timestamps.
- Parallel by design: Scale from one worker to hundreds with two numbers.
- Pay for what you use: Pods exist only while the work runs.
- Same tools as the rest: Images, secrets, quotas, and monitoring all apply.
Disadvantages
- Simple by nature: A Job does not understand steps that depend on each other.
- Immutable template: Changing the recipe means creating a new Job.
- Cleanup is your job: Old Jobs and pods pile up if you forget the time-to-live.
- Start-up cost: Each pod needs scheduling and an image pull, so very short tasks carry overhead.
- At-least-once running: Tasks must be safe to repeat.
Choosing between related tools
| You need | Best choice | Why |
|---|---|---|
| One task with retries | Job | Simple and built in |
| The same task on a timetable | CronJob | Schedule plus Job in one object |
| Steps with dependencies, branching, or approvals | A workflow engine such as Argo Workflows or Apache Airflow | They model graphs of tasks and run each step as a pod |
| A worker that handles messages forever | Deployment | It should never end, and a queue system feeds it |
| Thousands of tiny tasks per second | A queue with long-lived workers | Creating a pod per task is too slow and costly |
If a task takes less than a few seconds, the time to start a pod may exceed the time to do the work. Group many small items into one Job run, or use long-lived workers.
13Security for Jobs
Jobs often touch valuable data and run with real credentials, so they deserve the same care as any service.
Control who can create Jobs
Anyone who may create a Job can run any container in that namespace. Grant this power carefully using role-based access control.
A narrow service account
Give each Job a service account with only the permissions it needs, such as read access to one storage bucket, and nothing more.
Keep credentials out of images
Pass passwords and keys through Secrets or a secrets manager, never in the command line or image.
Least privilege
Run as a non-root user, drop unneeded Linux powers, and block privilege escalation.
Trusted images
Use images from your own registry, pinned to a version or a digest, and scan them for known flaws.
Quotas and network rules
Use resource quotas to cap how many pods a namespace can create, and network policies to limit where Jobs can connect.
A CronJob with a bug can create Jobs faster than they finish and quietly fill a cluster. A namespace resource quota, a Forbid concurrency policy, and a deadline on each Job are the three safety belts against this.
Short life is a security advantage. Because Job pods exist only for the time needed, credentials they hold live in the cluster only briefly. Combine that with a time-to-live, and few traces are left behind once the work is done.
14Monitoring, Logging, and Metrics
A Job that fails at 3 a.m. is only a problem if nobody notices. Good monitoring turns silent failures into clear alerts.
| What to watch | Where it comes from | Why it matters |
|---|---|---|
| Failed Jobs | kube-state-metrics: job failed and job status failed | A Job that exhausted its retries needs a human |
| Successful pods | kube-state-metrics: job status succeeded | Shows progress of a long parallel Job |
| Last schedule time of a CronJob | kube-state-metrics: cronjob last schedule time | Detects a schedule that silently stopped running |
| Job duration | Start and completion times | A run that suddenly takes twice as long often signals trouble |
| Pod restarts and exit codes | Pod status | Reveals crashes, memory kills, and bad inputs |
| Application logs | Log collector on each node | Explain why a run failed, even after its pods are gone |
Alerts that help
- Job failed: page someone, or open a ticket, as soon as a Job enters the Failed state.
- Missed schedule: alert when a CronJob has not started a run for longer than expected.
- Too slow: alert when a Job runs far beyond its usual duration, before the deadline kills it.
- Too many leftovers: alert when finished Jobs or pods grow past a sensible number.
Finally, remember that logs from finished pods vanish with the pods. Ship logs to a central store as they are produced. It is also useful to have the task print a short summary at the end, such as items processed, items skipped, and time taken, so that a quick look at the logs tells the story.
For very important CronJobs, add a “dead man’s switch”: the task reports success to a monitoring service, and that service raises an alarm if no report arrives on time. This catches the case where the CronJob itself never ran.
15Troubleshooting Jobs
When a Job misbehaves, the symptom usually points straight at the cause.
| Symptom | Likely cause | Where to look |
|---|---|---|
| Job exists but no pods appear | The Job is suspended, a namespace quota is full, or the template is invalid | Job conditions and events |
| Pods stay Pending | Not enough CPU or memory, untolerated taints, or waiting for a new node | Pod events |
| ImagePullBackOff | Wrong image name or tag, or missing registry credentials | Pod events |
| Pods fail again and again | The application has an error, a bad setting, or cannot reach a dependency | Logs of the failed pods, exit codes |
| Failed with BackoffLimitExceeded | The retry limit was used up | Logs of each failed pod; look for a repeating cause |
| Failed with DeadlineExceeded | The work took longer than the time limit | Job duration history; raise the limit or speed up the task |
| Job Complete but results missing | The program exited with code 0 despite an error, or wrote to a temporary disk that was lost | Application logs and output location |
| CronJob never runs | It is suspended, the schedule is wrong, a time zone is misunderstood, or the start deadline was missed | CronJob status and last schedule time |
| Hundreds of old pods remain | No time-to-live and no history limits | Job spec and CronJob spec |
| Work seems done twice | A retry or a duplicate CronJob run repeated it | Task design; add idempotency |
A calm routine
Describe the Job
Read its conditions, counters, and events. They often name the reason directly.
List its pods
Use the Job’s name label to find them and see which are Pending, Running, Failed, or Completed.
Read the logs and exit code
Look at a failed pod first. Its last lines and exit code usually explain the failure.
Change and re-create
Since the template cannot be edited, fix the recipe and create a new Job. Delete the old one once you have what you need from it.
Setting the restart policy to Never keeps each failed pod as a record, so you can read the logs of every attempt. With OnFailure the same pod is reused and earlier attempts are harder to inspect.
16Design Patterns and Anti-patterns
Teams that run batch work well keep repeating the same good habits. Trouble also has repeating shapes.
Patterns that work
Idempotent tasks
Every run is safe to repeat, so retries and duplicate runs are harmless.
Indexed slicing
Split big data into numbered slices, one per pod, so only the failed slice is retried.
Init container for setup
Download inputs or wait for a dependency in an init container, so the main container focuses on the work.
Queue in front, Jobs behind
Submit work to a queueing system that releases Jobs as capacity allows.
Deadline plus limit plus TTL
Every Job gets a time limit, a retry limit, and an automatic cleanup time.
Anti-patterns to avoid
What people do
They write tasks that are not safe to repeat, such as inserting rows without a unique key or sending emails without checking.
Why it hurts
Retries and duplicate CronJob runs then create double charges, duplicate emails, and messy data.
Do this instead
Use unique keys, record what was already done, and write results under predictable names so a second run simply overwrites the first.
What people do
They never set a time-to-live or history limit, so finished Jobs and pods remain for months.
Why it hurts
The cluster’s database fills with thousands of dead records, listing commands slow down, and the control plane becomes sluggish.
Do this instead
Set ttlSecondsAfterFinished on Jobs and sensible history limits on CronJobs. Keep logs in a central store instead.
What people do
They use a Job as a long-running service, or run a Deployment that sleeps and waits for work.
Why it hurts
A Job has a deadline and a retry limit and is expected to end. A sleeping Deployment wastes resources and cannot report completion.
Do this instead
Use a Job for work that ends, and a Deployment fed by a queue for work that never ends.
What people do
They allow many overlapping CronJob runs, with no limits, for a task that takes longer than its interval.
Why it hurts
Runs pile up, compete for the same data, and can exhaust the cluster.
Do this instead
Use the Forbid policy, or lengthen the interval, and always add a deadline.
17Best Practices and Common Mistakes
A short checklist that suits nearly every Job, followed by the slips seen most often.
Best practices
Exit honestly
Return zero only on real success and a non-zero code on failure. The Job can only judge by the exit code.
Set every safety limit
Pick a retry limit, a time limit, and a time-to-live on every Job.
Request the right resources
Measure a real run and set CPU and memory requests and limits from it.
Handle shutdown signals
Pods may be stopped early by a drain or a deadline. Save progress on SIGTERM so the next attempt can resume.
Use exact image versions
Pin images to a fixed tag or digest, so retries and reruns behave identically.
Alert on results, not only on infrastructure
Watch for failed Jobs and missed CronJob runs, not just node health.
Common mistakes
Restart policy Always
Kubernetes rejects it for Jobs. Choose OnFailure or Never.
Editing a running Job’s template
The template cannot change. Create a new Job with the fixed recipe.
Saving results inside the pod
Files on the pod’s own disk disappear. Save to storage that lasts.
Guessing the time zone
A schedule may follow UTC. Set the time zone explicitly to avoid runs at surprising hours.
18Real-World and Industry Examples
Batch work on Kubernetes shows up in many well-known places.
Apache Airflow and Airbnb
Apache Airflow, the workflow tool created at Airbnb, offers a Kubernetes executor that runs each task of a workflow in its own pod. Every task gets a clean environment, its own resources, and a clear success or failure, which is the same idea as a Job applied to a chain of steps.
GitLab CI pipelines
GitLab Runner can use Kubernetes to run each pipeline job in a fresh pod. When a build finishes, the pod disappears, so thousands of builds a day use cluster capacity only while they are running.
CERN and scientific computing
CERN, the European physics laboratory, has publicly discussed running batch-style data-processing workloads on Kubernetes. Physics experiments produce huge data sets that must be split into many independent slices and processed in parallel, which is exactly the pattern that indexed Jobs describe.
Large-scale AI training
Organisations such as OpenAI have described using Kubernetes for very large machine-learning training on thousands of nodes. Training is batch work with a start and an end, and it benefits from queueing, gang-style scheduling, and retry rules, the same features covered in this guide.
Scenario: a monthly invoice run
Picture a telecom company that must produce five million invoices on the first of each month. A CronJob starts an indexed Job with 200 slices at 1 a.m. Each pod produces about 25,000 invoices, writes them to object storage under names derived from the customer number, and exits. A node fails at 2 a.m., so one slice is retried, and because the file names are predictable, the retry safely replaces the partial output. By 3 a.m. the Job is Complete, and the finished pods are removed an hour later by the time-to-live.
19Frequently Asked Questions
20Summary and Key Takeaways
Key Takeaways
- A Job runs work that must end. It creates pods, retries failures, and stops when enough pods have succeeded.
- Choose it over a Deployment or bare pod whenever a task has a finish line and you need a clear verdict of Complete or Failed.
- Two numbers shape parallel work: completions say how many successes are needed, and parallelism says how many pods run together. Indexed mode gives each pod its own slice.
- Failure handling is built in. Use the restart policy, backoff limit, deadline, and pod failure policy to decide how hard to try and when to give up.
- A CronJob is a schedule that creates Jobs. Set the time zone, the concurrency policy, and history limits, and expect at-least-once behaviour.
- Make tasks idempotent and store results outside the pod, because retries and reruns are normal and pod disks are temporary.
- Set limits and clean up. Give every Job a deadline, a retry limit, a time-to-live, honest resource requests, and alerts for failures and missed runs.