--- title: Job 从运行到完成 content_template: templates/concept feature: title: Batch execution description: > In addition to services, Kubernetes can manage your batch and CI workloads, replacing containers that fail, if desired. weight: 70 --- {{% capture overview %}} Job 创建一个或多个 pod,并且确保指定数量的 pod 成功终止。Pod 成功完成后,Job 将跟踪成功完成的情况。当达到指定的成功完成次数时,任务(即 Job)就完成了。删除 Job 将清除其创建的 pod。 一种简单的情况时创建一个 Job 对象,以便可靠地运行一个 Pod 来完成。如果第一个 pod 失败了或被删除了(例如,由于节点硬件故障或节点重启),则 Job 对象将启动一个新的 pod。 也可以使用 Job 去并行运行多个 pod。 {{% /capture %}} {{% capture body %}} ## 运行一个 Job 示例 下面是一个 Job 配置样例。它计算 π 到2000位并将其打印出来。大约需要 10 秒钟才能完成。 {{< codenew file="controllers/job.yaml" >}} 可以使用以下命令运行示例: ```shell kubectl apply -f https://k8s.io/examples/controllers/job.yaml ``` ``` job "pi" created ``` 使用 `kubectl` 命令检查 Job 的状态: ```shell 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: 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: Mounts: Volumes: 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,可以使用以下命令: ```shell pods=$(kubectl get pods --selector=job-name=pi --output=jsonpath='{.items[*].metadata.name}') echo $pods ``` ``` pi-aiw0a ``` 这里,选择器与 Job 的选择器相同。`--output=jsonpath` 选项指定一个表达式来从 pod 返回列表中获取每个 pod 的名称。 查看其中一个 pod 的标准输出: ```shell kubectl logs $pods ``` The output is similar to this: ```shell 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727938000816470600161452491921732172147723501414419735685481613611573525521334757418494684385233239073941433345477624168625189835694855620992192221842725502542568876717904946016534668049886272327917860857843838279679766814541009538837863609506800642251252051173929848960841284886269456042419652850222106611863067442786220391949450471237137869609563643719172874677646575739624138908658326459958133904780275901 ``` ## 编写 Job 规范 和其他 Kubernetes 配置一样,Job 需要 `apiVersion`,`kind` 和 `metadata` 字段。 Job 也需要 [`.spec` 部分](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status)。 ### Pod 模板 `.spec.template` 是 `.spec` 中唯一必填字段。 `.spec.template` 是 [pod 模板](/docs/concepts/workloads/pods/pod-overview/#pod-templates)。它具有与 [pod](/docs/user-guide/pods) 完全相同的架构,只是它是嵌套的并且没有 `apiVersion` 或 `kind`。 除了 pod 的必填字段,Job 中的 pod 模板还必须指定合适的标签(参考 [pod 选择器](#pod-selector))和适当的重启策略。 [`RestartPolicy`](/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy) 只能等于 `Never` 或 `OnFailure`。 ### Pod 选择器 `.spec.selector` 字段是可选的。在大多数场景下不必指定它。请参阅[指定自己的 pod 选择器](#specifying-your-own-pod-selector)。 ### 并行 Job 下面是适合作为 Job 运行的三种主要任务类型: 1. 非并行 Job - 通常,除非 pod 发生故障,否则仅启动一个 pod。 - 一旦 pod 成功终止,Job 即完成。 1. 配置了*固定完成计数*的并行 Job: - 为 `.spec.completions` 指定一个非零正数值。 - Job 代表整体任务,并且在 1 到 `.spec.completions` 范围内都有一个 pod 成功完成时,Job 才算完成。 - **尚未实现:** 每个 pod 都会被传递一个从 1 到 `.spec.completions` 之间的索引值。 1. 具有*工作队列*的并行 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 模式](#job-patterns)部分。 #### 控制并行 请求的并发数(`.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 生命周期](/docs/concepts/workloads/pods/pod-lifecycle/#example-states)获取更多关于 `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,则会重置退避计数。 {{< note >}} 1.12 之前的 Kubernetes 版本仍然存在问题 [#54870](https://github.com/kubernetes/kubernetes/issues/54870) {{< /note >}} {{< note >}} 如果你的 Job 外配置了 `restartPolicy = "OnFailure"`,请记住,一旦 Job 达到退避限制,运行在该 Job 的容器将被终止。这会使得调试 Job 的可执行将变得更加困难。我们建议在调试 Job 或使用日志系统时设置 `restartPolicy = "Never"`,以确保失败 Job 的输出不会在不经意间丢失。 {{< /note >}} ## Job 终止和清理 当 Job 完成后,不会再创建 pod,但是 pod 也不会被删除。将这些 pod 保留,使你仍然可以查看已完成 pod 的日志来检查错误,警告或其他诊断输出。Job 对象在完成后也同样保留下来,以便你可以查看它的状态。由用户来决定在查看完状态后删除旧的 Job。使用 `kubectl`(例如 `kubectl delete jobs/pi` 或 `kubectl 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: Failed` 与 `reason: DeadlineExceeded`。 请注意,Job 的 `.spec.activeDeadlineSeconds` 配置优先于 `.spec.backoffLimit` 配置。因此,重试一个或多个失败 pod 的 Job 在达到 `activeDeadlineSeconds` 指定的时间限制前,不会部署其他的 pod,即使 `backoffLimit` 尚未达到。 例子: ```yaml 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 模板规范](/docs/concepts/workloads/pods/init-containers/#detailed-behavior)都有 `activeDeadlineSeconds` 字段。确保将此字段设置在适当的层级。 ## 自动清理已完成的 Job 系统中已完成的 Job 通常不再需要。将它们保留在系统中将会给 API 服务器带来压力。如果 Job 由更高级别的控制器管理,比如 [CronJobs](/docs/concepts/workloads/controllers/cron-jobs/),则 Job 可以由 CronJobs 基于指定的基于容量的清理策略来进行清除。 ### 已完成 Job 的 TTL 机制 {{< feature-state for_k8s_version="v1.12" state="alpha" >}} 清理已完成 Job(`Complete` 或 `Failed`)的另一个方式是通过使用 [TTL 控制器](/docs/concepts/workloads/controllers/ttlafterfinished/)提供的 TTL 机制,TTL 通过指定 Job 的 `.spec.ttlSecondsAfterFinished` 字段来清理已完成资源。 当 TTL 控制器清理 Job 时,它将级联删除 Job,即删除它的依赖对象,比如 pod 将和 Job 一起被删除。请注意,当 Job 被删除后,它的生命周期保证,如终结者,将被触发。 例如: ```yaml 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 控制器](/docs/concepts/workloads/controllers/ttlafterfinished/)中关于已完成资源的处理。 ## Job 模式 Job 对象可用于支持 pod 的可靠并行执行。Job 对象并非设计为支持紧密通信的并行过程(常见于科学计算领域)。它被用于支持一组独立但相关的*工作项*的并行处理。这些可能是要发送的电子邮件,要渲染的帧,要编码转换的文件,要扫描的 NoSQL 数据库中一段范围内的键等等。 在复杂的系统中,可能会有多个不同的工作项集。这里我们只考虑用户想要一起管理一组工作项 — *批处理任务*。 并行计算有几种不同的模式,每种都有各自的优缺点。权衡点如下: - 一个 Job 对象处理一个工作项,还是一个 Job 对象处理所有的工作项。后者适用于有大量工作项的场景。前者在管理大量 Job 对象时会对用户和系统带来一些开销。 - 创建和工作项数量一样多的 pod,还是一个 pod 处理多个工作项。前者通常对现有代码和容器进行较少的修改。后者更适用于处理大量工作项(理由和前一条的相似)。 - 几种方法使用工作队列。这需要运行一个队列服务,并对现有的程序和容器进行修改以使其能够使用工作队列。其他方法更容易适应现有的容器化应用程序。 权衡总结如下表,第 2 至 4 列对应上面的权衡点。模式名称也是样例和详细描述的链接。 | 模式 | 单个 Job 对象 | 比工作项更少的 pod? | 使用未修改的 app? | Kube 1.1 中生效? | | -------------------------------------------------------------------- |:-----------------:|:---------------------------:|:-------------------:|:-------------------:| | [Job 模板扩展](/docs/tasks/job/parallel-processing-expansion/) | | | ✓ | ✓ | | [一个项对应一个 pod 的队列](/docs/tasks/job/coarse-parallel-processing-work-queue/) | ✓ | | 有时 | ✓ | | [具有可变 pod 数量的队列](/docs/tasks/job/fine-parallel-processing-work-queue/) | ✓ | ✓ | | ✓ | | 具有静态工作分配的单个 Job | ✓ | | ✓ | | 当使用 `.spec.completions` 指定完成数时,由 Job 控制器创建的每个 pod 都有一个相同的 [`规范`](https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status)。这意味着一个任务的所有 pod 将有相同的命令行和镜像,相同的卷以及(几乎)相同的环境变量。这些模式通过不同的方式安排 pod 去处理不同的任务。 下表列出了每种模式下对 `.spec.parallelism` 和 `.spec.completions` 需要做的设置。 | 模式 | `.spec.completions` | `.spec.parallelism` | | -------------------------------------------------------------------- |:-------------------:|:--------------------:| | [Job 模板扩展](/docs/tasks/job/parallel-processing-expansion/) | 1 | 应该为 1 | | [一个项对应一个 pod 的队列](/docs/tasks/job/coarse-parallel-processing-work-queue/) | W | 任意 | | [具有可变 pod 数量的队列](/docs/tasks/job/fine-parallel-processing-work-queue/) | 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 是[副本控制器](/docs/user-guide/replication-controller)的补充。副本控制器管理预期不会终止的 pod(比如 web 服务器),而 Job 管理预期将会终止的 pod(比如批处理任务)。 正如在 [Pod 生命周期](/docs/concepts/workloads/pods/pod-lifecycle/)中所讨论的,`Job` *仅*适用于具有 `RestartPolicy` 值为 `OnFailure` 或 `Never` 的 pod。(注意:如果 `RestartPolicy` 未指定,默认值为 `Always`。) ### 单 Job 启动控制 pod 单个 Job 的另一种模式是创建一个创建其他 pod 的 pod,充当这些 pod 的一种自定义控制器。这提供了更大的灵活性,但是入门起来可能有点复杂,并且与 Kubernetes 的集成较少。 这种模式的一个样例是:一个 Job 启动一个 pod 来运行一个脚本,该脚本依次启动 Spark 主控制器(参阅 [spark 例子](https://github.com/kubernetes/examples/tree/{{< param "githubbranch" >}}/staging/spark/README.md)),并运行 spark 驱动,然后进行清理。 这种方法的优点是整个过程可以获取 Job 对象的完成保证,不是完全控制 pod 的创建以及如何将工作分配给 pod。 ## Cron Jobs {#cron-jobs} 你可以使用 [`CronJob`](/docs/concepts/workloads/controllers/cron-jobs/) 来创建一个会在指定的时间/日期执行的 Job,类似于 Unix 工具 `cron`。 {{% /capture %}}