CronJob

CronJob 通过重复调度启动一次性的 Job。

特性状态: Kubernetes v1.21 [stable]

CronJob 创建基于时隔重复调度的 Job

CronJob 用于执行排期操作,例如备份、生成报告等。 一个 CronJob 对象就像 Unix 系统上的 crontab(cron table)文件中的一行。 它用 Cron 格式进行编写, 并周期性地在给定的调度时间执行 Job。

CronJob 有所限制,也比较特殊。 例如在某些情况下,单个 CronJob 可以创建多个并发任务。 请参阅下面的限制

当控制平面为 CronJob 创建新的 Job 和(间接)Pod 时,CronJob 的 .metadata.name 是命名这些 Pod 的部分基础。 CronJob 的名称必须是一个合法的 DNS 子域值, 但这会对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的 DNS 标签规则。 即使名称是一个 DNS 子域,它也不能超过 52 个字符。这是因为 CronJob 控制器将自动在你所提供的 Job 名称后附加 11 个字符,并且存在 Job 名称的最大长度不能超过 63 个字符的限制。

示例

下面的 CronJob 示例清单会在每分钟打印出当前时间和问候消息:

application/job/cronjob.yaml CronJob - 图1

  1. apiVersion: batch/v1
  2. kind: CronJob
  3. metadata:
  4. name: hello
  5. spec:
  6. schedule: "* * * * *"
  7. jobTemplate:
  8. spec:
  9. template:
  10. spec:
  11. containers:
  12. - name: hello
  13. image: busybox:1.28
  14. imagePullPolicy: IfNotPresent
  15. command:
  16. - /bin/sh
  17. - -c
  18. - date; echo Hello from the Kubernetes cluster
  19. restartPolicy: OnFailure

使用 CronJob 运行自动化任务一文会为你详细讲解此例。

编写 CronJob 声明信息

Cron 时间表语法

.spec.schedule 字段是必需的。该字段的值遵循 Cron 语法:

  1. # ┌───────────── 分钟 (0 - 59)
  2. # │ ┌───────────── 小时 (0 - 23)
  3. # │ │ ┌───────────── 月的某天 (1 - 31)
  4. # │ │ │ ┌───────────── 月份 (1 - 12)
  5. # │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周一;在某些系统上,7 也是星期日)
  6. # │ │ │ │ │ 或者是 sun,mon,tue,web,thu,fri,sat
  7. # │ │ │ │ │
  8. # │ │ │ │ │
  9. # * * * * *

例如 0 0 13 * 5 表示此任务必须在每个星期五的午夜以及每个月的 13 日的午夜开始。

该格式也包含了扩展的 “Vixie cron” 步长值。 FreeBSD 手册中解释如下:

步长可被用于范围组合。范围后面带有 /<数字> 可以声明范围内的步幅数值。 例如,0-23/2 可被用在小时字段来声明命令在其他数值的小时数执行 (V7 标准中对应的方法是 0,2,4,6,8,10,12,14,16,18,20,22)。 步长也可以放在通配符后面,因此如果你想表达 “每两小时”,就用 */2

说明:

时间表中的问号 (?) 和星号 * 含义相同,它们用来表示给定字段的任何可用值。

除了标准语法,还可以使用一些类似 @monthly 的宏:

输入描述相当于
@yearly (或 @annually)每年 1 月 1 日的午夜运行一次0 0 1 1
@monthly每月第一天的午夜运行一次0 0 1
@weekly每周的周日午夜运行一次0 0 0
@daily (或 @midnight)每天午夜运行一次0 0
@hourly每小时的开始一次0

为了生成 CronJob 时间表的表达式,你还可以使用 crontab.guru 这类 Web 工具。

任务模板

.spec.jobTemplate为 CronJob 创建的 Job 定义模板,它是必需的。它和 Job 的语法完全一样, 只不过它是嵌套的,没有 apiVersionkind。 你可以为模板化的 Job 指定通用的元数据, 例如标签注解。 有关如何编写一个任务的 .spec, 请参考编写 Job 规约

任务延迟开始的最后期限

.spec.startingDeadlineSeconds 字段是可选的。 它表示任务如果由于某种原因错过了调度时间,开始该任务的截止时间的秒数。

过了截止时间,CronJob 就不会开始该任务的实例(未来的任务仍在调度之中)。 例如,如果你有一个每天运行两次的备份任务,你可能会允许它最多延迟 8 小时开始,但不能更晚, 因为更晚进行的备份将变得没有意义:你宁愿等待下一次计划的运行。

对于错过已配置的最后期限的 Job,Kubernetes 将其视为失败的任务。 如果你没有为 CronJob 指定 startingDeadlineSeconds,那 Job 就没有最后期限。

如果 .spec.startingDeadlineSeconds 字段被设置(非空), CronJob 控制器将会计算从预期创建 Job 到当前时间的时间差。 如果时间差大于该限制,则跳过此次执行。

例如,如果将其设置为 200,则 Job 控制器允许在实际调度之后最多 200 秒内创建 Job。

并发性规则

.spec.concurrencyPolicy 也是可选的。它声明了 CronJob 创建的任务执行时发生重叠如何处理。 spec 仅能声明下列规则中的一种:

  • Allow(默认):CronJob 允许并发任务执行。
  • Forbid: CronJob 不允许并发任务执行;如果新任务的执行时间到了而老任务没有执行完,CronJob 会忽略新任务的执行。
  • Replace:如果新任务的执行时间到了而老任务没有执行完,CronJob 会用新任务替换当前正在运行的任务。

请注意,并发性规则仅适用于相同 CronJob 创建的任务。如果有多个 CronJob,它们相应的任务总是允许并发执行的。

调度挂起

通过将可选的 .spec.suspend 字段设置为 true,可以挂起针对 CronJob 执行的任务。

这个设置会影响 CronJob 已经开始的任务。

如果你将此字段设置为 true,后续发生的执行都会被挂起 (这些任务仍然在调度中,但 CronJob 控制器不会启动这些 Job 来运行任务),直到你取消挂起 CronJob 为止。

注意:

在调度时间内挂起的执行都会被统计为错过的任务。当现有的 CronJob 将 .spec.suspendtrue 改为 false 时, 且没有开始的最后期限,错过的任务会被立即调度。

任务历史限制

.spec.successfulJobsHistoryLimit.spec.failedJobsHistoryLimit 字段是可选的。 这两个字段指定应保留多少已完成和失败的任务。 默认设置分别为 3 和 1。将限制设置为 0 代表相应类型的任务完成后不会保留。

有关自动清理任务的其他方式, 请参见自动清理完成的 Job

时区

特性状态: Kubernetes v1.27 [stable]

对于没有指定时区的 CronJob, kube-controller-manager 基于本地时区解释排期表(Schedule)。

你可以通过将 .spec.timeZone 设置为一个有效时区的名称, 为 CronJob 指定一个时区。例如设置 .spec.timeZone: "Etc/UTC" 将告诉 Kubernetes 基于世界标准时间解读排期表。

Go 标准库中的时区数据库包含在二进制文件中,并用作备用数据库,以防系统上没有可用的外部数据库。

CronJob 的限制

不支持的时区规范

Kubernetes 1.29 中的 CronJob API 实现允许你设置 .spec.schedule 字段,在其中包括时区信息; 例如 CRON_TZ=UTC * * * * *TZ=UTC * * * * *

以这种方式指定时区是 未正式支持的(而且也从未正式支持过)。

如果你尝试设置包含 TZCRON_TZ 时区规范的排期表, Kubernetes 会向客户端报告一条警告。 后续的 Kubernetes 版本将完全阻止设置非正式的时区机制。

修改 CronJob

按照设计,CronJob 包含一个用于 Job 的模板。 如果你修改现有的 CronJob,你所做的更改将应用于修改完成后开始运行的新任务。 已经开始的任务(及其 Pod)将继续运行而不会发生任何变化。 也就是说,CronJob 会更新现有任务,即使这些任务仍在运行。

Job 创建

CronJob 根据其计划编排,在每次该执行任务的时候大约会创建一个 Job。 我们之所以说 “大约”,是因为在某些情况下,可能会创建两个 Job,或者不会创建任何 Job。 我们试图使这些情况尽量少发生,但不能完全杜绝。因此,Job 应该是 幂等的

如果 startingDeadlineSeconds 设置为很大的数值或未设置(默认),并且 concurrencyPolicy 设置为 Allow,则作业将始终至少运行一次。

注意:

如果 startingDeadlineSeconds 的设置值低于 10 秒钟,CronJob 可能无法被调度。 这是因为 CronJob 控制器每 10 秒钟执行一次检查。

对于每个 CronJob,CronJob 控制器(Controller) 检查从上一次调度的时间点到现在所错过了调度次数。如果错过的调度次数超过 100 次, 那么它就不会启动这个任务,并记录这个错误:

  1. Cannot determine if job needs to be started. Too many missed start time (> 100). Set or decrease .spec.startingDeadlineSeconds or check clock skew.

需要注意的是,如果 startingDeadlineSeconds 字段非空,则控制器会统计从 startingDeadlineSeconds 设置的值到现在而不是从上一个计划时间到现在错过了多少次 Job。 例如,如果 startingDeadlineSeconds200,则控制器会统计在过去 200 秒中错过了多少次 Job。

如果未能在调度时间内创建 CronJob,则计为错过。 例如,如果 concurrencyPolicy 被设置为 Forbid,并且当前有一个调度仍在运行的情况下, 试图调度的 CronJob 将被计算为错过。

例如,假设一个 CronJob 被设置为从 08:30:00 开始每隔一分钟创建一个新的 Job, 并且它的 startingDeadlineSeconds 字段未被设置。如果 CronJob 控制器从 08:29:0010:21:00 终止运行,则该 Job 将不会启动, 因为其错过的调度次数超过了 100。

为了进一步阐述这个概念,假设将 CronJob 设置为从 08:30:00 开始每隔一分钟创建一个新的 Job, 并将其 startingDeadlineSeconds 字段设置为 200 秒。 如果 CronJob 控制器恰好在与上一个示例相同的时间段(08:29:0010:21:00)终止运行, 则 Job 仍将从 10:22:00 开始。 造成这种情况的原因是控制器现在检查在最近 200 秒(即 3 个错过的调度)中发生了多少次错过的 Job 调度,而不是从现在为止的最后一个调度时间开始。

CronJob 仅负责创建与其调度时间相匹配的 Job,而 Job 又负责管理其代表的 Pod。

接下来

  • 了解 CronJob 所依赖的 PodJob 的概念。
  • 阅读 CronJob .spec.schedule 字段的详细格式
  • 有关创建和使用 CronJob 的说明及 CronJob 清单的示例, 请参见使用 CronJob 运行自动化任务
  • CronJob 是 Kubernetes REST API 的一部分, 阅读 CronJob API 参考了解更多细节。