Skip to content

Job

  • https://kuboard.cn/learning/k8s-intermediate/workload/wl-job/

  • https://kubernetes.io/docs/concepts/workloads/controllers/job/

Kubernetes中的 Job 对象将创建一个或多个 Pod,并确保指定数量的 Pod 可以成功执行到进程正常结束:

  • 当 Job 创建的 Pod 执行成功并正常结束时,Job 将记录成功结束的 Pod 数量
  • 当成功结束的 Pod 达到指定的数量时,Job 将完成执行
  • 删除 Job 对象时,将清理掉由 Job 创建的 Pod

例子

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    spec:
      containers:
      - name: pi
        image: perl
        command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
      restartPolicy: Never
  backoffLimit: 4

执行结果可以通过 kubectl logs <pod-name> 查看。

1. 创建 Job

1.1 Pod Template

指定合适的重启策略 restartPolicy .spec.template.spec.restartPolicy,此处只允许使用 NeverOnFailure 两个取值。

1.2 Parallel Jobs

有三种主要的任务类型适合使用 Job 运行:

  • Non-parallel Jobs
    • 通常,只启动一个 Pod,除非该 Pod 执行失败
    • Pod 执行成功并结束以后,Job 也立刻进入完成 completed 状态
  • Parallel Jobs with a fixed completion count
    • .spec.completions 为一个非零正整数
    • Job 将创建至少 .spec.completions 个 Pod,编号为 1 - .spec.completions尚未实现
    • Job 记录了任务的整体执行情况,当 1 - .spec.completions 中每一个编号都有一个对应的 Pod 执行成功时,Job 进入完成状态
  • Parallel Jobs with a work queue
    • 不指定 .spec.completions,使用 .spec.parallelism
    • Pod 之间必须相互之间自行协调并发,或者使用一个外部服务决定每个 Pod 各自执行哪些任务。例如,一个 Pod 可能会从工作队列中获取多达 n 个项目的批处理。
    • 每个 Pod 都可以独立判断其他同僚(peers)是否完成,并确定整个Job是否完成
    • 当 Job 中任何一个 Pod 成功结束,将不再为其创建新的 Pod
    • 当所有的 Pod 都结束了,且至少有一个 Pod 执行成功后才结束,则 Job 判定为成功结束
    • 一旦任何一个 Pod 执行成功并退出,Job 中的任何其他 Pod 都应停止工作和输出信息,并开始终止该 Pod 的进程

completions 和 parallelism

  • 对于 non-parallel Job,.spec.completions.spec.parallelism 可以不填写,默认值都为 1
  • 对于 fixed completion count Job,需要设置 .spec.completions 为您期望的个数;同时不设置 .spec.parallelism 字段(默认值为 1)
  • 对于 work queue Job,不能设置 .spec.completions 字段,且必须设置 .spec.parallelism 为0或任何正整数

1.3 Controlling Parallelism 并发控制

并发数 .spec.parallelism 可以被设置为0或者任何正整数,如果不设置,默认为1,如果设置为 0,则 Job 被暂停,直到该数字被调整为一个正整数。

实际的并发数(同一时刻正在运行的 Pod 数量)可能比设定的并发数 .spec.parallelism 要大一些或小一些,不一定严格相等,主要的原因有:

  • 对于 fixed completion count Job,实际并发运行的 Pod 数量不会超过剩余未完成的数量。如果 .spec.parallelism 比这个数字更大,将被忽略
  • 对于 work queue Job,任何一个 Pod 成功执行后,将不再创建新的 Pod (剩余的 Pod 将继续执行)
  • Job 控制器可能没有足够的时间处理并发控制
  • 如果 Job 控制器创建 Pod 失败(例如,ResourceQuota 不够用,没有足够的权限等)
  • 同一个Job中,在已创建的 Pod 出现大量失败的情况下,Job 控制器可能限制 Pod 的创建
  • 当 Pod 被优雅地关闭时(gracefully shut down),需要等候一段时间才能结束

2. 处理Pod和容器的失败

Pod 中的容器可能会因为多种原因执行失败,例如:

  • 容器中的进程退出了,且退出码(exit code)不为 0
  • 容器因为超出内存限制而被 Kill
  • 其他原因

如果 Pod 中的容器执行失败,且 .spec.template.spec.restartPolicy = "OnFailure",则 Pod 将停留在该节点上,但是容器将被重新执行。此时,您的应用程序需要处理在原节点(失败之前的节点)上重启的情况。或者,您也可以设置为 .spec.template.spec.restartPolicy = "Never"

整个 Pod 也可能因为多种原因执行失败,例如:

  • Pod 从节点上被驱逐(节点升级、重启、被删除等)
  • Pod 的容器执行失败,且 .spec.template.spec.restartPolicy = "Never"

当 Pod 执行失败时,Job 控制器将创建一个新的 Pod。此时,您的应用程序需要处理在一个新 Pod 中重新启动的情况。具体来说,需要处理临时文件、锁、未完成的输出信息以及前一次执行可能遗留下来的其他东西。

Warning

  • 即使您指定 .spec.parallelism = 1.spec.completions = 1 以及 .spec.template.spec.restartPolicy = "Never",同一个应用程序仍然可能被启动多次
  • 如果指定 .spec.parallelism.spec.completions 的值都大于 1,则,将可能有多个 Pod 同时执行。此时,您的 Pod 还必须能够处理并发的情况

2.1 Pod 失败重试策略

某些情况下(例如,配置错误),您可能期望在 Job 多次重试仍然失败的情况下停止该 Job。此时,可通过 .spec.backoffLimit 来设定 Job 最大的重试次数。该字段的默认值为 6。

Job 中的 Pod 执行失败之后,Job 控制器将按照一个指数增大的时间延迟(10s,20s,40s ... 最大为 6 分钟)来多次重新创建 Pod。如果没有新的 Pod 执行失败,则重试次数的计数将被重置。

建议在 debug 时,设置 restartPolicy = "Never",或者使用日志系统确保失败的 Job 的日志不会丢失。

3. Job的终止和清理

当 Job 完成后:

  • 将不会创建新的 Pod
  • 已经创建的 Pod 也不会被清理掉。此时,您仍然可以继续查看已结束 Pod 的日志,以检查 errors/warnings 或者其他诊断用的日志输出
  • Job 对象也仍然保留着,以便您可以查看该 Job 的状态
  • 由用户决定是否删除已完成的 Job 及其 Pod
    • 可通过 kubectl 命令删除 Job,例如: kubectl delete jobs/pi 或者 kubectl delete -f https://kuboard.cn/statics/learning/job/job.yaml
    • 删除 Job 对象时,由该 Job 创建的 Pod 也将一并被删除

Job 通常会顺利的执行下去,但是在如下情况可能会非正常终止:

  • 某一个 Pod 执行失败(且 restartPolicy=Never
  • 或者某个容器执行出错(且 restartPolicy=OnFailure
    • 此时,Job 按照上节《Pod 失败重试策略》 .spec.bakcoffLimit 描述的方式进行处理
    • 一旦重试次数达到了 .spec.backoffLimit 中的值,Job 将被标记为失败,且尤其创建的所有 Pod 将被终止
  • Job 中设置了 .spec.activeDeadlineSeconds。该字段限定了 Job 对象在集群中的存活时长,一旦达到 .spec.activeDeadlineSeconds (比 .spec.backoffLimit的优先级高)指定的时长,该 Job 创建的所有的 Pod 都将被终止,Job 的 Status 将变为 type:Failedreason: DeadlineExceeded

4. Job的自动清理

系统中已经完成的 Job 通常是不在需要里的,长期在系统中保留这些对象,将给 apiserver 带来很大的压力。如果通过更高级别的控制器(例如 CronJobs)来管理 Job,则 CronJob 可以根据其中定义的基于容量的清理策略(capacity-based cleanup policy)自动清理Job。

4.1 TTL 机制

FEATURE STATE: Kubernetes v1.12 [alpha]

除了 CronJob 之外,TTL 机制是另外一种自动清理已结束Job(CompletedFinished)的方式:

  • TTL 机制由 TTL 控制器 提供
  • 在 Job 对象中指定 .spec.ttlSecondsAfterFinished 字段可激活该特性

  • .spec.ttlSecondsAfterFinished 值为 100,则,在其结束 100 秒之后,将可以被自动删除

  • 如果 .spec.ttlSecondsAfterFinished 被设置为 0,则 TTL 控制器在 Job 执行结束后,立刻就可以清理该 Job 及其 Pod
  • 如果 .spec.ttlSecondsAfterFinished 值未设置,则 TTL 控制器不会清理该 Job

5. Job 模式

Kubernetes Job 对象可以用来支持 Pod 的并发执行,但是:

  • Job 对象并非设计为支持需要紧密相互通信的Pod的并发执行,例如科学计算。
  • Job 对象支持并发处理一系列相互独立但是又相互关联的工作任务,例如:
    • 发送邮件
    • 渲染页面
    • 转码文件
    • 扫描 NoSQL 数据库中的主键
    • 其他

在一个复杂的系统中,可能存在多种类型的工作任务,本文只考虑批处理任务(batch job)。

  • 每个工作任务一个 Job 对象 v.s. 一个 Job 对象负责所有的工作任务
    • 当工作任务特别多时,第二种选择(一个 Job 对象负责所有的工作任务)更合适一些
    • 第一种选择(每个工作任务一个 Job 对象)将为管理员和系统带来很大的额外开销,因为要管理很多数量的 Job 对象
  • Pod的数量与工作任务的数量相等 v.s. 每个Pod可以处理多个工作任务
    • 第一种选择(Pod的数量与工作任务的数量相等)通常只需要对现有的代码或容器做少量的修改
    • 第二种选择(每个Pod可以处理多个工作任务)更适合工作任务的数量特别多的情况,相较于第一种选择可以降低系统开销
  • 使用工作队列,此时:
    • 需要运行一个队列服务
    • 需要对已有的程序或者容器做修改,以便其可以配合队列工作
    • 如果是一个已有的程序,改造时可能存在难度
模式 单个 Job 对象 Pod的数量少于工作任务 无需修改已有代码 在 Kube 1.1 可以运行
Job Template Expansion
Queue with Pod Per Work Item sometimes
Queue with Variable Pod Count
Single Job with Static Work Assignment

当您指定 .spec.completions 时,Job 控制器创建的每个 Pod 都有一个相同的 spec。这意味着,同一个 Job 创建的所有的 Pod 都使用:

  • 相同的执行命令
  • 相同的容器镜像
  • 相同的数据卷
  • 相同的环境变量(例如,不同时间点创建的Pod,Service的环境变量 可能会不同)

Job 的不同模式本质上讲,是如何为一组工作任务分配 Pod。下表总结了不同的模式下 .spec.parallelism.spec.completions 字段的设置。(表中 w 代表工作任务的数量)

Pattern .spec.completions .spec.parallelism
Job Template Expansion 1 should be 1
Queue with Pod Per Work Item W any
Queue with Variable Pod Count 1 any
Single Job with Static Work Assignment W any