带静态工作分配的索引式并行处理作业

特性状态: Kubernetes v1.24 [stable]

在此示例中,你将运行一个使用多个并行工作进程的 Kubernetes Job。每个工作进程是运行在自己 Pod 中的不同容器。Pod 具有控制平面自动设置的*索引号*,这使得每个 Pod 可以识别整个任务的哪个部分来工作。

Pod 索引可在注解 batch.kubernetes.io/job-completion-index 中获取,其格式为表示十进制值的字符串。为了让容器化的任务进程获取此索引,你可以使用Downward API 机制发布注解的值。为方便起见,控制平面自动设置 Downward API,以在环境变量 JOB_COMPLETION_INDEX 中暴露该索引。

下面是此示例的步骤概述

  1. 使用索引式完成定义 Job 清单。Downward API 允许你将 Pod 索引注解作为环境变量或文件传递给容器。
  2. 基于该清单启动一个 Indexed Job.

开始之前

你应该已经熟悉 Job 的基本非并行用法。

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具已配置为与你的集群通信。建议在至少有两个不是控制平面主机的节点上运行此教程。如果你还没有集群,可以使用 minikube 创建一个,或者你可以使用以下 Kubernetes 练手环境之一:

你的 Kubernetes 服务器版本必须是 v1.21 或更高。

要检查版本,请输入 kubectl version

选择一种方法

要从工作程序访问工作项,你有以下几种选择:

  1. 读取 JOB_COMPLETION_INDEX 环境变量。Job 控制器自动将此变量链接到包含完成索引的注解。
  2. 读取包含完成索引的文件。
  3. 假设你不能修改程序,你可以用一个脚本来封装它,该脚本使用上述任何方法读取索引,并将其转换为程序可以作为输入使用的内容。

对于此示例,假设你选择了选项 3,并且想要运行 rev 工具。这个程序接受一个文件作为参数,并以相反的顺序打印其内容。

rev data.txt

你将使用来自 busybox 容器镜像的 rev 工具。

由于这只是一个示例,每个 Pod 只做一小部分工作(反转一个短字符串)。在实际工作负载中,你可能例如创建一个 Job,表示根据场景数据生成 60 秒视频的任务。视频渲染 Job 中的每个工作项将是渲染该视频片段的特定帧。索引式完成意味着 Job 中的每个 Pod 都知道要渲染和发布的帧,通过从片段的开头计算帧数来实现。

定义索引式 Job

下面是使用 Indexed 完成模式的示例 Job 清单

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      initContainers:
      - name: 'input'
        image: 'docker.io/library/bash'
        command:
        - "bash"
        - "-c"
        - |
          items=(foo bar baz qux xyz)
          echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt          
        volumeMounts:
        - mountPath: /input
          name: input
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        emptyDir: {}

在上面的示例中,你使用了 Job 控制器为所有容器设置的内置 JOB_COMPLETION_INDEX 环境变量。一个 初始化容器 将索引映射到静态值,并将其写入一个文件,该文件通过一个 emptyDir 卷 与运行工作进程的容器共享。或者,你可以 通过 Downward API 定义自己的环境变量 来向容器发布索引。你还可以选择从 ConfigMap 中加载值列表作为环境变量或文件

另外,你可以直接使用 Downward API 将注解值作为卷文件传递,如下例所示

apiVersion: batch/v1
kind: Job
metadata:
  name: 'indexed-job'
spec:
  completions: 5
  parallelism: 3
  completionMode: Indexed
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: 'worker'
        image: 'docker.io/library/busybox'
        command:
        - "rev"
        - "/input/data.txt"
        volumeMounts:
        - mountPath: /input
          name: input
      volumes:
      - name: input
        downwardAPI:
          items:
          - path: "data.txt"
            fieldRef:
              fieldPath: metadata.annotations['batch.kubernetes.io/job-completion-index']

运行 Job

现在运行 Job

# This uses the first approach (relying on $JOB_COMPLETION_INDEX)
kubectl apply -f https://kubernetes.ac.cn/examples/application/job/indexed-job.yaml

创建此 Job 后,控制平面会创建一系列 Pod,每个指定的索引对应一个。.spec.parallelism 的值决定了可以同时运行多少个,而 .spec.completions 决定了 Job 总共创建多少个 Pod。

由于 .spec.parallelism 小于 .spec.completions,控制平面会在一些首批 Pod 完成后才启动更多 Pod。

你可以等待 Job 成功,设置一个超时时间

# The check for condition name is case insensitive
kubectl wait --for=condition=complete --timeout=300s job/indexed-job

现在,描述 Job 并检查它是否成功。

kubectl describe jobs/indexed-job

输出类似于

Name:              indexed-job
Namespace:         default
Selector:          controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
Labels:            controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
                   job-name=indexed-job
Annotations:       <none>
Parallelism:       3
Completions:       5
Start Time:        Thu, 11 Mar 2021 15:47:34 +0000
Pods Statuses:     2 Running / 3 Succeeded / 0 Failed
Completed Indexes: 0-2
Pod Template:
  Labels:  controller-uid=bf865e04-0b67-483b-9a90-74cfc4c3e756
           job-name=indexed-job
  Init Containers:
   input:
    Image:      docker.io/library/bash
    Port:       <none>
    Host Port:  <none>
    Command:
      bash
      -c
      items=(foo bar baz qux xyz)
      echo ${items[$JOB_COMPLETION_INDEX]} > /input/data.txt

    Environment:  <none>
    Mounts:
      /input from input (rw)
  Containers:
   worker:
    Image:      docker.io/library/busybox
    Port:       <none>
    Host Port:  <none>
    Command:
      rev
      /input/data.txt
    Environment:  <none>
    Mounts:
      /input from input (rw)
  Volumes:
   input:
    Type:       EmptyDir (a temporary directory that shares a pod's lifetime)
    Medium:
    SizeLimit:  <unset>
Events:
  Type    Reason            Age   From            Message
  ----    ------            ----  ----            -------
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-njkjj
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-9kd4h
  Normal  SuccessfulCreate  4s    job-controller  Created pod: indexed-job-qjwsz
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-fdhq5
  Normal  SuccessfulCreate  1s    job-controller  Created pod: indexed-job-ncslj

在这个示例中,你为每个索引运行 Job 并使用自定义值。你可以检查其中一个 Pod 的输出

kubectl logs indexed-job-fdhq5 # Change this to match the name of a Pod from that Job

输出类似于

xuq
最后修改于 2023 年 8 月 24 日 下午 6:38 PST:使用 code_sample shortcode 代替 code shortcode (e8b136c3b3)