DEBUG.WIKI
V.PS:让项目靠近用户,亚太、欧洲、北美 KVM VPS
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国

CronJob 设置 Forbid 后,手动创建的多个 Job 仍同时运行

手动从 CronJob 模板创建 Job 不经过计划调度的并发检查,parallelism 也只作用于单个 Job。区分触发入口后,按人工维护或统一互斥方式控制重叠执行。

CronJob 已经设置 concurrencyPolicy: Forbid,通过 kubectl create job --from=cronjob/... 手动创建两个不同名称的 Job,却仍看到它们同时执行。把 Job 模板中的 parallelism 改成 1,也没有阻止第二个 Job 启动。

这是两层控制范围混在一起了:Forbid 约束 CronJob 控制器的计划调度;parallelism 控制单个 Job 的并行度。手动从模板创建 Job,不等于向 CronJob 控制器申请一次受并发策略约束的调度。

以下使用 batch/v1 CronJob,适用于支持该 API 的 Kubernetes 1.21 及以后集群。具体生成的元数据要以所用 kubectl 版本为准,不能靠 Job 名称或是否存在 ownerReference 单独判断它受到了互斥保护。

先区分两个 Job 与一个 Job 的多个 Pod

在正确的集群和命名空间读取资源。ops、daily-report、manual-report-1 都是占位名称:

kubectl -n ops get cronjob daily-report -o yaml
kubectl -n ops get jobs
kubectl -n ops get job manual-report-1 -o yaml
kubectl -n ops get pods -l batch.kubernetes.io/job-name=manual-report-1

核对每个 Job 的 UID、创建时间、模板和 Pod 归属。如果是两个不同的 Job 各自启动了一个 Pod,parallelism: 1 没有被跨 Job 地应用,这是预期范围。若同一个 Job 出现多个 Pod,则要继续核对失败重试、终止中的 Pod 及 Job 状态,不能直接归到手动触发的问题。

较旧版本的 Pod 标签可能使用 job-name。选择器没有结果时,查看实际标签和 ownerReferences,不要直接认定没有 Pod。CronJob 的 status.active 也不是手动创建接口上的原子互斥条件。

--from 复制模板,不会替你检查 Forbid

可以只生成对象,查看本机 kubectl 准备提交什么:

kubectl -n ops create job manual-report-preview \
  --from=cronjob/daily-report --dry-run=client -o yaml

保留 --dry-run=client:它不创建 Job,但为了读取 CronJob 模板,仍需要访问对应集群。

生成对象的 kind 是 Job,其 spec 来自 CronJob 的 jobTemplate.spec。concurrencyPolicy 位于 CronJob spec,不是 Job 的并发准入开关。这个创建路径不会因为已经有活动 Job 就自动等待,也不会因为模板来源配置了 Forbid 就拒绝第二个不同名称的 Job。

某些版本生成的对象还带有指向 CronJob 的 ownerReferences,以及用于标记手动实例化的注解。归属信息不能代替创建时的互斥检查;不要通过删除或补写 ownerReferences 来“修复”并发控制。

偶尔补跑时,先控制所有触发入口

如果只是维护窗口内补跑一次,先停止其他人工、CI 和接口触发入口,由一个明确的操作者负责这一轮。保存 CronJob 当前配置,核对计划时间和已有 Job,再按维护流程临时将 CronJob 的 spec.suspend 设置为 true,阻止后续计划调度。

暂停只影响后续调度,不会停止已经开始的 Job,也不会禁止任何拥有权限的客户端直接创建 Job。因此必须等相关活动任务结束,并确认没有其他操作者或自动化继续提交。仅执行一次“列出 Job,发现为空,再创建”存在时间窗口,不能作为多人并发环境里的可靠锁。

完成补跑后,核对 Job 和实际业务产物,再恢复原来的暂停状态。恢复前检查 startingDeadlineSeconds 与暂停期间漏掉的计划:取消暂停可能触发补跑,不应在手工任务仍运行时直接恢复调度。不要为了腾出执行位置删除一个仍在写入数据的 Job。

这种流程适合可以封闭触发入口的人工维护。如果无法控制所有入口,就不能把它当成严格的并发保证。

经常手动触发时,把互斥放到共同入口

定时执行和手动执行都必须经过同一处串行调度或同一套业务互斥机制,才能约束它们之间的重叠。只在手动按钮前加检查,而 CronJob 继续直接运行任务,仍会留下两条互不约束的路径。

已有任务平台提供并发上限时,让定时和手动请求都交给该平台;业务已有可靠的跨实例锁时,让所有任务实例在开始实际工作前参与同一锁。采用哪种方式取决于任务的失败恢复、重试和数据写入要求,不能用两个独立 Job 各自的 parallelism: 1 替代。

也不要简单将 Forbid 换成 Replace。后者会影响控制器处理既有任务的方式,却不会让任意客户端创建 Job 的请求自动排队,还可能中止正在进行的工作。

验证实际工作没有重叠

在隔离环境中分别覆盖计划触发、手动触发,以及两者接近同时发生的情形。检查 Job/Pod 状态之外,还要记录业务开始和结束时刻:验收目标是只有允许的实例进入实际工作,其他请求明确等待或被拒绝,不能只看提交命令是否成功。

Kubernetes Job 与 CronJob 都不应被当作业务层“恰好执行一次”的保证。任务仍要考虑重试和重复执行造成的副作用。回退调度或锁配置前,先停止新请求并核对正在运行的任务;恢复旧配置不会撤销已经发生的数据写入。