Start your free 14-day ContainIQ trial

Kubernetes CronJobs | Actionable Guide and Tutorial

This guide introduces the reader to Kubernetes CronJobs, explaining what they are and how they can be used when working with Kubernetes.

July 13, 2022
James Walker
Software Engineer

Kubernetes CronJobs are objects that create jobs on a recurring schedule. The mechanism is modeled on the cron command-line scheduler, a staple of Unix environments.

CronJobs let you start pods, run a task, and benefit from automatic cleanup after the operation. They come with built-in features for controlling concurrency and tracking job failures and successes. These features make CronJobs the most convenient way to manage regular tasks and maintenance activities within your Kubernetes cluster.

This article will explain what CronJobs are, how you can set them up, and the caveats you should be aware of when using them in your cluster. One of the most common challenges is the terminology: CronJobs are intentionally similar to regular crontab scripts, but the two are not identical. As usual in the world of Kubernetes, CronJobs layer their own behaviors and unique characteristics over the basic principles of cron.

How Do CronJobs Work?

Most complex software systems end up incorporating processes that need to run outside their main loop at a regular cadence. You might be sending emails in batches, processing user photo uploads, or refreshing data from an external provider. In all of these cases, you need a way of running the operation on a time-based schedule.

Setting up such a schedule is easy in the non-containerized world of traditional VMs and OS-level services. You’d write a script, add it to your system’s crontab, and call it a day. Kubernetes CronJob objects are the container ecosystem’s answer to this ubiquitous requirement.

CronJobs solve the challenge by bringing the familiarity of cron to a cluster environment. Each CronJob consists of a <terminal inline>schedule<terminal inline> in cron format, such as <terminal inline>*/5 * * * *<terminal inline> to run the schedule every five minutes, and a <terminal inline>spec<terminal inline> defining pods to start for each job execution. Kubernetes will schedule the pods to nodes in your cluster with spare capacity.

CronJobs are automatically managed by the cluster control plane. When it’s time to run your schedule, the cluster creates regular jobs with the pod spec from your CronJob object. Whereas standard jobs are used to encapsulate pods and retry execution until they terminate, a CronJob is a higher-level abstraction that repeats the cycle periodically.

When Should I Use a CronJob?

CronJobs are the right choice for anything you’d typically use cron to solve. They’re a fully automated approach to scheduled tasks and suitable for many different scenarios. Common use cases include application-level jobs such as sending emails and ingesting data, as well as infrastructure-bound operations like backing up a database or cleaning expired caches. CronJobs are always preferable to running a cron daemon within an existing container in your cluster, which goes against containerization best practices by giving the container multiple responsibilities.

Kubernetes CronJobs provide a way to run these tasks outside your main application containers, converting them to first-class components in your cluster. They also offer greater opportunities to inspect and manage your scheduled operations independently of your primary workload.

There are some cases where CronJobs might not be suitable. One particular limitation is Kubernetes’ lack of time zone support. While most clusters will respect the <terminal inline>CRON_TZ<terminal inline> and <terminal inline>TZ<terminal inline> environment variables, this is not standard and should not be taken for granted.

Jobs aren’t guaranteed to execute at the exact time indicated by their schedule. There are also cases where a CronJob could create multiple instances of its containers or start none at all. These rare scenarios arise from Kubernetes’ internal handling of scheduled operations and container creation. You should carefully consider whether to use a CronJob for critical operations where reliability is vital. All tasks run by a CronJob need to be idempotent, meaning they need to be able to run multiple times without side effects, because of the possibility that Kubernetes will complete a scheduled run more than once.

Creating a CronJob

You can add a CronJob to your cluster by writing a YAML file that describes its configuration. First, make sure you’ve got kubectl installed and configured with a connection to your cluster.

Create a YAML file in your working directory with the following content:


apiVersion: batch/v1
kind: CronJob
metadata:
  name: busybox-cron
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
      spec:
        template:
         spec:
           containers:
             - name: busybox
               image: busybox:latest
           command: ["/bin/sh", "-c”, “echo ‘Hello World’”]
           restartPolicy: OnFailure

This sets up a simple CronJob that executes every minute and logs <terminal inline>“Hello World”<terminal inline> to the job output. You’ll see how to retrieve these logs later in this article. The <terminal inline>spec.schedule<terminal inline> field sets the CronJob schedule and should be a valid cron expression.

The <terminal inline>spec.jobTemplate<terminal inline> field defines the job that will be created each time the schedule occurs. Within the job template, the <terminal inline>spec.containers<terminal inline> section is where you set up the pods that will be started as part of the job.

In this example, a new container running the <terminal inline>busybox:latest<terminal inline> image will start each time the CronJob is scheduled (ie, every minute). The command <terminal inline>/bin/sh -c “echo ‘Hello World’<terminal inline>” will be executed inside the container. Once the command exits successfully, the container will stop, the job will be marked as complete, and the CronJob will finish its scheduled run in turn. If the command ends in a failure, the container will restart and repeat the command until it succeeds. This is because the restart policy is set to <terminal inline>OnFailure<terminal inline>.

Add your new CronJob object to your cluster using kubectl:


# Replace the YAML file path with the correct path to your file
$ kubectl apply -f ./cron-job.yaml

cronjob.batch/busybox-cron created

That’s it! Kubernetes will start running the CronJob in line with the requested <terminal inline>spec.schedule<terminal inline>. You’ve got a scheduled operation running in your cluster, independent of your regular long-running pods and their containers.

Monitor Kubernetes Events in Real-Time
Monitor the health of your cluster and troubleshoot issues faster with pre-built dashboards that just work.
Learn More Book a Demo
event dashboard

Common Gotchas and Things To Tweak

Your example CronJob works as a model for many real-world jobs. In other cases, you might need to make a few more adjustments. Here are some things to think about as you define your CronJobs.

Concurrency

CronJobs come with built-in concurrency controls. These controls are a key difference from plain old Unix cron, where concurrent execution is always allowed and cannot be disabled. Kubernetes mirrors this and permits concurrency by default. Here’s an illustration of the possible effects in a CronJob that runs each minute:


09:00:00 - Job #1 starts.
09:00:50 - Job #1 ends.
09:01:00 - Job #2 starts.
09:02:00 - Job #3 starts.
09:02:10 - Job #2 ends.
09:02:50 - Job #3 ends.

The second run took longer than the scheduled minute to complete. As concurrency is allowed, the third run started anyway while the second was still incomplete. Concurrency can be undesirable when you need individual executions of a CronJob to run sequentially.

Setting the <terminal inline>spec.concurrencyPolicy<terminal inline> field on a CronJob object lets you control this behavior. It has three possible values:

  • <terminal inline>Allow<terminal inline>: Allow concurrency as shown above (default).
  • <terminal inline>Forbid<terminal inline>: Prevent concurrent runs. When a job is due to start but the previous run is incomplete, the new job is skipped. Kubernetes won’t try to start the job again until the next scheduled occurrence.
  • <terminal inline>Replace<terminal inline>: A hybrid of the previous two. When a job is due to start but the previous run is incomplete, the containers created by the previous job are terminated, and the new job is allowed to proceed.

Here’s an example of selecting the concurrency policy for a CronJob:


apiVersion: batch/v1
kind: CronJob
metadata:
  name: demo-cron-job
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
      # ...

Applying this object to your cluster will result in a CronJob where only one run can exist at any given time.

Starting Deadlines

The starting deadline is another mechanism that determines whether a new scheduled CronJob run can begin. This Kubernetes-specific concept is used to determine how long a job run remains eligible to start after its scheduled time has passed. It’s used when the job doesn’t start on time, perhaps because its concurrency policy is set to <terminal inline>Forbid<terminal inline> and the previous run is still live.

This value is controlled by the <terminal inline>spec.startingDeadlineSeconds<terminal inline> field:


apiVersion: batch/v1
kind: CronJob
metadata:
  name: demo-cron-job
spec:
  schedule: "*/5 * * * *"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 30
  jobTemplate:
      # ...

As an example, consider the schedule of the CronJob shown above. If the 09:00:00 run is still live at 09:05:00, that next run can’t start on time. Since the CronJob has a starting deadline of <terminal inline>30<terminal inline>, its start can be delayed for up to thirty seconds. If the previous job terminates at 09:05:15, the 09:05:00 job can begin. However, if the previous run takes until 09:05:30 to end, the 09:05:00 job is skipped entirely.

History Retention

Two other fields are also worth mentioning:

  • <terminal inline>spec.successfulJobsHistoryLimit<terminal inline>
  • <terminal inline>spec.failedJobsHistoryLimit<terminal inline>

These two values control how long Kubernetes retains the history of successful and failed jobs, respectively. They default to three successful jobs and one failed job. This means you can view the job objects and container logs of the last three successful CronJob runs on a rolling basis.

You might want to raise these values for jobs that run on a very frequent schedule. Higher values for the history will give you a better chance of successfully debugging issues before the logs get deleted.

Monitoring Your CronJobs

You can monitor CronJobs using familiar Kubernetes mechanisms such as <terminal inline>kubectl get<terminal inline>:

$ kubectl get cronjob busybox-cron

NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
busybox-cron */1**** False 0 <none> 5m

This gives you all the details of the CronJob definition. To view the details of job runs, you can use the following command:

$ kubectl get jobs

NAME COMPLETIONS DURATION AGE
busybox-cron-1644237060 1/1 1m 1m

Jobs belonging to a CronJob will display the CronJob’s name with their starting timestamp appended.

Once you’ve found the individual jobs, you can get their container logs using the <terminal inline>kubectl logs job/<job-name><terminal inline> command:


$ kubectl logs job/busybox-cron-1644237060

The ContainIQ monitoring dashboard is a reliable method of conveniently and precisely tracking Kubernetes CronJobs. It includes a comprehensive Kubernetes events view that tracks cluster activities such as job creations, failures, and successes. Unlike Kubernetes itself, ContainIQ offers indefinite data retention. You can go back in time to find events from a CronJob run that occurred several weeks ago. You can also view cluster-level and application logs at the points in time when these actions occurred.

Final Thoughts

Kubernetes CronJobs let you automate recurring and periodic tasks inside your Kubernetes cluster. They have strong parallels with traditional Unix-based cron implementations but also offer extra functionality with some unique caveats.

This article explored what CronJobs are, the situations in which they’re used, and how you can create your own within your Kubernetes cluster. You also looked at the important choices and considerations to make when managing job concurrency and log retention.

Most containerized systems will end up using a CronJob sooner or later. Gaining an understanding of how they work and the options for configuring them will help you integrate scheduled activities into your application and anticipate any errors that could occur. Combining CronJobs with a monitoring platform like ContainIQ is an excellent way to stay ahead of real-time events so you know when jobs are failing.

Start your free 14-day ContainIQ trial
Start Free TrialBook a Demo
No card required
James Walker
Software Engineer

James Walker is the founder of Heron Web, a UK-based digital agency providing bespoke software development services to SMEs. He has experience managing complete end-to-end web development workflows with DevOps, CI/CD, Docker, and Kubernetes. James also writes technical articles on programming and the software development lifecycle, using the insights acquired from his industry career. He's currently a regular contributor to CloudSavvy IT and has previously written for DigitalJournal.com, OnMSFT.com, and other technology-oriented publications.

READ MORE