Kubernetes CronJob Concurrency Management
Kubernetes CronJob Concurrency Management ensures multiple jobs run safely by controlling how and when they are executed across nodes.
Kubernetes CronJob Concurrency Management is the set of rules governing what happens when a new scheduled execution is due to start while a previous run created by the same batch/v1 CronJob is still active, controlled through the .spec.concurrencyPolicy field. Because a CronJob's schedule and a given run's actual duration are independent of each other, overlap is always a possibility: a run that takes longer than the interval between scheduled ticks will still be active when the next tick arrives, and concurrency management is what determines whether that overlap is allowed, prevented, or resolved by replacing the older run.
Choosing the right concurrency policy is a correctness decision, not just a performance one — for workloads that are not safe to run in parallel against themselves, the wrong policy can produce data corruption, duplicated side effects, or race conditions that only manifest occasionally, whenever a run happens to take longer than usual.
The Three Policies
Allow (Default)
concurrencyPolicy: Allow permits a new Job to be created at every scheduled tick regardless of whether a previous run is still active. Multiple Jobs from the same CronJob can run simultaneously, each independently tracked and retried.
This fits workloads that are naturally safe to run concurrently: independent, side-effect-free reads; per-item processing where different runs would never touch the same data; or workloads where the operational cost of an occasional overlap (extra resource consumption) is preferable to the complexity of coordinating exclusivity.
Forbid
concurrencyPolicy: Forbid skips creating a new Job entirely if a previous run from the same CronJob is still active at the scheduled time. The skipped tick is not queued or caught up later; it is simply not started, and the CronJob will attempt again at the next scheduled tick.
This is the appropriate choice for workloads that would conflict with themselves if run concurrently: a job that reads and rewrites a single shared file or database table, a migration step, or anything relying on exclusive access to a resource that only one execution should hold at a time.
spec:
concurrencyPolicy: Forbid
Replace
concurrencyPolicy: Replace terminates the still-running previous Job (and its Pods) and immediately starts the new one in its place. This guarantees that only the most recently scheduled run is ever active, at the cost of discarding whatever progress the terminated run had made.
This suits workloads where only the freshest execution matters and stale in-progress work has no value once superseded — for example, a job that recomputes a dashboard snapshot from the latest available data, where an in-flight run based on now-outdated inputs is better abandoned than allowed to finish.
spec:
concurrencyPolicy: Replace
Interaction with Other Fields
Missed Schedules Under Forbid
Combined with startingDeadlineSeconds, Forbid can cause a chain of skipped runs if each run consistently takes longer than the schedule interval: every new tick finds the previous run still active, skips, and the cycle repeats. This is a signal that either the schedule interval is too aggressive for the workload's actual duration, or the workload itself needs to be optimized or split into smaller units.
Manually Created Jobs Are Not Governed by concurrencyPolicy
A Job created manually via kubectl create job --from=cronjob/... is not subject to the CronJob's concurrencyPolicy, since that policy only governs the CronJob controller's own scheduled creation logic. A manual run can therefore still overlap with a scheduled run even under Forbid, which is an important caveat when using manual triggers for backfills or testing on a CronJob whose workload is not safe to run concurrently.
Observing Skipped or Replaced Runs
kubectl get events --field-selector involvedObject.name=codartium-nightly-report --sort-by=.lastTimestamp
Events on the CronJob object record when a scheduled tick was skipped due to Forbid or when a previous Job was terminated due to Replace, which is the primary way to confirm concurrency policy behavior is working as intended after a configuration change.
Choosing a Policy
| Workload characteristic | Recommended policy |
|---|---|
| Safe to run in parallel, independent side effects | Allow |
| Must not run concurrently with itself (shared state) | Forbid |
| Only the latest run's result matters | Replace |
Example
apiVersion: batch/v1
kind: CronJob
metadata:
name: codartium-concurrency-example
spec:
schedule: "*/5 * * * *"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 60
jobTemplate:
spec:
activeDeadlineSeconds: 240
template:
spec:
restartPolicy: Never
containers:
- name: exclusive-writer
image: codartium/exclusive-writer:latest