bigywater.tech
Kubernetes · 55

CronJob:concurrencyPolicy 与错过的调度

CronJob 看似简单,但上一次还没跑完怎么办、暂停之后会不会补跑、错过了怎么处理,每一条都有坑。这篇让五个 CronJob 在同一分钟起跑,真跑五分钟,把 concurrencyPolicy 三种取值、错过的调度、startingDeadlineSeconds 和历史清理都量出来。

+4 / +81 / +160 / +238 秒
concurrencyPolicy: Forbid 的 CronJob 创建各个 Job 的时刻(每个 Job 跑 75 秒,被跳过的整分调度推迟补跑)
1 个
suspend 了 150 秒(错过 3 个整分)再恢复后立即创建的 Job 数
+29.0 秒
startingDeadlineSeconds: 10 时,错过了 31 秒的调度不补,第一个 Job 直到下一个整分才创建

三种并发策略

实测 · 本机执行输出
  cj-allow(concurrencyPolicy: Allow):在 287 秒的观察期里一共创建了 5 个 Job:
    Job …29860670 创建于 +  4.0s;最后一次被看到是 +261.1s(之后消失了:被删除或被替换)
    Job …29860671 创建于 + 64.0s;最后一次被看到是 +287.2s
    Job …29860672 创建于 +124.0s;最后一次被看到是 +287.2s
    Job …29860673 创建于 +184.0s;最后一次被看到是 +287.2s
    Job …29860674 创建于 +244.0s;最后一次被看到是 +287.2s
  cj-forbid(concurrencyPolicy: Forbid):在 287 秒的观察期里一共创建了 4 个 Job:
    Job …29860670 创建于 +  4.0s;最后一次被看到是 +287.2s
    Job …29860671 创建于 + 81.0s;最后一次被看到是 +287.2s
    Job …29860672 创建于 +160.0s;最后一次被看到是 +287.2s
    Job …29860673 创建于 +238.0s;最后一次被看到是 +287.2s
  cj-replace(concurrencyPolicy: Replace):在 287 秒的观察期里一共创建了 5 个 Job:
    Job …29860670 创建于 +  4.0s;最后一次被看到是 + 62.4s(之后消失了:被删除或被替换)
    Job …29860671 创建于 + 64.0s;最后一次被看到是 +120.9s(之后消失了:被删除或被替换)
    Job …29860672 创建于 +124.0s;最后一次被看到是 +182.6s(之后消失了:被删除或被替换)
    Job …29860673 创建于 +184.0s;最后一次被看到是 +241.5s(之后消失了:被删除或被替换)
    Job …29860674 创建于 +244.0s;最后一次被看到是 +287.2s
  CronJob 控制器的事件:
    cj-allow    SuccessfulCreate   x1 Created job cj-allow-29860670
    cj-forbid   SuccessfulCreate   x1 Created job cj-forbid-29860670
    cj-replace  SuccessfulCreate   x1 Created job cj-replace-29860670
    cj-allow    SuccessfulCreate   x1 Created job cj-allow-29860671
    cj-replace  SuccessfulDelete   x1 Deleted job cj-replace-29860670
    cj-replace  SuccessfulCreate   x1 Created job cj-replace-29860671
    cj-allow    SawCompletedJob    x1 Saw completed job: cj-allow-29860670, condition: Complete
    cj-forbid   SawCompletedJob    x1 Saw completed job: cj-forbid-29860670, condition: Complete
    cj-forbid   SuccessfulCreate   x1 Created job cj-forbid-29860671
    cj-allow    SuccessfulCreate   x1 Created job cj-allow-29860672
    cj-deadline SuccessfulCreate   x1 Created job cj-deadline-29860672
    cj-replace  SuccessfulDelete   x1 Deleted job cj-replace-29860671
    cj-replace  SuccessfulCreate   x1 Created job cj-replace-29860672
    cj-deadline SawCompletedJob    x1 Saw completed job: cj-deadline-29860672, condition: Complete
    cj-allow    SawCompletedJob    x1 Saw completed job: cj-allow-29860671, condition: Complete
    cj-miss     SuccessfulCreate   x1 Created job cj-miss-29860672
    cj-miss     SawCompletedJob    x1 Saw completed job: cj-miss-29860672, condition: Complete
    cj-forbid   SawCompletedJob    x1 Saw completed job: cj-forbid-29860671, condition: Complete
    cj-forbid   SuccessfulCreate   x1 Created job cj-forbid-29860672
    cj-allow    SuccessfulCreate   x1 Created job cj-allow-29860673
    cj-deadline SuccessfulCreate   x1 Created job cj-deadline-29860673
    cj-miss     SuccessfulCreate   x1 Created job cj-miss-29860673
    cj-replace  SuccessfulDelete   x1 Deleted job cj-replace-29860672
    cj-replace  SuccessfulCreate   x1 Created job cj-replace-29860673
    cj-deadline SawCompletedJob    x1 Saw completed job: cj-deadline-29860673, condition: Complete
    cj-miss     SawCompletedJob    x1 Saw completed job: cj-miss-29860673, condition: Complete
    cj-allow    SawCompletedJob    x1 Saw completed job: cj-allow-29860672, condition: Complete
    cj-forbid   SawCompletedJob    x2 Saw completed job: cj-forbid-29860672, condition: Complete
    cj-forbid   SuccessfulCreate   x1 Created job cj-forbid-29860673
    cj-allow    SuccessfulCreate   x1 Created job cj-allow-29860674
    cj-deadline SuccessfulCreate   x1 Created job cj-deadline-29860674
    cj-forbid   JobAlreadyActive   x10 Not starting job because prior execution is running and concurrency policy is Forbid
    cj-miss     SuccessfulCreate   x1 Created job cj-miss-29860674
    cj-replace  SuccessfulDelete   x1 Deleted job cj-replace-29860673
    cj-replace  SuccessfulCreate   x1 Created job cj-replace-29860674
    cj-deadline SawCompletedJob    x1 Saw completed job: cj-deadline-29860674, condition: Complete
    cj-miss     SawCompletedJob    x1 Saw completed job: cj-miss-29860674, condition: Complete
    cj-allow    SuccessfulDelete   x1 Deleted job cj-allow-29860670
    cj-allow    SawCompletedJob    x2 Saw completed job: cj-allow-29860673, condition: Complete

每个 Job 睡 75 秒、每分钟一次,所以相邻两个必然重叠。Allow(默认)每个整分都创建新 Job,同时有两个在跑。Forbid:21:51:00 那次因为上一个还在运行而不创建,事件 JobAlreadyActive: Not starting job because prior execution is running and concurrency policy is Forbid(累计 10 次);等上一个完成后,控制器发现 21:51 的调度还没执行,于是在 +81.0 秒补建它,之后 +160.0、+238.0 秒——被跳过的调度只是推迟,并且越拖越晚。Replace:每个整分先 SuccessfulDelete 删掉仍在运行的上一个 Job 再创建新的,每个 Job 活 58~62 秒,从不跑完。

错过的调度

实测 · 本机执行输出
  cj-miss 暂停了约 150 秒(期间错过 2 次整分调度),恢复后 137 秒内创建的 Job:[('29860672', -0.1), ('29860673', 33.9), ('29860674', 93.9)](时间是相对恢复那一刻的秒数)

suspend: true 暂停约 150 秒(错过了 3 个整分)后恢复:只立刻创建了一个 Job——最近那次整分的(cj-miss-29860672,后缀是调度时刻的分钟数),没有把 3 次都补上;之后回到每个整分一个(+33.9 秒、+93.9 秒)。

startingDeadlineSeconds

实测 · 本机执行输出
  cj-deadline(startingDeadlineSeconds=10)在某个整分之后约 31 秒恢复(那个整分的调度已经错过了 31 秒 > 10 秒):恢复后创建的 Job:[('29860672', 29.0), ('29860673', 89.0), ('29860674', 149.0)]
  事件:[('SuccessfulCreate', 'Created job cj-deadline-29860672'), ('SawCompletedJob', 'Saw completed job: cj-deadline-29860672, condition: Complete'), ('SuccessfulCreate', 'Created job cj-deadline-29860673'), ('SawCompletedJob', 'Saw completed job: cj-deadline-29860673, condition: Complete'), ('SuccessfulCreate', 'Created job cj-deadline-29860674'), ('SawCompletedJob', 'Saw completed job: cj-deadline-29860674, condition: Complete')]

设 startingDeadlineSeconds: 10 的 CronJob 在整分之后约 31 秒才恢复:那次调度已经晚了 31 秒,超过截止时间,不补;第一个 Job 要等到下一个整分(恢复后 +29.0 秒)才创建。不设这个字段时,错过的调度总是可以补,所以上面 Forbid 的 Job 才会越拖越晚。对“过时就没意义”的任务一定要设。

状态与历史

实测 · 本机执行输出
  cj-miss 的 status:lastScheduleTime=2026-10-10T13:54:00Z lastSuccessfulTime=2026-10-10T13:54:08Z active=0;默认保留成功 Job 3 个、失败 1 个
  命名空间里现存的 Job 总数 15(每个 CronJob 只留着最近成功的 3 个以及还在运行的)

status.lastScheduleTime 是最后一次创建 Job 的调度时刻,lastSuccessfulTime 是最后一次成功完成的时间。successfulJobsHistoryLimit 默认 3、failedJobsHistoryLimit 默认 1,多出来的旧 Job 会被自动删除(事件 SuccessfulDelete:cj-allow 删掉了最老的一个)。五个 CronJob 一共还剩 15 个 Job。

分步演示

CronJob 的调度
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0);五个 CronJob 的 schedule 都是 "* * * * *",脚本等到整分前 4 秒创建它们(21:49:56,第一个整分 21:50:00),观察 287 秒,每 3 秒记录一次各 CronJob 名下的 Job;cj-allow/forbid/replace 的 Job 睡 75 秒,cj-miss 与 cj-deadline 的睡 5 秒并创建时 suspend: true;cj-miss 在 +150 秒恢复,cj-deadline 在某个整分之后约 31 秒恢复;Job 的名字后缀是调度时刻按分钟数计算的数字;每种情形各一次
    • 没有验证:错过超过 100 次调度时 CronJob 放弃并报错的行为;timeZone 字段;kube-controller-manager 重启期间的错过调度;successfulJobsHistoryLimit 为 0;CronJob 的 Job 因 Pod 失败重试与下一次调度的交互;大量 CronJob 在同一个整分触发时的创建延迟(这里观察到的创建时刻基本都在整分之后 0~4 秒内)
    京ICP备2026067502号-1京公网安备11010502063487号