批处理任务的场景与挑战

业务里有一大类工作不是「一直在跑的服务」,而是跑完就结束的任务:凌晨做数据迁移、每天定时生成报表、定期清理过期日志与缓存、批量给用户发通知。这类任务以前经常跑在服务器 crontab 或 Jenkins 里,问题也不少:脚本散落、没有重试、失败靠人工发现、执行机器挂了任务就静默消失。

Kubernetes 为此提供了两个原生控制器:Job 负责「跑一次就退出」的一次性批处理,CronJob 负责按 cron 表达式定时的任务。它们把任务变成了集群里的资源:有状态可查询(kubectl get jobs)、失败可重试(backoffLimit)、并发可控(parallelism)、过期自动清理(TTL),比在虚拟机里堆 crontab 靠谱得多。

# 任务在 K8s 里的样子
Job ──创建──▶ Pod(跑完退出)──▶ 完成状态(可查询、可清理)
CronJob ──按 schedule 定时──▶ 每次生成一个 Job ──▶ Pod 跑完即止
本文核心知识点
  • Job一次性批处理:跑完即止、失败可重试。
  • CronJob定时任务:cron 表达式驱动、并发可控。
  • 编排并行度、依赖顺序、任务队列。
  • 监控成功率、延迟与告警闭环。
Kubernetes 批处理与定时任务配图:左侧服务器机箱与大时钟表盘外圈分布任务节点小方块、右侧排队点亮的任务队列与沙漏箭头的蓝色扁平科技插画
把「跑完就结束」的任务交给集群,失败可重试、状态可查询。

Job 基础:跑一次就退出

Job 的语义很明确:创建一组 Pod,等它们全部成功退出。Pod 里的容器如果正常结束(exit 0),Job 就标记完成;如果失败,Job 按 backoffLimit 自动重新创建 Pod 重试,超过上限就标记失败。一个关键配置是 restartPolicy 必须设为 Never 或 OnFailure(服务类 Pod 常用的 Always 在 Job 里不合法,因为任务要允许退出)。

# job-migrate.yaml:一次性数据迁移任务
apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  backoffLimit: 3          # 最多重试 3 次
  activeDeadlineSeconds: 600  # 超过 10 分钟整体放弃
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: migrate
          image: registry.example.com/db-tools:2.1
          command: ["/bin/sh", "-c", "db-migrate --up"]

想要「并发跑 N 个」就用 parallelism + completions:比如 completions: 8, parallelism: 2 表示总共要成功 8 个、同时最多跑 2 个。适合把大任务拆成分片并行处理;配合「按序号分片」的环境变量,同一个镜像就能处理不同的数据切片。

CronJob:像 crontab 一样跑定时任务

CronJob 在 Job 外面加了一层「定时触发」:按 schedule(标准 5 段 cron 表达式,如 0 2 * * * 表示每天凌晨两点)到点生成一个新的 Job。它有两个必须认真配的参数:concurrencyPolicy 决定上一轮还没跑完时这一轮怎么办(Allow 并发跑 / Forbid 跳过 / Replace 顶掉旧的),startingDeadlineSeconds 决定错过调度窗口后还补不补跑。

# cronjob-cleanup.yaml:每天 02:00 清理过期数据
apiVersion: batch/v1
kind: CronJob
metadata:
  name: daily-cleanup
spec:
  schedule: "0 2 * * *"
  concurrencyPolicy: Forbid     # 上一轮没跑完就跳过本轮
  startingDeadlineSeconds: 300
  jobTemplate:
    spec:
      backoffLimit: 2
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: cleanup
              image: registry.example.com/cleaner:1.4
              args: ["--older-than=30d"]

历史 Job 会越积越多,用 successfulJobsHistoryLimit 和 failedJobsHistoryLimit 控制保留条数(比如各留 3 条),既保住「可回看最近执行」又不至于把 etcd 撑爆。排查时 kubectl get cronjobs 看上次调度时间、kubectl get jobs 看最近的执行,一眼能判断「到点没跑」还是「跑了失败」。

任务参数化与失败重试

同一个任务脚本要跑不同参数(比如按日期回补数据),不要写死镜像,用环境变量或命令行参数把变化点暴露出来。Job 里通过 env 传入参数,CronJob 想「每天带当天日期」可以在 Job 模板里加 date +%F 之类的容器内替换,或者挂 ConfigMap 动态更新。

失败的兜底设计比「尽量不失败」更重要:任务要幂等(重复跑同一参数结果一致,可以安全重试),重试要有上限(backoffLimit 挡住死循环),成功要有明确退出码(脚本里区分「业务失败」与「环境故障」,别把数据库连不上也当成业务结果返回 0)。

# 参数化 + 主动清理(TTL 自动删完成 Job)
apiVersion: batch/v1
kind: Job
metadata:
  name: report-gen
  annotations:
    batch.kubernetes.io/job-ttl: "3600"   # 完成后 1 小时自动清理
spec:
  ttlSecondsAfterFinished: 3600
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: report
          image: registry.example.com/reporter:3.2
          env:
            - name: REPORT_DATE
              value: "2026-10-28"
            - name: OUTPUT_BUCKET
              value: "s3://reports/monthly"
          command: ["/bin/sh", "-c", "gen-report --date $REPORT_DATE --out $OUTPUT_BUCKET && exit 0 || exit 1"]

ttlSecondsAfterFinished 是省心的关键:完成或失败的 Job 到点自动被回收,不用定期手动清理。要排查历史时,把失败任务的历史保留期适当调大,成功任务调小,兼顾「能复盘」与「不占资源」。