周期性作业(Periodic Job)模式通过添加时间维度扩展了批处理作业(Batch Job)模式,并允许由时间事件触发一个工作单元的执行。

问题描述

在分布式系统和微服务的世界里,使用 HTTP 和轻量级消息传递的实时和事件驱动的应用交互是一个明显的趋势。然而,无论软件开发的最新趋势如何,作业调度都有着悠久的历史,而且它仍然具有相关性。周期性作业通常用于自动化系统维护或管理任务。它们也适用于需要定期执行特定任务的业务应用。这里典型的例子是通过文件传输进行业务对业务的集成,通过数据库轮询进行应用集成,发送通讯邮件,以及清理和归档旧文件。

为系统维护目的处理周期性作业的传统方法是使用专门的调度软件或 Cron。然而,对于简单的使用案例来说,专门的软件可能会很昂贵,而且在单一服务器上运行的 Cron 作业很难维护,并且是一个单一的故障点。这就是为什么,很多时候,开发人员倾向于实现既能处理调度方面,又能处理需要执行的业务逻辑的解决方案。例如,在 Java 世界中,Quartz、Spring Batch 等库以及带有 ScheduledThreadPoolExecutor 类的自定义实现都可以运行时态任务。但与 Cron 类似,这种方法的主要难点是使调度能力具有弹性和高可用性,从而导致资源的高消耗。另外,采用这种方法,基于时间的任务调度器是应用程序的一部分,要使调度器高可用,必须使整个应用程序高可用。通常情况下,这涉及到运行多个应用实例,而且是同时运行,确保只有一个实例是活跃的,并调度作业 — 这涉及到领导者选举和其他分布式系统的挑战。

最后,一个简单的服务,每天要复制几个文件一次,最终可能需要多个节点,一个分布式的领导者选举机制等等。Kubernetes CronJob 的实现解决了所有这些问题,它允许使用著名的 Cron 格式来调度 Job 资源,让开发人员只专注于实现要执行的工作,而不是时间上的调度方面。

解决方案

在第 7 章 “批处理作业” 中,我们看到了 Kubernetes Job 的用例和功能。所有这些也适用于本章,因为 CronJob 建立在 Job 之上。CronJob 实例类似于 Unix Crontab(cron 表)的一行,管理 Job 的时间方面。它允许在指定的时间点周期性地执行一个 Job。见例 8-1 的示例定义。

  1. # 例 8-1 一个 CronJob 资源
  2. ---
  3. apiVersion: batch/v1beta1
  4. kind: CronJob
  5. metadata:
  6. name: random-generator
  7. spec:
  8. # 每 3 分钟运行一次的 Cron 规范表述
  9. schedule: "*/3 * * * *"
  10. jobTemplate:
  11. spec:
  12. # 使用与常规作业相同规格的 Job 模板
  13. template:
  14. spec:
  15. containers:
  16. - image: k8spatterns/random-generator:1.0
  17. name: random-generator
  18. command: [ "java", "-cp", "/", "RandomRunner", "/numbers.txt", "10000" ]
  19. restartPolicy: OnFailure

除了 Job 规格字段,CronJob 还有其他字段来定义它的时间方面:

  • .spec.schedule:用于指定作业计划的 Crontab 条目(例如,0 表示每小时运行一次)。
  • .spec.startedDeadlineSeconds:如果任务错过了预定时间,启动任务的最后期限(以秒为单位)。在某些使用情况下,任务只有在一定的时间范围内执行才有效,如果执行得晚,就会减少使用。例如,如果一个 Job 因为缺乏计算资源或其他缺失的依赖关系而没有在所需的时间内执行,那么最好跳过一次执行,因为它应该处理的数据已经过时了。
  • .spec.concurrencyPolicy:指定如何管理同一 CronJob 创建的作业的并发执行。默认行为 Allow 会创建新的 Job 实例,即使之前的 Job 还没有完成。如果这不是所需的行为,可以使用 Forbid 跳过当前运行的作业,或者使用 Replace 取消当前正在运行的作业,并开始一个新的作业。
  • .spec.suspend:暂停所有后续的执行而不影响已经开始的执行。
  • .spec.successfulJobsHistoryLimit.spec.failedJobsHistoryLimit:指定应保留多少个已完成和未完成的作业的字段,以便进行审计。

CronJob 是一个非常专业的核心资源,它只适用于工作单元具有时间维度的情况。即使 CronJob 不是一个通用的基本要素,它也是一个很好的例子,说明 Kubernetes 的功能是如何建立在彼此之上的,并且也是一个非云本地的用例。

一些讨论

正如你所看到的,CronJob 是一个非常简单的基本要素,它将集群、类似 Cron 的行为添加到现有的 Job 定义中。但当它与其他基本要素(如 Pod、容器资源隔离)和其他 Kubernetes 特性(如第 6 章 “自动放置” 或第 4 章 “健康探针” 中描述的特性)相结合时,它最终会成为一个非常强大的 Job 调度系统。这使得开发人员能够只专注于问题域,并实现一个只负责执行业务逻辑的容器化应用。调度是在应用之外形成的,作为平台的一部分,它具有所有的附加优势,如高可用性、弹性、容量和策略驱动的 Pod 放置。当然,与 Job 的实现类似,在实现 CronJob 容器时,你的应用必须考虑重复运行、无运行、并行运行或取消运行的所有角落和故障情况。

参考资料