Ravindra BagaleCourses & study guides मराठी

Chapter 18: Kubernetes Workloads

18.10 Job and CronJob

Why: the problem

Not every program runs forever. At HDFC the nightly statement report runs once, writes a file and exits. A database migration runs once before a release. A cleanup script deletes old uploads every Sunday at 2 a.m. If you put these in a Deployment, Kubernetes restarts them again and again, because a Deployment thinks an exit is a crash. A Job is made for work that should finish. It runs Pods until a set number of them succeed, retries failures up to backoffLimit, and can stop after a time limit. A CronJob creates a Job on a schedule, using the same five-field cron format as Linux. In this lesson you will run a one-time Job, a parallel Job that processes several items, a Job that fails and retries, and a CronJob every minute, with history limits and safe concurrency.

What we will build

You will run a Job that computes pi, a parallel Job with 6 completions and 2 at a time, a failing Job that hits backoffLimit, and a CronJob that writes a timestamp every minute. You will also trigger a CronJob by hand and suspend it.

What: every term in plain English

  • Job — A controller that runs Pods until a number of them finish successfully.
  • completions — How many successful Pods are needed.
  • parallelism — How many Pods may run at the same time.
  • backoffLimit — How many retries before the Job is marked Failed. Default 6.
  • activeDeadlineSeconds — The most time the whole Job may run.
  • ttlSecondsAfterFinished — Delete the finished Job automatically after this many seconds.
  • CronJob — Creates a Job on a cron schedule.
  • concurrencyPolicy — Allow, Forbid or Replace. What to do if the last run is still going.
  • Cron format — minute hour day-of-month month day-of-week, for example 0 2 * * 0.
Start a lab cluster Make a practice namespace Run a one-time Job Wait for it Read the result See the Job and its Pod Create a CronJob
Job and CronJob

How: step by step

  1. Start a lab cluster

    kind create cluster --name lab
    kubectl get nodes
    

    What it does: Creates a one-node Kubernetes cluster inside Docker. On minikube use: minikube start.

    Output

    Creating cluster "lab" ...
     ✓ Ensuring node image (kindest/node:v1.30.0)
     ✓ Preparing nodes
    Set kubectl context to "kind-lab"
    NAME                STATUS   ROLES           AGE   VERSION
    lab-control-plane   Ready    control-plane   40s   v1.30.0
    
  2. Make a practice namespace

    kubectl create namespace practice
    kubectl config set-context --current --namespace=practice
    

    What it does: Keeps all lab objects in one folder that is easy to delete.

    Output

    namespace/practice created
    Context "kind-lab" modified.
    
  3. Run a one-time Job

    kubectl create job pi --image=perl:5.38 -- perl -Mbignum=bpi -wle "print bpi(200)"
    

    What it does: Computes pi to 200 digits and exits.

    Output

    job.batch/pi created
    
  4. Wait for it

    kubectl wait --for=condition=complete job/pi --timeout=120s
    

    What it does: Blocks until the Job succeeds.

    Output

    job.batch/pi condition met
    
  5. Read the result

    kubectl logs job/pi | cut -c1-40
    

    What it does: The Pod logs stay until the Job is deleted.

    Output

    3.14159265358979323846264338327950288419
    
  6. See the Job and its Pod

    kubectl get job,pods -l job-name=pi
    

    What it does: COMPLETIONS 1/1 and the Pod is Completed.

    Output

    NAME           STATUS     COMPLETIONS   DURATION
    job.batch/pi   Complete   1/1           14s
    NAME           READY   STATUS      
    pod/pi-7xk2m   0/1     Completed
    
  7. Create a CronJob

    kubectl create cronjob tick --image=busybox:1.36 --schedule="*/1 * * * *" -- sh -c "date; echo tick"
    

    What it does: Runs every minute.

    Output

    cronjob.batch/tick created
    

Example 1: A parallel Job: 6 items, 2 at a time

Stack. Kubernetes Jobs, Indexed mode

Goal. A parallel Job: 6 items, 2 at a time.

batch.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: resize
spec:
  completions: 6
  parallelism: 2
  completionMode: Indexed
  backoffLimit: 2
  ttlSecondsAfterFinished: 600
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: worker
        image: busybox:1.36
        command: ["sh", "-c", "echo resizing photo-$JOB_COMPLETION_INDEX.jpg; sleep 5; echo done $JOB_COMPLETION_INDEX"]

terminal

kubectl apply -f batch.yaml
kubectl get pods -l job-name=resize -w
kubectl logs -l job-name=resize --prefix | sort

What each line does

  • completions: 6 — Six Pods must succeed.
  • parallelism: 2 — Only two run at once.
  • completionMode: Indexed — Each Pod gets a number 0 to 5 in JOB_COMPLETION_INDEX.
  • restartPolicy: Never — Jobs must use Never or OnFailure.
  • ttlSecondsAfterFinished: 600 — The Job cleans itself up after 10 minutes.

Green output

job.batch/resize created
resize-0-x2k4p   1/1   Running
resize-1-m7qz9   1/1   Running
resize-0-x2k4p   0/1   Completed
resize-2-t5vn3   1/1   Running
...
[pod/resize-0-x2k4p/worker] resizing photo-0.jpg
[pod/resize-0-x2k4p/worker] done 0
[pod/resize-1-m7qz9/worker] resizing photo-1.jpg
[pod/resize-1-m7qz9/worker] done 1

Expected result. This is how teams split batch work, like resizing uploads or sending reports, across Pods.

Example 2: A failing Job and backoffLimit

Stack. Kubernetes Jobs

Goal. A failing Job and backoffLimit.

fail.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: migrate
spec:
  backoffLimit: 3
  activeDeadlineSeconds: 120
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: migrate
        image: busybox:1.36
        command: ["sh", "-c", "echo running migration; echo ERROR: cannot reach db:5432; exit 1"]

terminal

kubectl apply -f fail.yaml
sleep 60
kubectl get job migrate
kubectl get pods -l job-name=migrate
kubectl describe job migrate | grep -A2 BackoffLimitExceeded

What each line does

  • backoffLimit: 3 — Try at most 3 retries.
  • activeDeadlineSeconds: 120 — Give up after 2 minutes whatever happens.
  • exit 1 — A failed run.
  • Several Pods — With restartPolicy Never each retry is a new Pod, so logs of every try are kept.

Green output

job.batch/migrate created
NAME      STATUS   COMPLETIONS   DURATION
migrate   Failed   0/1           58s
NAME            READY   STATUS   
migrate-4kx9p   0/1     Error
migrate-8bq2z   0/1     Error
migrate-j7m3c   0/1     Error
migrate-w2n6t   0/1     Error
  Warning  BackoffLimitExceeded  5s  job-controller  Job has reached the specified backoff limit

Expected result. A CI pipeline can wait on this Job and stop the release when it fails: kubectl wait --for=condition=failed.

Example 3: A safe CronJob for a weekly cleanup

Stack. Kubernetes CronJob, timeZone field (GA in 1.27)

Goal. A safe CronJob for a weekly cleanup.

cleanup.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup
spec:
  schedule: "0 2 * * 0"
  timeZone: "Asia/Kolkata"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 300
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: OnFailure
          containers:
          - name: clean
            image: busybox:1.36
            command: ["sh", "-c", "echo deleting uploads older than 30 days; echo removed 214 files"]

terminal

kubectl apply -f cleanup.yaml
kubectl create job cleanup-manual --from=cronjob/cleanup
kubectl wait --for=condition=complete job/cleanup-manual
kubectl logs job/cleanup-manual
kubectl patch cronjob cleanup -p '{"spec":{"suspend":true}}'
kubectl get cronjob cleanup

What each line does

  • schedule 0 2 * * 0 — Sunday at 2:00.
  • timeZone — Run at 2 a.m. India time, not UTC.
  • concurrencyPolicy: Forbid — Skip a run if the last one is still going.
  • history limits — Keep only a few old Jobs.
  • create job --from=cronjob — Test it now, without waiting for Sunday.
  • suspend: true — Pause the schedule without deleting it.

Green output

cronjob.batch/cleanup created
job.batch/cleanup-manual created
job.batch/cleanup-manual condition met
deleting uploads older than 30 days
removed 214 files
cronjob.batch/cleanup patched
NAME      SCHEDULE    TIMEZONE       SUSPEND   ACTIVE   LAST SCHEDULE
cleanup   0 2 * * 0   Asia/Kolkata   True      0        none

Expected result. Always test a CronJob with create job --from before trusting the schedule.

Verify: did it work?

  1. See CronJob runs

    kubectl get jobs -l job-name --sort-by=.metadata.creationTimestamp; kubectl get jobs
    

    What it does: Each minute a new tick Job appears; old ones are trimmed to the history limit.

    Output

    NAME            STATUS     COMPLETIONS   AGE
    tick-28800001   Complete   1/1           2m
    tick-28800002   Complete   1/1           1m
    tick-28800003   Complete   1/1           5s
    
  2. Read the last run

    kubectl logs job/$(kubectl get jobs -o name | grep tick | tail -1 | cut -d/ -f2)
    

    What it does: Shows output of the newest tick.

    Output

    Sun Oct 25 06:31:00 UTC 2026
    tick
    

When it is red 1

The Job "pi" is invalid: spec.template.spec.restartPolicy: Required value: valid values: "OnFailure", "Never"

Cause. restartPolicy Always is not allowed in a Job.

Fix, step by step

  1. Set restartPolicy: Never or OnFailure in the Pod template.

When it is red 2

Cannot determine if job needs to be started: too many missed start times (> 100)

Cause. The CronJob controller was down or the schedule was suspended for long.

Fix, step by step

  1. Set startingDeadlineSeconds, for example 300.
  2. Check the controller manager is healthy.

When it is red 3

Job runs twice at the same time and data is corrupted

Cause. concurrencyPolicy is Allow (the default) and the job took longer than the interval.

Fix, step by step

  1. Set concurrencyPolicy: Forbid.
  2. Make the script safe to run twice.

Cleanup

  1. Delete Jobs and CronJobs

    kubectl delete job --all
    kubectl delete cronjob --all
    

    What it does: Removes everything batch related in the namespace.

    Output

    job.batch "pi" deleted
    ...
    cronjob.batch "tick" deleted
    cronjob.batch "cleanup" deleted
    
  2. Delete the practice namespace

    kubectl delete namespace practice
    

    What it does: Deletes every object you made in this lesson in one go.

    Output

    namespace "practice" deleted
    

Ravindra Bagale's Tip

Kaam sampla ki Pod band, tyasathi Job. Thraavik velet chalvaycha asel tar CronJob, ani timeZone Asia/Kolkata theva. Lakshat theva!

Practice tasks

Easy Read cron

Why: Cron mistakes run jobs at wrong times.

What to do: Write cron lines for: every 15 minutes, 9 a.m. weekdays, 1st of month midnight.

How:
  1. */15 * * * *
  2. 0 9 * * 1-5
  3. 0 0 1 * *

Check: All three are correct.

Medium Gate a release

Why: CI uses Jobs for migrations.

What to do: Run a migration Job and wait on success or failure in a script.

How:
  1. kubectl apply -f migrate.yaml
  2. kubectl wait --for=condition=complete job/migrate --timeout=60s || exit 1

Check: The script exits 1 on failure.

Hard Work queue

Why: Real batch systems.

What to do: Run a Job with parallelism 3 that pops items from a Redis list until empty.

How:
  1. Run Redis.
  2. Push 10 items.
  3. Workers use redis-cli LPOP.

Check: All 10 items processed once.

Easy Say Jobs and CronJobs in two lines

Why: If you can say it simply, you understand it. Interviewers want the short version first.

What to do: Write two short lines about Jobs and CronJobs for a fresher who knows only Docker basics.

How:
  1. Write what problem it solves.
  2. Write the one kubectl command you use for it.
  3. Read it aloud to a friend.

Check: A fresher understands it without asking a question.

Easy Start a fresh lab cluster

Why: Practice needs a throwaway cluster. If you break it, you delete it and start again.

What to do: Create a kind cluster, or a minikube cluster, and check the node is Ready.

How:
  1. kind create cluster --name lab
  2. kubectl get nodes
  3. kubectl get pods -A

Check: One node shows STATUS Ready and the kube-system Pods are Running.

Medium Break it on purpose

Why: You must know the red message by face. During an outage there is no time to read slowly.

What to do: Cause one error from the "When it is red" part of this lesson and fix it.

How:
  1. Follow the error section.
  2. Copy the exact message and the kubectl describe Events into your notes.
  3. Write the fix in one line.

Check: You fixed it using only the steps, without searching the web.

Medium Write a one-page runbook

Why: At 2 a.m. nobody remembers the right command. A short page saves the on-call person.

What to do: Write a runbook page for this lesson.

How:
  1. Title, when to use, exact kubectl command, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Repeat it on a cloud cluster

Why: kind is a laptop toy. Real clusters run on EKS, GKE, AKS or k3s on a VM.

What to do: Do the main steps of this lesson again on a small k3s VM or a free-tier managed cluster.

How:
  1. Install k3s on one Ubuntu 22.04 VM: curl -sfL https://get.k3s.io | sh -
  2. Copy the kubeconfig.
  3. Apply the same YAML and compare the output.

Check: Same output on the cloud cluster as on kind.

Hard Explain it like an interview

Why: Interviewers want real stories, not definitions.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the YAML field you used, the kubectl command you ran, and one error you hit.

Check: Your answer has a real command and a real error message.

Hard Draw the flow top to bottom

Why: A drawing shows who talks to whom and in what order.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at kubectl apply, go through the API server, controller and scheduler, end at the running container.

Check: Each box matches a real thing you saw in kubectl output.

Homework: try at home

Try at home

At home, write a CronJob that runs every 2 minutes in Asia/Kolkata time, uses Forbid, keeps 2 successful Jobs, and prints the date. Trigger it once by hand, then suspend it.

Recap

  • A Job runs Pods until they succeed.
  • completions and parallelism split batch work.
  • backoffLimit and activeDeadlineSeconds stop endless retries.
  • A CronJob makes Jobs on a schedule, with timeZone.
  • Use Forbid, history limits and create job --from for safety.

FAQ

Why not run the cron inside a container?

Then one Pod must run forever and you lose retries, history and kubectl logs per run.

Default time zone?

The controller time zone, usually UTC. Set timeZone to be sure.

Can a CronJob run every 10 seconds?

No. The smallest unit is one minute.

Are Job logs kept?

As long as the Pods exist. Ship logs to a central store for long history.

What is Indexed mode for?

Each Pod gets a fixed index, useful to split a list of items.

Interview questions

  1. Job versus Deployment?

    A Job runs to completion; a Deployment keeps Pods running forever and restarts them if they exit.

  2. How do you stop a Job from retrying forever?

    backoffLimit and activeDeadlineSeconds.

  3. What is concurrencyPolicy?

    It decides if a CronJob may start a new run while the previous is running: Allow, Forbid or Replace.

  4. How do you clean old Jobs?

    ttlSecondsAfterFinished and successfulJobsHistoryLimit or failedJobsHistoryLimit.

  5. How do you test a CronJob without waiting?

    kubectl create job --from=cronjob/NAME.

Quick quiz

Q1. Which restartPolicy values are allowed in a Job?

Never and OnFailure.

Q2. What does parallelism: 2 mean?

At most two Pods run at once.

Q3. What does concurrencyPolicy: Forbid do?

Skips a new run while the last one is still running.

Q4. How do you run a CronJob now?

kubectl create job NAME --from=cronjob/CRON.

Q5. What cleans up finished Jobs?

ttlSecondsAfterFinished or the CronJob history limits.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards