CronJob 把一份 Job 模板与循环时间表组合起来。CronJob 控制器负责创建 Job,再由 Job 管理执行工作的 Pod。一次运行可以包含重试、替换 Pod,或按照 Job 配置启动多个并行工作者,不应把它简化为每次只启动一个短命容器。两次运行之间不需要专门的应用进程等候触发,但已经完成的 Job 和 Pod 可能继续保留,供排查与审计使用。
时间表只是近似兑现。某个时刻可能生成重复 Job,也可能完全漏掉,因此不能无条件承诺每个计划时刻都至少执行一次。应用既要能安全重复执行,也要记录哪些业务周期真正完成。幂等性解决重复效果,独立的完整性检查解决根本没有运行的工作,这两个问题不能互相替代。
时间表产生的是 Job
五个字段依次表示分、时、日、月与星期,支持区间、步长和 @daily 等宏。*/10 * * * * 表示每十分钟的计划时刻,并不保证 Pod 恰好在那个瞬间开始执行。即使 Job 已经建立,节点资源不足、镜像拉取或调度过程仍可能让实际工作晚于计划。
如果业务依赖当地钟表时间,应明确设置 spec.timeZone。省略时使用控制器管理器的本地时区;换一台机器本身不会必然改变行为,只有相关时区配置变化才会影响解释。不要把 TZ 或 CRON_TZ 塞进 schedule 字符串,应使用专门字段。夏令时切换以及报表所覆盖的业务日期,仍需要应用自己的明确规则,不能靠时区名称代替业务定义。
CronJob 名称应控制在五十二个字符以内,为生成 Job 名称时追加的后缀留空间。从 Kubernetes 1.32 起,生成的 Job 带有 batch.kubernetes.io/cronjob-scheduled-timestamp,可以把原定时间与创建、启动时间比较,判断延迟发生在哪一层。这个注解是诊断依据之一,并非唯一审计来源,更不能证明备份、报表或其他业务结果已经成功完成。
并发策略只约束同一个 CronJob
默认的 Allow 允许 Job 重叠。Forbid 在前一次仍活跃时拒绝新的启动;跳过的时间计入漏跑,之后是否还能启动仍取决于截止时间。Replace 先请求删除,再创建替代 Job;异步删除不证明旧 Pod 已经停止。这些策略不会协调另一个 CronJob,也不会阻止集群外的工作者同时修改相同数据。
如果任务每次都比调度间隔长,Replace 可能不断打断尚未完成的工作。Forbid 用漏掉启动机会换取减少重叠,并没有建立一个持久任务队列。对于重复写入有害的操作,应设计业务级去重或协调,同时考虑替换期间已经提交了一半的结果。持有一把锁本身不代表中途失败之后就能安全继续,更不代表漏掉的业务周期会自动补齐。
启动期限、挂起与历史保留
startingDeadlineSeconds 限制的是允许多晚开始,而不是任务最多运行多久。不设置时,每次计划没有单独的启动截止时间。概念文档描述超过一百次漏跑时拒绝补启动,但此次核对的 1.34.0 调度辅助函数会产生警告并返回最近的合格时刻,并不单凭这个计数拒绝启动。应核对服务端版本,不要看到警告就推断永久停摆。设置启动期限后,统计窗口受该期限限制;小于十秒的值可能错过控制器检查,不能当作精确实时调度器。
挂起会停止创建新 Job,但不会终止已存在的 Job。挂起期间的计划时间也算漏跑,所以恢复时可能立即发生补调度,仍受其他规则限制。不要把这理解成每个历史时刻都必然对应一个补建 Job。若每一期报表都必须处理,应使用持久的业务工作清单记录完成情况,而不是把 CronJob 历史当作可靠队列。
历史保留默认留下三个成功 Job 和一个失败 Job。它控制的是已完成对象的保留数量,不是同时运行数,也不替代执行期限。修改模板只影响将来创建的 Job;已经启动的 Job 继续使用自己的规格。调度配置、单次任务配置和结果保留策略需要分别审阅,才能知道一次变更真正影响哪批工作。
坏掉的样子
一个运行二十五分钟的任务放在十分钟时间表上,使用 Allow 时可能出现重叠和重复写入。应查看真实所属关系、活跃 Pod 与完成记录,再判断业务重复是否由调度造成。启动期限应该反映结果迟到多久仍然有用,而不是随便选一个数字,只为了让控制器不再产生警告。
lastScheduleTime 长时间不更新也可能是预期行为:挂起、Forbid、稀疏的日历表达式或时区改变,都可能解释这个现象。事件会过期,成功创建 Job 也不等于备份可恢复。监控应同时关注最后一次经过验证的可用结果,以及调度本身的健康,避免每天都有绿色 Job,却从未检查产物是否真的能用。
快速验证
在指定的只读集群上下文与命名空间中,检查已有的 nightly-report。下列命令保留自原文,本轮没有在真实集群重新执行。
kubectl get cronjob nightly-report -o jsonpath='{.spec.schedule} {.spec.concurrencyPolicy} {.spec.startingDeadlineSeconds}{"\n"}'
kubectl get cronjob nightly-report -o jsonpath='{.status.lastScheduleTime}{"\n"}'
kubectl get jobs --sort-by=.metadata.creationTimestamp -o custom-columns=NAME:.metadata.name,START:.status.startTime,SUCCEEDED:.status.succeeded
kubectl get events --field-selector involvedObject.kind=CronJob --sort-by=.lastTimestamp | tail -5
Job 列表还会包含同一命名空间里的其他任务,判断之前必须通过 ownerReferences 核对所属 CronJob。只有 START 时间不能证明运行重叠,SUCCEEDED 为空也不能证明任务仍在执行;还要结合 active、failed 与完成条件,以及对应的原定时间。时间间隔要按实际日历和策略解释,保留的事件没有说明时,再寻找控制器日志,不能根据一条缺失记录直接认定整个控制面停过。
来源
Kubernetes CronJob,调度语法、并发策略、漏跑一百次的规则与历史上限,已核验 2026-10-15。
CronJob v1 API 参考,CronJob spec 的字段级定义,已核验 2026-10-15。
Kubernetes 1.34.0 CronJob scheduling helper, mostRecentScheduleTime and nextScheduleTime select the latest missed time and warn above 100,已核验 2026-10-15。
CronJob job control (v1.34.0),DeleteJob 使用后台传播;替换不是等待 Pod 终止的屏障,已核验 2026-10-18。