CertGrid CertGrid
Hands-on Lab·Certified Kubernetes Application Developer

CronJob Schedules and concurrencyPolicy

A CronJob creates a Job on a schedule, and the interesting question is what it does when the previous Job is still running. `concurrencyPolicy` answers it, and the default is to let them overlap. This runs a two-and-a-half minute job on a one-minute schedule with `Forbid` set, so the skip is observable, and covers the history limits and `suspend` alongside it.

Application Design and Build Guide 11 of 44 Intermediate

Written against the versions above. The Job names a CronJob generates end in a number - `tick-29790909` - which is the scheduled time in minutes since the epoch, not a random suffix. Two Jobs from consecutive minutes therefore have consecutive numbers, which makes the gap left by a skipped run visible in the listing.

The CronJob controller runs in the control plane; the Jobs it creates land on the workers.
Server NameIP AddressOSRolesCPURAMHDD
CKA1001192.168.0.175Ubuntu 26.04 LTSControl Plane Node2 Core4 GB50 GB
CKA1001-NODE01192.168.0.176Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE02192.168.0.177Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB
CKA1001-NODE03192.168.0.178Ubuntu 26.04 LTSWorker Node2 Core4 GB50 GB

Before you start

  1. A Job every minute

    kubectl create cronjob generates this in one line - no jobTemplate nesting typed by hand. After seventy-five seconds there are Jobs in the namespace, and the CronJob records when it last fired:

    SCHEDULE      LAST                   SUCCESSHIST   FAILHIST
    */1 * * * *   2026-08-23T03:...Z   3             1

    successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 1 - both defaults, and both are why a CronJob that has run for a week has only three Jobs to show for it. If you are looking for the run from an hour ago, it has been garbage collected.

    The logs of every run at once, with the Job name prefixed, is the query worth knowing:

    kubectl logs -l batch.kubernetes.io/job-name --tail=1 --prefix=true

    bash Example session
    kubectl create namespace ckad-cronnamespace/ckad-cron createdkubectl -n ckad-cron create cronjob tick --image=busybox:1.36 --schedule='*/1 * * * *' -- /bin/sh -c 'date +%H:%M:%S'cronjob.batch/tick createdkubectl -n ckad-cron get cronjob tickNAME   SCHEDULE      TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGEtick   */1 * * * *   <none>     False     0        <none>          0ssleep 75; kubectl -n ckad-cron get jobsNAME            STATUS     COMPLETIONS   DURATION   AGEtick-29790908   Complete   1/1           3s         72stick-29790909   Complete   1/1           2s         12skubectl -n ckad-cron get cronjob tick -o 'custom-columns=SCHEDULE:.spec.schedule,LAST:.status.lastScheduleTime,SUCCESSHIST:.spec.successfulJobsHistoryLimit,FAILHIST:.spec.failedJobsHistoryLimit'SCHEDULE      LAST                   SUCCESSHIST   FAILHIST*/1 * * * *   2026-08-23T03:09:00Z   3             1

    Expected resultOne Job per minute, and the two history limits at their defaults.

    Success conditionYou have a CronJob and know why old runs disappear.

  2. What happens when a run overruns

    slow sleeps 150 seconds on a */1 * * * * schedule, so by the time the second tick comes due the first is still going. With concurrencyPolicy: Forbid:

    JOB             ACTIVE   SUCCEEDED
    slow-29790910   1        <none>
    tick-29790909   <none>   1
    tick-29790910   <none>   1
    tick-29790911   <none>   1

    One slow Job across two ticks. tick produced ...909, ...910 and ...911; slow produced only ...910. The ...911 run was due and was skipped.

    POLICY   ACTIVE          LAST
    Forbid   slow-29790910   2026-08-23T03:10:00Z

    The skip is silent - describe cronjob recorded no event for it. Nothing tells you a run did not happen except the gap in the numbering, which is worth knowing before you rely on a schedule for something that matters.

    The three policies:

    • Allow (default) - runs overlap. Fine for idempotent work, bad for anything that writes.
    • Forbid - skip the new run, as here.
    • Replace - kill the running Job and start the new one.
    bash Example session
    sleep 140; kubectl -n ckad-cron get jobs -l app!=none -o 'custom-columns=JOB:.metadata.name,ACTIVE:.status.active,SUCCEEDED:.status.succeeded'JOB             ACTIVE   SUCCEEDEDslow-29790910   1        <none>tick-29790909   <none>   1tick-29790910   <none>   1tick-29790911   <none>   1kubectl -n ckad-cron get cronjob slow -o 'custom-columns=POLICY:.spec.concurrencyPolicy,ACTIVE:.status.active[*].name,LAST:.status.lastScheduleTime'POLICY   ACTIVE          LASTForbid   slow-29790910   2026-08-23T03:10:00Z

    Expected resultOne active slow Job over two schedule ticks.

    Success conditionYou can choose a concurrency policy and know the skip will be silent.

  3. Suspend it

    NAME   SCHEDULE      TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE
    tick   */1 * * * *   <none>     True      0        34s             3m38s

    SUSPEND True and nothing further is created. Already-running Jobs are left alone - suspend stops *scheduling*, it does not stop work in flight.

    This is the correct answer to "stop this CronJob" on the exam and in production, and it is much better than deleting it, because the object and its history stay. Unsuspend with the same patch and false.

    Note TIMEZONE : without spec.timeZone the schedule is interpreted in the controller's time zone, which on most clusters is UTC and is very rarely what a business schedule means.

    bash Example session
    kubectl -n ckad-cron patch cronjob tick --type=merge -p '{"spec":{"suspend":true}}'cronjob.batch/tick patchedkubectl -n ckad-cron get cronjob tick -o 'custom-columns=SCHEDULE:.spec.schedule,LAST:.status.lastScheduleTime,SUCCESSHIST:.spec.successfulJobsHistoryLimit,FAILHIST:.spec.failedJobsHistoryLimit'SCHEDULE      LAST                   SUCCESSHIST   FAILHIST*/1 * * * *   2026-08-23T03:09:00Z   3             1kubectl -n ckad-cron get cronjob tick -o jsonpath='{.spec.suspend}'truekubectl delete namespace ckad-cron --wait=falsenamespace "ckad-cron" deleted

    Expected resultSUSPEND True and no further Jobs.

    Success conditionYou can stop a schedule without destroying it.

Troubleshooting

Official sources