Active CronJobs that have never started a single run
What does ZopNight detect here?
ZopNight flags a Kubernetes CronJob on EKS, GKE or AKS that is not suspended, has no `status.lastScheduleTime`, and is older than its expected first run: 48 hours for daily or faster schedules, 8 days for weekly, 32 days for monthly and 366 days for yearly ones. Such a CronJob exists but has done nothing.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-1734 · RC-1834 · RC-1934 |
| Category | governance |
| Severity | medium |
| Metric | status.lastScheduleTime |
| Threshold | absent, CronJob older than 48h to 366d depending on schedule |
| Source | ZopNight |
| Permissions used | list cronjobs.batch |
Where it applies
A CronJob that never fires is a silent failure
The CronJob controller records when it last created a Job in status.lastScheduleTime, which
the CronJob API reference
describes as the last time the job was successfully scheduled. A CronJob with no value there has
never launched anything. Nobody gets paged, because nothing failed: the backup, report or cleanup
the CronJob was written for simply never ran.
The usual causes are in the scheduling rules on the
CronJob concepts page. A
startingDeadlineSeconds below 10 seconds can stop the job being scheduled at all, because the
controller only checks every 10 seconds. More than 100 missed schedules makes the controller skip
the run and log too many missed start times. A schedule written for the wrong time zone can
also put the first run much later than expected, since a CronJob without .spec.timeZone uses
the controller manager’s local zone.
Spotting CronJobs with no run on record
kubectl get cronjobs -A -o json | jq -r ' .items[] | select(.spec.suspend != true and .status.lastScheduleTime == null) | "\(.metadata.namespace)/\(.metadata.name) \(.spec.schedule) created=\(.metadata.creationTimestamp)"'For any hit, kubectl describe cronjob <name> shows controller events such as missed-start
warnings.
The age gate scales with the schedule
A CronJob created yesterday for a monthly report has not failed; its first run is not due yet. ZopNight therefore reads the cron expression and waits longer for slower schedules:
@yearly, @annually, or a specific month set -> 366 days old before flagging@monthly, or a specific day of month -> 32 days@weekly, or a specific day of week -> 8 days@daily, @hourly, anything more frequent -> 48 hoursunrecognised expressions -> 48 hoursAge is measured from the CronJob’s creation timestamp. If that timestamp is missing or cannot be parsed, the rule stays silent rather than guess.
Where the check steps aside
Suspended CronJobs are skipped here, because pausing is a deliberate choice covered by
Suspended CronJob. Any CronJob with a
lastScheduleTime, even a very old one, is also skipped: this rule is about never having run,
not about runs that stopped.
Governance finding, not a saving
The recommendation carries no dollar figure. An idle CronJob object costs nothing to keep; the risk is the missing work and the false confidence that it is happening.
Getting the first run to happen
- Print the schedule with
kubectl get cronjob <name> -o jsonpath='{.spec.schedule}'and check the expression and, if set,.spec.timeZone. - Look at
startingDeadlineSeconds. Unset it or raise it well above 10 seconds. - Trigger a test run from the template with
kubectl create job <name>-manual --from=cronjob/<name>and confirm it succeeds. - If the work is no longer needed, delete the CronJob so the inventory reflects what runs.