此文档中的信息可能已过时

此文档的更新日期比原文晚,因此其中的信息可能已过时。如果能阅读英文,请查看英文版本以获取最新信息: CronJob

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 示例清单会在每分钟打印出当前时间和问候消息:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox:1.28
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure

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

编写 CronJob 声明信息

Cron 时间表语法

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

# ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)(周日到周六)
# │ │ │ │ │                          或者是 sun,mon,tue,web,thu,fri,sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *

例如 0 3 * * 1 表示此任务计划于每周一凌晨 3 点运行。

该格式也包含了扩展的 “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 指定通用的元数据, 例如标签注解。 有关如何编写一个 Job 的 .spec, 请参考编写 Job 规约

Job 延迟开始的最后期限

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

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

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

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

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

并发性规则

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

  • Allow(默认):CronJob 允许并发 Job 执行。
  • Forbid:CronJob 不允许并发执行;如果新 Job 的执行时间到了而老 Job 没有执行完,CronJob 会忽略新 Job 的执行。 另请注意,当老 Job 执行完成时,仍然会考虑 .spec.startingDeadlineSeconds,可能会导致新的 Job 执行。
  • Replace:如果新 Job 的执行时间到了而老 Job 没有执行完,CronJob 会用新 Job 替换当前正在运行的 Job。

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

调度挂起

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

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

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

任务历史限制

.spec.successfulJobsHistoryLimit.spec.failedJobsHistoryLimit 字段指定应保留多少已完成和失败的 Job。这两个字段都是可选的。

  • .spec.successfulJobsHistoryLimit:此字段指定要保留多少成功完成的 Job。默认值为 3。 将此字段设置为 0 意味着不会保留任何成功的 Job。

  • .spec.failedJobsHistoryLimit:此字段指定要保留多少失败完成的 Job。默认值为 1。 将此字段设置为 0 意味着不会保留任何失败的 Job。

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

时区

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

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

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

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

CronJob 的限制

不支持的时区规范

.spec.schedule 中通过 CRON_TZTZ 变量来指定时区并未得到官方支持(而且从未支持过)。

从 Kubernetes 1.29 版本开始,如果你尝试设定包含 TZCRON_TZ 时区规范的排期表, Kubernetes 将无法创建该资源,并会报告验证错误。 对已经设置 TZCRON_TZ 的 CronJob 进行更新时, 系统会继续向客户端发送警告

修改 CronJob

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

Job 创建

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

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

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

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 参考了解更多细节。