概念

Kubernetes v1.16 版本的文档已不再维护。您现在看到的版本来自于一份静态的快照。如需查阅最新文档,请点击 最新版本。

Edit This Page

Job 从运行到完成

Job 创建一个或多个 pod,并且确保指定数量的 pod 成功终止。Pod 成功完成后,Job 将跟踪成功完成的情况。当达到指定的成功完成次数时,任务(即 Job)就完成了。删除 Job 将清除其创建的 pod。

一种简单的情况时创建一个 Job 对象,以便可靠地运行一个 Pod 来完成。如果第一个 pod 失败了或被删除了(例如,由于节点硬件故障或节点重启),则 Job 对象将启动一个新的 pod。

也可以使用 Job 去并行运行多个 pod。

运行一个 Job 示例

下面是一个 Job 配置样例。它计算 π 到2000位并将其打印出来。大约需要 10 秒钟才能完成。

controllers/job.yaml
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 apply -f https://k8s.io/examples/controllers/job.yaml
job "pi" created

使用 kubectl 命令检查 Job 的状态:

kubectl describe jobs/pi
Name:             pi
Namespace:        default
Selector:         controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
Labels:           controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                  job-name=pi
Annotations:      <none>
Parallelism:      1
Completions:      1
Start Time:       Tue, 07 Jun 2016 10:56:16 +0200
Pods Statuses:    0 Running / 1 Succeeded / 0 Failed
Pod Template:
  Labels:       controller-uid=b1db589a-2c8d-11e6-b324-0209dc45a495
                job-name=pi
  Containers:
   pi:
    Image:      perl
    Port:
    Command:
      perl
      -Mbignum=bpi
      -wle
      print bpi(2000)
    Environment:        <none>
    Mounts:             <none>
  Volumes:              <none>
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath    Type        Reason            Message
  ---------    --------    -----    ----            -------------    --------    ------            -------
  1m           1m          1        {job-controller }                Normal      SuccessfulCreate  Created pod: pi-dtn4q

要查看 Job 的已完成 pod,可以使用 kubectl get pods 命令。

要以机器可读的形式列出属于 Job 的所有 pod,可以使用以下命令:

pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}')
echo $pods
pi-aiw0a

这里,选择器与 Job 的选择器相同。--output=jsonpath 选项指定一个表达式来从 pod 返回列表中获取每个 pod 的名称。

查看其中一个 pod 的标准输出:

kubectl logs $pods

The output is similar to this:

3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901

编写 Job 规范

和其他 Kubernetes 配置一样,Job 需要 apiVersionkindmetadata 字段。

Job 也需要 .spec 部分

Pod 模板

.spec.template.spec 中唯一必填字段。

.spec.templatepod 模板。它具有与 pod 完全相同的架构,只是它是嵌套的并且没有 apiVersionkind

除了 pod 的必填字段,Job 中的 pod 模板还必须指定合适的标签(参考 pod 选择器)和适当的重启策略。

RestartPolicy 只能等于 NeverOnFailure

Pod 选择器

.spec.selector 字段是可选的。在大多数场景下不必指定它。请参阅指定自己的 pod 选择器

并行 Job

下面是适合作为 Job 运行的三种主要任务类型:

  1. 非并行 Job
    • 通常,除非 pod 发生故障,否则仅启动一个 pod。
    • 一旦 pod 成功终止,Job 即完成。
  2. 配置了*固定完成计数*的并行 Job:
    • .spec.completions 指定一个非零正数值。
    • Job 代表整体任务,并且在 1 到 .spec.completions 范围内都有一个 pod 成功完成时,Job 才算完成。
    • 尚未实现: 每个 pod 都会被传递一个从 1 到 .spec.completions 之间的索引值。
  3. 具有*工作队列*的并行 Job:
    • 不指定 .spec.completions 的话, 默认为 .spec.parallelism
    • Pod 必须在彼此之间或外部服务之间进行协调,来确定每个 pod 该处理什么任务。例如,一个 pod 可以从工作队列中获取一批任务(最多 N 个)。
    • 每个 pod 都可以独立确定其所有对端是否完成,从而确定整个 Job 完成。
    • 当 Job 中的任何 pod 成功结束后,不会再创建新的 pod。
    • 一旦至少一个 pod 成功终止并且所有的 pod 都终止了,则 Job 就成功完成了。
    • 一旦 任何 pod 成功退出,其他 pod 都不应该为此任务做任务工作或编写任何输出。它们都应该处在退出过程中。

对于非并行 Job,可以不设置 .spec.completions.spec.parallelism。两者均未设置时,默认值为 1。

对于一个固定完成计数 Job,应该将 .spec.completions 设置为所需的完成数量。.spec.parallelism 可以设置,也可以不设置,默认值为 1。

对于一个工作队列 Job,不要设置 .spec.completions,并且将 .spec.parallelism 设置一个非负整数。

有关如何使用不同类型 Job 的更多信息,请参考 Job 模式部分。

控制并行

请求的并发数(.spec.parallelism)可以设置为任何非负的整数。如果未指定,默认值为 1。如果指定为 0,则 Job 就被有效地暂停直到该配置值增加。

实际的并发数(在任何时刻运行着的 pod 数量)由于下面的一些原因,可能大于或小于请求的并发数:

  • 对于固定完成计数 Job,实际上并行运行的 pod 数量不会超过剩余的完成数。更高的 .spec.parallelism 值将被优先忽略。
  • 对于工作队列 Job,任何 pod 成功之后都不会启动新的 pod。然而,剩余的 pod 可以完成。
  • 如果控制器还没来得及反应。
  • 如果控制器由于任何原因(缺少 ResourceQuota,缺少权限等)未能创建 pod,则 pod 数量将少于请求数。
  • 控制器可能会因为同一 Job 中有过多之前失败的 pod,从而限制新的 pod 的创建。
  • 当 pod 优雅关闭时,需要花费一些时间才能停止。

处理 Pod 和容器失败

Pod 中的容器可能由于多种原因而失败,例如,由于容器中的进程以非零退出码退出,或者容器由于超过内存限制而被杀掉等。如果容器失败发生,并且 .spec.template.spec.restartPolicy = "OnFailure",则 pod 停留在节点上,但是容器会重新运行。因此,你的程序需要处理本地重启的情况,或者指定 .spec.template.spec.restartPolicy = "Never"。参阅 pod 生命周期获取更多关于 restartPolicy 的信息。

由于多种原因,整个 pod 也可能失败,比如,当 pod 从节点上被踢走(节点在升级,重启,被删除等),或者 pod 中的容器失败且设置了 .spec.template.spec.restartPolicy = "Never"。当 pod 失败后,Job 控制器将启动一个新的 pod。这意味着你的应用程序需要处理这种情况(应用程序在新的 pod 中重启)。特别是,它需要处理由先前运行引起的临时文件,锁,不完整输出等等。

请注意,即使你指定了 .spec.parallelism = 1.spec.completions = 1.spec.template.spec.restartPolicy = "Never",统一程序有时也会启动两次。

如果你指定 .spec.parallelism.spec.completions 配置都大于 1,则可能同时有多个 pod 运行。因此,你的 pod 必须容忍并发。

Pod 的退避失败策略

在某些情况下(配置中的逻辑错误等),你需要在重试一定次数后才使 Job 失败。为此需要设置 .spec.backoffLimit 配置值为判断 Job 失败前的重试次数。 退避限制默认设置为 6。与 Job 相关的 pod在失败后,经过一个指数级退避延迟(10 秒,20 秒,40 秒,上限为 6 分钟),由 Job 控制器重新创建。 如果在 Job 的下一次状态检查之前未出现新的失败 pod,则会重置退避计数。

注意:

1.12 之前的 Kubernetes 版本仍然存在问题 #54870

注意:

如果你的 Job 外配置了 restartPolicy = "OnFailure",请记住,一旦 Job 达到退避限制,运行在该 Job 的容器将被终止。这会使得调试 Job 的可执行将变得更加困难。我们建议在调试 Job 或使用日志系统时设置 restartPolicy = "Never",以确保失败 Job 的输出不会在不经意间丢失。

Job 终止和清理

当 Job 完成后,不会再创建 pod,但是 pod 也不会被删除。将这些 pod 保留,使你仍然可以查看已完成 pod 的日志来检查错误,警告或其他诊断输出。Job 对象在完成后也同样保留下来,以便你可以查看它的状态。由用户来决定在查看完状态后删除旧的 Job。使用 kubectl(例如 kubectl delete jobs/pikubectl delete -f ./job.yaml )来删除 Job。当你使用 kubectl 删除 Job 后,它所创建的所有 pod 也会被删除。

默认情况下,除非 pod 失败(restartPolicy=Never)或容器错误退出(restartPolicy=OnFailure),否则 Job 将不会被中断运行。Job 中断将遵循上面介绍的 .spec.backoffLimit。一旦 .spec.backoffLimit 达到,Job 将被标记为失败,企鹅别所有正运行的 pod 将被终止。

终止 Job 的另一种方式是设置有限期限。通过设置 Job 对象的 .spec.activeDeadlineSeconds 字段为秒数来达到。activeDeadlineSeconds 适用于 Job 存在期间,不管有多少 pod 被创建。一旦 Job 达到 activeDeadlineSeconds,所有运行中的 pod将被终止,并且 Job 的状态将变成 type: Failedreason: DeadlineExceeded

请注意,Job 的 .spec.activeDeadlineSeconds 配置优先于 .spec.backoffLimit 配置。因此,重试一个或多个失败 pod 的 Job 在达到 activeDeadlineSeconds 指定的时间限制前,不会部署其他的 pod,即使 backoffLimit 尚未达到。

例子:

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

请注意,Job 对象中的 Job 规范和 Pod 模板规范都有 activeDeadlineSeconds 字段。确保将此字段设置在适当的层级。

自动清理已完成的 Job

系统中已完成的 Job 通常不再需要。将它们保留在系统中将会给 API 服务器带来压力。如果 Job 由更高级别的控制器管理,比如 CronJobs,则 Job 可以由 CronJobs 基于指定的基于容量的清理策略来进行清除。

已完成 Job 的 TTL 机制

FEATURE STATE: Kubernetes v1.12 alpha
该功能目前处于 alpha 状态,意味着:

  • 版本名称包含 alpha(例如 v1alpha1)。
  • 可能存在问题,启用该功能可能会暴露 bug。默认情况下被禁用。
  • 对该功能的支持可能在任何时候被取消,而不另行通知。
  • API 可能会在以后的软件版本中以不兼容的方式被更改,而不另行通知。
  • 建议仅在短期测试集群中使用该功能,这是因为使用该功能会增加出现 bug 的风险,而且缺乏长期支持。

清理已完成 Job(CompleteFailed)的另一个方式是通过使用 TTL 控制器提供的 TTL 机制,TTL 通过指定 Job 的 .spec.ttlSecondsAfterFinished 字段来清理已完成资源。

当 TTL 控制器清理 Job 时,它将级联删除 Job,即删除它的依赖对象,比如 pod 将和 Job 一起被删除。请注意,当 Job 被删除后,它的生命周期保证,如终结者,将被触发。

例如:

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

pi-with-ttl Job 在完成后 100 秒就会自动删除掉。

如果该字段设为为 0,那么 Job 将在完成后立即被自动删除掉。如果该字段未设置,则 Job 在完成后将不会被 TTL 控制器清理掉。

请注意,此 TTL 机制处于 alpha 阶段,由 TTLAfterFinished 功能门控制是否启动。有关详细信息,请参阅 TTL 控制器中关于已完成资源的处理。

Job 模式

Job 对象可用于支持 pod 的可靠并行执行。Job 对象并非设计为支持紧密通信的并行过程(常见于科学计算领域)。它被用于支持一组独立但相关的*工作项*的并行处理。这些可能是要发送的电子邮件,要渲染的帧,要编码转换的文件,要扫描的 NoSQL 数据库中一段范围内的键等等。

在复杂的系统中,可能会有多个不同的工作项集。这里我们只考虑用户想要一起管理一组工作项 — *批处理任务*。

并行计算有几种不同的模式,每种都有各自的优缺点。权衡点如下:

  • 一个 Job 对象处理一个工作项,还是一个 Job 对象处理所有的工作项。后者适用于有大量工作项的场景。前者在管理大量 Job 对象时会对用户和系统带来一些开销。
  • 创建和工作项数量一样多的 pod,还是一个 pod 处理多个工作项。前者通常对现有代码和容器进行较少的修改。后者更适用于处理大量工作项(理由和前一条的相似)。
  • 几种方法使用工作队列。这需要运行一个队列服务,并对现有的程序和容器进行修改以使其能够使用工作队列。其他方法更容易适应现有的容器化应用程序。

权衡总结如下表,第 2 至 4 列对应上面的权衡点。模式名称也是样例和详细描述的链接。

模式 单个 Job 对象 比工作项更少的 pod? 使用未修改的 app? Kube 1.1 中生效?
Job 模板扩展
一个项对应一个 pod 的队列 有时
具有可变 pod 数量的队列
具有静态工作分配的单个 Job

当使用 .spec.completions 指定完成数时,由 Job 控制器创建的每个 pod 都有一个相同的 规范。这意味着一个任务的所有 pod 将有相同的命令行和镜像,相同的卷以及(几乎)相同的环境变量。这些模式通过不同的方式安排 pod 去处理不同的任务。

下表列出了每种模式下对 .spec.parallelism.spec.completions 需要做的设置。

模式 .spec.completions .spec.parallelism
Job 模板扩展 1 应该为 1
一个项对应一个 pod 的队列 W 任意
具有可变 pod 数量的队列 1 任意
具有静态工作分配的单个 Job W 任意

高级用法

指定你的 pod 选择器

通常,在创建 Job 对象时,不用指定 .spec.selector。系统的默认逻辑会在创建 Job 时添加这个字段。它会选择一个不会和其他 pod 重叠的选择器。

但是,在某些情况下,你可能需要去覆盖默认设置的选择器。为此,你需要指定 Job 的 .spec.selector

当设置时需要特别小心。如果你指定的标签选择器不是 Job 的 pod 所独有的,并且匹配到了不相关的 pod,那么不相关 Job 的 pod 可能会被删除,或此 Job 可能将其他 pod 统计为已完成,或者其中一个或两个 Job 可能拒绝创建 pod 或运行完成。如果一个选择了非唯一的选择器,则其他的控制器(比如 ReplicationController)和它的 pod 的行为也将是不可预测的。当指定了 .spec.selector,Kubernetes将不会阻止你产生错误。

下面的例子描述了你想要使用该功能的场景。

假设 old Job 已经在运行了。你想要运行中的 pod 保持运行,但是你想 Job 创建的其他 pod 使用不同的 pod 模板,并使 Job 具有一个新的名称。你不能更新 Job 对象,因为这些字段是不可更新的。因此,你需要删除 old 但是_让它的 pod 运行_,可以使用 kubectl delete jobs/old --cascade=false 命令。在删除之前,你需要记下它所使用的选择器:

kubectl get job old -o yaml
kind: Job
metadata:
  name: old
  ...
spec:
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

然后使用新名称 new 创建一个新的 Job ,并且显示指定相同的选择器。由于现有的 pod 具有 controller-uid=a8f3d00d-c6d2-11e5-9f87-42010af00002 标签,它们也受 new Job 的控制。

由于你不再使用系统通常为你自动生成的选择器,你需要在新的 Job 中指定 manualSelector: true

kind: Job
metadata:
  name: new
  ...
spec:
  manualSelector: true
  selector:
    matchLabels:
      controller-uid: a8f3d00d-c6d2-11e5-9f87-42010af00002
  ...

新的 Job 本身将具有一个与 a8f3d00d-c6d2-11e5-9f87-42010af00002 不通的 uid。设置 manualSelector: true 来告诉系统你知道自己在做什么,并使其允许这种不匹配。

备选方案

裸 pod

当 pod 所在节点重启或发生故障时,pod 将终止并且不会重启。然后,Job 将创建新的 pod 替换终止的 pod。因此,即使你的应用仅需要一个 pod,我们还是推荐使用 Job 而不是裸 pod。

副本控制器

Job 是副本控制器的补充。副本控制器管理预期不会终止的 pod(比如 web 服务器),而 Job 管理预期将会终止的 pod(比如批处理任务)。

正如在 Pod 生命周期中所讨论的,Job *仅*适用于具有 RestartPolicy 值为 OnFailureNever 的 pod。(注意:如果 RestartPolicy 未指定,默认值为 Always。)

单 Job 启动控制 pod

单个 Job 的另一种模式是创建一个创建其他 pod 的 pod,充当这些 pod 的一种自定义控制器。这提供了更大的灵活性,但是入门起来可能有点复杂,并且与 Kubernetes 的集成较少。

这种模式的一个样例是:一个 Job 启动一个 pod 来运行一个脚本,该脚本依次启动 Spark 主控制器(参阅 spark 例子),并运行 spark 驱动,然后进行清理。

这种方法的优点是整个过程可以获取 Job 对象的完成保证,不是完全控制 pod 的创建以及如何将工作分配给 pod。

Cron Jobs

你可以使用 CronJob 来创建一个会在指定的时间/日期执行的 Job,类似于 Unix 工具 cron

反馈