使用扩展进行并行处理

本任务演示如何基于通用模板运行多个Job。你可以使用此方法并行处理批处理工作。

本示例中只有三个项目:_apple_、_banana_ 和 _cherry_。示例 Job 通过打印字符串然后暂停来处理每个项目。

请参阅在实际工作负载中使用 Job,了解此模式如何适用于更真实的用例。

准备工作

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

你需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与你的集群通信。建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。如果你还没有集群,可以使用 minikube 创建一个集群,或者你可以使用这些 Kubernetes 演练场之一

对于基本模板,你需要命令行工具 sed

要遵循高级模板示例,你需要安装可工作的 Python 以及 Python 的 Jinja2 模板库。

设置好 Python 后,可以通过运行以下命令安装 Jinja2:

pip install --user jinja2

基于模板创建 Job

首先,将以下 Job 模板下载到名为 job-tmpl.yaml 的文件中。这是你将下载的内容:

apiVersion: batch/v1
kind: Job
metadata:
  name: process-item-$ITEM
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox:1.28
        command: ["sh", "-c", "echo Processing item $ITEM && sleep 5"]
      restartPolicy: Never
# Use curl to download job-tmpl.yaml
curl -L -s -O https://k8s.io/examples/application/job/job-tmpl.yaml

你下载的文件还不是有效的 Kubernetes 清单。相反,该模板是 Job 对象的 YAML 表示,其中包含一些需要在使用前填写的占位符。$ITEM 语法对 Kubernetes 没有意义。

从模板创建清单

以下 shell 代码段使用 sed 将字符串 $ITEM 替换为循环变量,写入名为 jobs 的临时目录。立即运行此命令:

# Expand the template into multiple files, one for each item to be processed.
mkdir ./jobs
for i in apple banana cherry
do
  cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml
done

检查是否有效

ls jobs/

输出类似于:

job-apple.yaml
job-banana.yaml
job-cherry.yaml

你可以使用任何类型的模板语言(例如:Jinja2;ERB),或者编写程序来生成 Job 清单。

从清单创建 Job

接下来,使用一个 kubectl 命令创建所有 Job:

kubectl create -f ./jobs

输出类似于:

job.batch/process-item-apple created
job.batch/process-item-banana created
job.batch/process-item-cherry created

现在,检查 Job:

kubectl get jobs -l jobgroup=jobexample

输出类似于:

NAME                  COMPLETIONS   DURATION   AGE
process-item-apple    1/1           14s        22s
process-item-banana   1/1           12s        21s
process-item-cherry   1/1           12s        20s

使用 kubectl 的 -l 选项仅选择属于此 Job 组的 Job(系统中可能存在其他不相关的 Job)。

你也可以使用相同的标签选择器检查 Pod:

kubectl get pods -l jobgroup=jobexample

输出类似于:

NAME                        READY     STATUS      RESTARTS   AGE
process-item-apple-kixwv    0/1       Completed   0          4m
process-item-banana-wrsf7   0/1       Completed   0          4m
process-item-cherry-dnfu9   0/1       Completed   0          4m

我们可以使用此单个命令一次性检查所有 Job 的输出:

kubectl logs -f -l jobgroup=jobexample

输出应该是

Processing item apple
Processing item banana
Processing item cherry

清理

# Remove the Jobs you created
# Your cluster automatically cleans up their Pods
kubectl delete job -l jobgroup=jobexample

使用高级模板参数

第一个示例中,模板的每个实例都有一个参数,该参数也用于 Job 的名称。但是,名称被限制为只包含某些字符。

这个稍微复杂一些的示例使用 Jinja 模板语言从清单生成清单,然后从这些清单生成对象,每个 Job 具有多个参数。

对于本任务的这一部分,你将使用一个单行 Python 脚本将模板转换为一组清单。

首先,将以下 Job 对象的模板复制并粘贴到名为 job.yaml.jinja2 的文件中:

{% set params = [{ "name": "apple", "url": "http://dbpedia.org/resource/Apple", },
                  { "name": "banana", "url": "http://dbpedia.org/resource/Banana", },
                  { "name": "cherry", "url": "http://dbpedia.org/resource/Cherry" }]
%}
{% for p in params %}
{% set name = p["name"] %}
{% set url = p["url"] %}
---
apiVersion: batch/v1
kind: Job
metadata:
  name: jobexample-{{ name }}
  labels:
    jobgroup: jobexample
spec:
  template:
    metadata:
      name: jobexample
      labels:
        jobgroup: jobexample
    spec:
      containers:
      - name: c
        image: busybox:1.28
        command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"]
      restartPolicy: Never
{% endfor %}

上述模板使用 Python 字典列表(第 1-4 行)为每个 Job 对象定义了两个参数。一个 for 循环为每组参数发出一个 Job 清单(其余行)。

此示例依赖于 YAML 的一个特性。一个 YAML 文件可以包含多个文档(在本例中为 Kubernetes 清单),由单独一行上的 --- 分隔。你可以将输出直接通过管道传输到 kubectl 以创建 Job。

接下来,使用这个单行 Python 程序扩展模板:

alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"'

使用 render_template 将参数和模板转换为包含 Kubernetes 清单的单个 YAML 文件:

# This requires the alias you defined earlier
cat job.yaml.jinja2 | render_template > jobs.yaml

你可以查看 jobs.yaml 以验证 render_template 脚本是否正确工作。

一旦你确定 render_template 按照你的意图工作,你就可以将其输出通过管道传输到 kubectl

cat job.yaml.jinja2 | render_template | kubectl apply -f -

Kubernetes 接受并运行你创建的 Job。

清理

# Remove the Jobs you created
# Your cluster automatically cleans up their Pods
kubectl delete job -l jobgroup=jobexample

在实际工作负载中使用 Job

在实际用例中,每个 Job 都执行一些重要的计算,例如渲染电影的帧,或处理数据库中的一系列行。如果你正在渲染电影,你可以将 $ITEM 设置为帧号。如果你正在处理数据库表中的行,你可以将 $ITEM 设置为表示要处理的数据库行范围。

在本任务中,你通过获取 Pod 的日志来收集 Pod 的输出。在实际用例中,Job 的每个 Pod 在完成之前将其输出写入持久存储。你可以为每个 Job 使用 PersistentVolume,或者使用外部存储服务。例如,如果你正在渲染电影帧,可以使用 HTTP 将渲染的帧数据 PUT 到 URL,每个帧使用不同的 URL。

Job 和 Pod 上的标签

创建 Job 后,Kubernetes 会自动添加额外的标签,以区分一个 Job 的 Pod 和另一个 Job 的 Pod。

在此示例中,每个 Job 及其 Pod 模板都有一个标签:jobgroup=jobexample

Kubernetes 本身不关心名为 jobgroup 的标签。为从模板创建的所有 Job 设置标签,可以方便地一次性操作所有这些 Job。在第一个示例中,你使用模板创建了几个 Job。该模板确保每个 Pod 也获得相同的标签,因此你可以使用一个命令检查这些模板化 Job 的所有 Pod。

替代方案

如果你计划创建大量的 Job 对象,你可能会发现:

  • 即使使用标签,管理如此多的 Job 也很麻烦。
  • 如果你在批处理中创建许多 Job,你可能会给 Kubernetes 控制平面带来高负载。或者,Kubernetes API 服务器可能会对你进行速率限制,暂时拒绝你的请求,状态为 429。
  • 你受到 Job 的资源配额限制:当你在一个批处理中创建大量工作时,API 服务器会永久拒绝你的一些请求。

还有其他Job 模式,你可以使用它们来处理大量工作,而无需创建非常多的 Job 对象。

你还可以考虑编写自己的控制器来自动管理 Job 对象。

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