8 minute read

Kubernetes Jobs and CronJobs

Jobs

A Job creates one or more Pods and will continue to retry execution of the Pods until a specified number of them successfully terminate. A simple case is to create one Job object in order to reliably run one Pod to completion. The Job object will start a new Pod if the first Pod fails or is deleted (for example due to a node hardware failure or a node reboot).

lab@k8s1:~$ kubectl get job
No resources found in default namespace.
lab@k8s1:~$ kubectl get job -A
No resources found
lab@k8s1:~$
lab@k8s1:~$ kubectl create job -h
Create a job with the specified name.

Examples:
  # Create a job
  kubectl create job my-job --image=busybox

  # Create a job with a command
  kubectl create job my-job --image=busybox -- date

  # Create a job from a cron job named "a-cronjob"
  kubectl create job test-job --from=cronjob/a-cronjob

Options:
      --allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in
the template. Only applies to golang and jsonpath output formats.
      --dry-run='none': Must be "none", "server", or "client". If client strategy, only print the object that would be
sent, without sending it. If server strategy, submit server-side request without persisting the resource.
      --field-manager='kubectl-create': Name of the manager used to track field ownership.
      --from='': The name of the resource to create a Job from (only cronjob is supported).
      --image='': Image name to run.
  -o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file.
      --save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
      --show-managed-fields=false: If true, keep the managedFields when printing objects in JSON or YAML format.
      --template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
      --validate=true: If true, use a schema to validate the input before sending it

Usage:
  kubectl create job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...] [options]

Use "kubectl options" for a list of global command-line options (applies to all commands).
lab@k8s1:~$
lab@k8s1:~$ cat job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4
lab@k8s1:~$
lab@k8s1:~$ kubectl create -f job.yaml
job.batch/pi created
lab@k8s1:~$
lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     0/1           4s         4s
lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     0/1           6s         6s
lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     0/1           7s         7s

lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           36s        44s
lab@k8s1:~$
lab@k8s1:~$ kubectl describe job
Name:             pi
Namespace:        default
Selector:         controller-uid=b2835c58-d4fd-4d9f-b715-b26fdecd2c19
Labels:           controller-uid=b2835c58-d4fd-4d9f-b715-b26fdecd2c19
                  job-name=pi
Annotations:      <none>
Parallelism:      1
Completions:      1
Completion Mode:  NonIndexed
Start Time:       Fri, 01 Apr 2022 02:16:45 -0700
Completed At:     Fri, 01 Apr 2022 02:17:21 -0700
Duration:         36s
Pods Statuses:    0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:  controller-uid=b2835c58-d4fd-4d9f-b715-b26fdecd2c19
           job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:       <none>
    Host Port:  <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  58s   job-controller  Created pod: pi--1-vb4sm
  Normal  Completed         22s   job-controller  Job completed
lab@k8s1:~$
lab@k8s1:~$ kubectl describe pod pi--1-vb4sm
Name:         pi--1-vb4sm
Namespace:    default
Priority:     0
Node:         k8s3/10.210.40.175
Start Time:   Fri, 01 Apr 2022 02:16:45 -0700
Labels:       controller-uid=b2835c58-d4fd-4d9f-b715-b26fdecd2c19
              job-name=pi
Annotations:  <none>
Status:       Succeeded
IP:           10.244.3.15
IPs:
  IP:           10.244.3.15
Controlled By:  Job/pi
Containers:
  pi:
    Container ID:  docker://2fb0c18484eaa3eb211fc7ce480764101f08b835df12630083114e160de35f29
    Image:         perl
    Image ID:      docker-pullable://perl@sha256:e85f67a5e83029c49a4ce2a91fbfd86a4073d1d2c171a3fa8957d0d5f52a975c
    Port:          <none>
    Host Port:     <none>
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    State:          Terminated
      Reason:       Completed
      Exit Code:    0
      Started:      Fri, 01 Apr 2022 02:17:16 -0700
      Finished:     Fri, 01 Apr 2022 02:17:20 -0700
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-kwcz6 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             False
  ContainersReady   False
  PodScheduled      True
Volumes:
  kube-api-access-kwcz6:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  4m54s  default-scheduler  Successfully assigned default/pi--1-vb4sm to k8s3
  Normal  Pulling    4m53s  kubelet            Pulling image "perl"
  Normal  Pulled     4m24s  kubelet            Successfully pulled image "perl" in 28.584816314s
  Normal  Created    4m24s  kubelet            Created container pi
  Normal  Started    4m23s  kubelet            Started container pi
lab@k8s1:~$
lab@k8s1:~$ kubectl delete jobs pi
job.batch "pi" deleted
lab@k8s1:~$ kubectl get jobs
No resources found in default namespace.
lab@k8s1:~$

Re-create the Job again.

lab@k8s1:~$ kubectl create -f job.yaml
job.batch/pi created
lab@k8s1:~$
lab@k8s1:~$ kubectl get jobs
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           8s         14s
lab@k8s1:~$ kubectl get pods pi--1-hdwp2
NAME          READY   STATUS      RESTARTS   AGE
pi--1-hdwp2   0/1     Completed   0          27s
lab@k8s1:~$
lab@k8s1:~$ kubectl  logs pi--1-hdwp2
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901
lab@k8s1:~$
lab@k8s1:~$ kubectl delete pods pi--1-hdwp2
pod "pi--1-hdwp2" deleted

lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           8s         2m10s
lab@k8s1:~

CronJobs

A CronJob creates Jobs on a repeating schedule.

lab@k8s1:~$ kubectl create cronjob -h
Create a cron job with the specified name.

Aliases:
cronjob, cj

Examples:
  # Create a cron job
  kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *"

  # Create a cron job with a command
  kubectl create cronjob my-job --image=busybox --schedule="*/1 * * * *" -- date

Options:
      --allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in
the template. Only applies to golang and jsonpath output formats.
      --dry-run='none': Must be "none", "server", or "client". If client strategy, only print the object that would be
sent, without sending it. If server strategy, submit server-side request without persisting the resource.
      --field-manager='kubectl-create': Name of the manager used to track field ownership.
      --image='': Image name to run.
  -o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|template|templatefile|jsonpath|jsonpath-as-json|jsonpath-file.
      --restart='': job's restart policy. supported values: OnFailure, Never
      --save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
      --schedule='': A schedule in the Cron format the job should be run with.
      --show-managed-fields=false: If true, keep the managedFields when printing objects in JSON or YAML format.
      --template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
      --validate=true: If true, use a schema to validate the input before sending it

Usage:
  kubectl create cronjob NAME --image=image --schedule='0/5 * * * ?' -- [COMMAND] [args...] [flags] [options]

Use "kubectl options" for a list of global command-line options (applies to all commands).
lab@k8s1:~$
lab@k8s1:~$ cat cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
lab@k8s1:~$

This example CronJob manifest prints the current time and a hello message every minute:

lab@k8s1:~$ kubectl create -f cronjob.yaml
cronjob.batch/hello created
lab@k8s1:~$
lab@k8s1:~$ kubectl get cronjob
NAME    SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
hello   * * * * *   False     0        <none>          5s
lab@k8s1:~$ kubectl describe cronjob
Name:                          hello
Namespace:                     default
Labels:                        <none>
Annotations:                   <none>
Schedule:                      * * * * *
Concurrency Policy:            Allow
Suspend:                       False
Successful Job History Limit:  3
Failed Job History Limit:      1
Starting Deadline Seconds:     <unset>
Selector:                      <unset>
Parallelism:                   <unset>
Completions:                   <unset>
Pod Template:
  Labels:  <none>
  Containers:
   hello:
    Image:      busybox:1.28
    Port:       <none>
    Host Port:  <none>
    Command:
      /bin/sh
      -c
      date; echo Hello from the Kubernetes cluster
    Environment:     <none>
    Mounts:          <none>
  Volumes:           <none>
Last Schedule Time:  Fri, 01 Apr 2022 02:33:00 -0700
Active Jobs:         <none>
Events:
  Type    Reason            Age   From                Message
  ----    ------            ----  ----                -------
  Normal  SuccessfulCreate  23s   cronjob-controller  Created job hello-27480093
  Normal  SawCompletedJob   19s   cronjob-controller  Saw completed job: hello-27480093, status: Complete
lab@k8s1:~$

After some more time,

lab@k8s1:~$ kubectl describe cronjob
Name:                          hello
Namespace:                     default
Labels:                        <none>
Annotations:                   <none>
Schedule:                      * * * * *
Concurrency Policy:            Allow
Suspend:                       False
Successful Job History Limit:  3
Failed Job History Limit:      1
Starting Deadline Seconds:     <unset>
Selector:                      <unset>
Parallelism:                   <unset>
Completions:                   <unset>
Pod Template:
  Labels:  <none>
  Containers:
   hello:
    Image:      busybox:1.28
    Port:       <none>
    Host Port:  <none>
    Command:
      /bin/sh
      -c
      date; echo Hello from the Kubernetes cluster
    Environment:     <none>
    Mounts:          <none>
  Volumes:           <none>
Last Schedule Time:  Fri, 01 Apr 2022 02:38:00 -0700
Active Jobs:         <none>
Events:
  Type    Reason            Age    From                Message
  ----    ------            ----   ----                -------
  Normal  SuccessfulCreate  5m57s  cronjob-controller  Created job hello-27480093
  Normal  SawCompletedJob   5m53s  cronjob-controller  Saw completed job: hello-27480093, status: Complete
  Normal  SuccessfulCreate  4m57s  cronjob-controller  Created job hello-27480094
  Normal  SawCompletedJob   4m56s  cronjob-controller  Saw completed job: hello-27480094, status: Complete
  Normal  SuccessfulCreate  3m57s  cronjob-controller  Created job hello-27480095
  Normal  SawCompletedJob   3m56s  cronjob-controller  Saw completed job: hello-27480095, status: Complete
  Normal  SuccessfulCreate  2m57s  cronjob-controller  Created job hello-27480096
  Normal  SuccessfulDelete  2m56s  cronjob-controller  Deleted job hello-27480093
  Normal  SawCompletedJob   2m56s  cronjob-controller  Saw completed job: hello-27480096, status: Complete
  Normal  SuccessfulCreate  117s   cronjob-controller  Created job hello-27480097
  Normal  SawCompletedJob   116s   cronjob-controller  Saw completed job: hello-27480097, status: Complete
  Normal  SuccessfulDelete  116s   cronjob-controller  Deleted job hello-27480094
  Normal  SuccessfulCreate  57s    cronjob-controller  Created job hello-27480098
  Normal  SawCompletedJob   56s    cronjob-controller  Saw completed job: hello-27480098, status: Complete
  Normal  SuccessfulDelete  56s    cronjob-controller  Deleted job hello-27480095
lab@k8s1:~$

We can see every minute, a new job gets created, and hence a new pod as well. But from the jobs list, we see only the latest 3 jobs.

lab@k8s1:~$ kubectl get jobs
NAME             COMPLETIONS   DURATION   AGE
hello-27480097   1/1           1s         2m21s
hello-27480098   1/1           1s         81s
hello-27480099   1/1           1s         21s
pi               1/1           8s         14m
lab@k8s1:~$

Similarly, there are only three latest Pods.

lab@k8s1:~$ kubectl get pods
NAME                      READY   STATUS      RESTARTS   AGE
hello-27480097--1-rqzxj   0/1     Completed   0          2m44s
hello-27480098--1-lkj9b   0/1     Completed   0          104s
hello-27480099--1-nsqpw   0/1     Completed   0          44s
web-79d88c97d6-4w4nl      1/1     Running     0          2d15h
web-79d88c97d6-7ktck      1/1     Running     0          2d15h
web-79d88c97d6-8tvsk      1/1     Running     0          2d15h
web-79d88c97d6-96sd9      1/1     Running     0          2d15h
web-79d88c97d6-9tzlh      1/1     Running     0          2d15h
web-79d88c97d6-brtgx      1/1     Running     0          2d15h
web-79d88c97d6-kngc4      1/1     Running     0          2d15h
web-79d88c97d6-p5vfg      1/1     Running     0          2d15h
web-79d88c97d6-rbhpr      1/1     Running     0          2d15h
lab@k8s1:~$

We can look at logs from these pods.

lab@k8s1:~$ kubectl logs hello-27480098--1-lkj9b
Fri Apr  1 09:38:01 UTC 2022
Hello from the Kubernetes cluster
lab@k8s1:~$
lab@k8s1:~$ kubectl logs hello-27480099--1-nsqpw
Fri Apr  1 09:39:01 UTC 2022
Hello from the Kubernetes cluster
lab@k8s1:~$
lab@k8s1:~$ kubectl delete cronjob hello
cronjob.batch "hello" deleted
lab@k8s1:~$ kubectl get job
NAME   COMPLETIONS   DURATION   AGE
pi     1/1           8s         21m
lab@k8s1:~$ kubectl get cronjob
No resources found in default namespace.
lab@k8s1:~$ kubectl delete job pi
job.batch "pi" deleted
lab@k8s1:~$
lab@k8s1:~$ kubectl get cronjob,job
No resources found in default namespace.
lab@k8s1:~$

This concludes our discussion on Kubernetes Jobs and CronJobs.

Back to Top ↑