CronJobs
You need to run a database backup every night at 2 AM. A Job runs once when you create it. A CronJob wraps a Job template with a schedule expression. At each scheduled time, the CronJob controller creates a new Job, which in turn creates a Pod. The backup runs, the Pod completes, and the cycle repeats on the next scheduled trigger.
nano backup-cron.yamlapiVersion: batch/v1kind: CronJobmetadata: name: backup-cronspec: schedule: '*/2 * * * *' jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: busybox:1.36 command: ['sh', '-c', 'echo Running backup at $(date)']The schedule */2 * * * * means every 2 minutes. For a real nightly backup you would use 0 2 * * *. The */2 schedule is used here because the simulation moves faster than wall time.
kubectl apply -f backup-cron.yamlReading the cron schedule syntax
The five fields are: minute (0-59), hour (0-23), day of month (1-31), month (1-12), day of week (0-6, where 0 is Sunday). Common patterns:
0 * * * *: every hour on the hour*/15 * * * *: every 15 minutes0 9 * * 1: every Monday at 9:00 AM0 0 1 * *: first day of every month at midnight
Watching the CronJob create Jobs
kubectl get cronjobsThe LAST SCHEDULE column shows when the most recent Job was triggered. The ACTIVE column shows how many Jobs are currently running.
kubectl get jobsEach run creates a Job named backup-cron-<timestamp>. After the CronJob runs a few times, you will see multiple completed Jobs in the list.
kubectl get podsCompleted Pods from past Job runs also accumulate. The CronJob keeps a history of completed and failed Jobs for debugging.
A CronJob has schedule: '0 8 * * 1-5'. When does it run?
Reveal answer
Every weekday (Monday through Friday) at 08:00. The 1-5 in the day-of-week field selects days 1 through 5, which correspond to Monday through Friday.
History limits and cleanup
By default, a CronJob retains the last 3 successful Jobs and the last 1 failed Job. As runs accumulate, older Jobs and their Pods are garbage-collected automatically.
You can control these limits:
spec: successfulJobsHistoryLimit: 5 failedJobsHistoryLimit: 3Setting successfulJobsHistoryLimit: 0 keeps no history at all: completed Jobs and Pods are deleted immediately. This reduces cluster noise but means you lose access to logs from past runs. For debugging, keep at least the last 1 or 2 successful runs.
Concurrency and missed schedules
Two fields handle edge cases in scheduling:
concurrencyPolicy controls what happens if a Job is still running when the next trigger fires:
Allow(default): a new Job starts regardless of the previous one’s status.Forbid: the new run is skipped if the previous Job is still active.Replace: the current Job is terminated and a new one starts.
startingDeadlineSeconds handles missed triggers. If the CronJob controller was down for a period, Kubernetes counts how many triggers were missed. If the count exceeds 100, the CronJob stops trying to catch up. Setting startingDeadlineSeconds: 300 tells Kubernetes to only attempt a missed run if less than 300 seconds have passed since the scheduled time.
If a CronJob misses more than 100 scheduled runs (which can happen if the controller is stopped for a long period), it will stop scheduling entirely and log a warning. You would need to inspect the CronJob with kubectl describe cronjob <name> to see the TooManyMissedStartTimes condition, and manually trigger a one-off Job if needed.
kubectl create job manual-backup --from=cronjob/backup-cronThis creates an immediate Job from the CronJob’s jobTemplate, outside the normal schedule. Useful for testing the Job template or triggering a run on demand.
A CronJob has concurrencyPolicy: Forbid. The 9:00 AM Job is still running at 9:05 AM when the next trigger fires. What happens?
Reveal answer
The 9:05 AM run is skipped entirely. The CronJob controller sees an active Job and does not create a new one. The 9:00 AM Job continues running undisturbed. The skipped run is recorded in the CronJob status.
kubectl delete cronjob backup-cronCronJobs add scheduling on top of Jobs without changing the Job semantics you already know. The schedule field follows standard cron syntax, concurrencyPolicy handles overlapping runs, and the history limits keep the cluster tidy. The next module covers commands-and-args, which controls exactly what a container runs when it starts.