Kubernetes Workloads: CronJobs
A CronJob creates a new Job on a schedule defined with cron syntax. Each Job it creates runs to completion on its own, the same way a standalone Job does. The CronJob is the parent of the Jobs it creates, and each Job is the parent of its Pods, the same parent-child chain that links a Deployment to its ReplicaSet and Pods.
Task 1 - Create a CronJob
A schedule of * * * * * means "every minute". At each tick, the CronJob controller creates a new Job from .spec.jobTemplate, with a name in the form <cronjob-name>-<timestamp>. After creating reporter, watch kubectl get jobs - a reporter-* Job will appear within a minute. Check the link below to see other schedule format options.
Steps:
- Create a CronJob named
reporterwith.spec.schedule: "* * * * *",restartPolicy: Never, one container namedreporterusing imagebusybox, commanddate
Once a reporter-* Job appears, run kubectl get cronjob reporter - the LAST SCHEDULE column shows when the last tick fired and the ACTIVE column shows how many Jobs are currently running.
Task 2 - Trigger a Job Manually from a CronJob
kubectl create job --from=cronjob/<name> creates a Job immediately, using the CronJob's pod template, without waiting for the schedule. The new Job's .metadata.ownerReferences point back to the CronJob, and its Pod's .metadata.ownerReferences point to the Job.
Steps:
- Create a Job named
manual-runfrom thereporterCronJob
.metadata.ownerReferences on manual-run points to reporter, and the .metadata.ownerReferences on its Pod point to the Job.
Task 3 - Limit Job History
By default, a CronJob keeps the last 3 successful Jobs and the last 1 failed Job. Older ones are deleted automatically once the limit is exceeded. The two limits are independent and can be set to different values.
Steps:
- Update the
reporterCronJob to set.spec.successfulJobsHistoryLimit: 2and.spec.failedJobsHistoryLimit: 0
Let the schedule run for a few more minutes and watch kubectl get jobs. Once more than 2 successful reporter-* Jobs have accumulated, the oldest is deleted automatically to stay within the limit.
Task 4 - Suspend the CronJob
Setting .spec.suspend: true stops the CronJob from creating new Jobs without deleting it. Jobs already running are not affected. To pause a running Job instead, set .spec.suspend: true on the Job itself - see Task 6 of the Jobs challenge.
Steps:
- Set
.spec.suspend: trueon thereporterCronJob
Run kubectl get cronjob reporter - the SUSPEND column shows True. No new reporter-* Jobs appear past the next scheduled minute.
Task 5 - concurrencyPolicy: Allow
Allow lets the CronJob create a new Job even if the previous one is still active. Multiple Jobs may end up running simultaneously. For stateless workloads this may be harmless, but for anything that writes to shared storage or sends messages, concurrent runs can cause duplicates or data corruption. No need to set .spec.concurrencyPolicy explicitly - the default is already Allow.
Steps:
- Create a CronJob named
allow-demowith.spec.schedule: "* * * * *",restartPolicy: Never, one container namedallow-demousing imagebusybox, commandsleep 90
After two minutes, run kubectl get jobs - two or more allow-demo-* Jobs appear simultaneously, each sleeping for 90 seconds while new ticks keep firing. kubectl get cronjob allow-demo shows the count of active Jobs in the ACTIVE column.
Task 6 - concurrencyPolicy: Forbid
When a Job takes longer than the schedule interval, it is still active when the next tick fires. Forbid skips the new Job entirely if the previous one is still active.
Steps:
- Create a CronJob named
db-backupwith.spec.schedule: "* * * * *",.spec.concurrencyPolicy: Forbid,restartPolicy: Never, one container nameddb-backupusing imagebusybox, commandsleep 90
After a couple of minutes, run kubectl get jobs - only one db-backup-* Job is active at a time. Each skipped tick is logged as a Warning event with reason JobAlreadyActive - run kubectl get events --sort-by='.lastTimestamp' to see them against db-backup.
Task 7 - concurrencyPolicy: Replace
A Job that takes longer than the schedule interval is still active when the next tick fires. Replace deletes the currently running Job and starts a new one in its place at the next tick.
Steps:
- Create a CronJob named
api-syncwith.spec.schedule: "* * * * *",.spec.concurrencyPolicy: Replace,restartPolicy: Never, one container namedapi-syncusing imagebusybox, commandsleep 90
After a couple of minutes, run kubectl get jobs - only one api-sync-* Job exists at any time, but the name changes each tick as the old one is deleted. kubectl get events --sort-by='.lastTimestamp' shows when each old Job was removed and a new one started.