Kubernetes CronJob 设计与实现

Kubernetes CronJob 设计与实现

💡 原文中文,约8700字,阅读约需21分钟。
📝

内容提要

本文从源码角度分析Kubernetes v1.28中CronJob的实现原理。CronJob控制器位于pkg/controller/cronjob/,核心为ControllerV2,通过工作队列和多个worker循环处理CronJob对象。sync方法获取CronJob及其关联Job,清理已完成Job后调用syncCronJob执行同步:更新Job状态、计算下次执行时间、处理StartingDeadlineSeconds延迟策略,并按Allow、Forbid、Replace三种并发策略创建或替换Job,最后更新状态与运行Job列表。

🔎

延伸解读

CronJob 的调度依赖队列延迟重入

控制器并非持续轮询所有 CronJob,而是由 worker 从限速队列取出对象执行 sync。同步成功后,若计算出下次执行时间,会先 Forget 当前 key,再用 AddAfter 延迟放回队列。这意味着 CronJob 的定时触发依赖队列的延迟机制,而非独立定时器;同步出错时则通过 AddRateLimited 重试,读者可据此理解控制器如何兼顾定时与失败重试。

StartingDeadlineSeconds 决定错过任务是否补跑

源码在计算 scheduledTime 后,若设置了 StartingDeadlineSeconds,会判断 scheduledTime 加上该截止时间是否早于当前时间,若是则标记 tooLate 并直接返回下次执行时间,不再创建 Job。这说明该字段用于限制错过调度后的补跑窗口,超过窗口的任务会被放弃,而不是无限期补执行,配置时需权衡容忍延迟的程度。

Replace 并发策略可能浪费已完成大部分的工作

文章指出,Replace 策略下若新 Job 到点而老 Job 仍在运行,控制器会逐个删除运行列表中的老 Job 再创建新 Job。源码注释提醒,对幂等型 Job 这可能造成资源浪费,例如老 Job 已完成 90% 仍被终止;而批量型 Job 如每次处理固定数量事件则不受影响。选择并发策略时应结合任务是否可中断来判断。

运行列表与 Job 实际存在性需双向核对

syncCronJob 会建立 Job Map,并遍历 CronJob 的 Status.Active 列表:若列表中的 Job 已无法从 API Server 查到,会将其从运行列表删除,避免过期附属关系让 CronJob 一直显示运行中。同时,对已完成 Job 会更新 LastSuccessfulTime 并移出运行列表。这体现了控制器通过双向核对维持状态一致性,而非只依赖 informer 缓存。

❓

Q&A

Kubernetes CronJob 控制器在源码中的核心结构体是什么?

核心结构体是 ControllerV2,位于 pkg/controller/cronjob/ 目录,包含工作队列 queue、kubeClient、jobLister 和 cronJobLister 等字段。

CronJob 控制器是如何启动并处理任务的?

通过 NewControllerV2 初始化,在 startCronJobController 中启动一个 goroutine 调用 Run 方法,Run 根据配置启动多个 worker,每个 worker 循环从队列取出 CronJob 对象并调用 sync 方法进行同步。

CronJob 的 sync 方法主要做了哪些事情?

sync 方法根据 key 获取 CronJob 对象,获取关联的 Job 列表,清理已完成的 Job,调用 syncCronJob 执行同步(更新状态、计算下次执行时间、处理延迟策略和并发策略、创建或替换 Job),最后更新 CronJob 状态并返回下次执行时间。

CronJob 的 StartingDeadlineSeconds 字段有什么作用?

StartingDeadlineSeconds 表示任务如果错过调度时间,开始该任务的最后截止时间。如果当前时间超过了 scheduledTime 加上该秒数,则判定为 tooLate,启动延迟策略并直接返回下次执行时间,不创建 Job。

CronJob 支持哪几种并发策略?分别是什么含义?

支持三种:Allow(默认)允许并发执行;Forbid 不允许并发,如果老 Job 未完成则忽略新 Job;Replace 用新 Job 替换正在运行的老 Job。

使用 Replace 并发策略可能会带来什么问题?

Replace 策略可能会引起资源浪费,例如一个老 Job 还在运行且已完成 90%,仍会被直接删除终止,导致已完成的工作白白浪费。但对于批量型 Job(如每次处理 10W 个 MQ 事件)则不存在此问题。

🏷️

标签

➡️

继续阅读