Information in this document may be out of date

This document has an older update date than the original, so the information it contains may be out of date. If you're able to read English, see the English version for the most up-to-date information: Parallel Processing using Expansions

使用展开的方式进行并行处理

本任务展示基于一个公共的模板运行多个Jobs。 你可以用这种方法来并行执行批处理任务。

在本任务示例中,只有三个工作条目:applebananacherry。 示例任务处理每个条目时打印一个字符串之后结束。

参考在真实负载中使用 Job了解更适用于真实使用场景的模式。

准备开始

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

你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

任务中的基本模板示例要求安装命令行工具 sed。 要使用较高级的模板示例,你需要安装 Python, 并且要安装 Jinja2 模板库。

一旦 Python 已经安装好,你可以运行下面的命令安装 Jinja2:

pip install --user jinja2

基于模板创建 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
 # 使用 curl 下载 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 的临时目录。

# 展开模板文件到多个文件中,每个文件对应一个要处理的条目
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)。

你可以使用相同的 标签选择算符 来过滤 Pods:

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

清理

# 删除所创建的 Job
# 集群会自动清理 Job 对应的 Pod
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 行)定义每个作业对象的参数。 然后使用 for 循环为每组参数(剩余行)生成一个作业 yaml 对象。 我们利用了多个 YAML 文档(这里的 Kubernetes 清单)可以用 --- 分隔符连接的事实。 我们可以将输出直接传递给 kubectl 来创建对象。

接下来我们用单行的 Python 程序将模板展开。

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

使用 render_template 将参数和模板转换成一个 YAML 文件,其中包含 Kubernetes 资源清单:

# 此命令需要之前定义的别名
cat job.yaml.jinja2 | render_template > jobs.yaml

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

当你对输出结果比较满意时,可以用管道将其输出发送给 kubectl,如下所示:

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

Kubernetes 接收清单文件并执行你所创建的 Job。

清理

# 删除所创建的 Job
# 集群会自动清理 Job 对应的 Pod
kubectl delete job -l jobgroup=jobexample

在真实负载中使用 Job

在真实的负载中,每个 Job 都会执行一些重要的计算,例如渲染电影的一帧, 或者处理数据库中的若干行。这时,$ITEM 参数将指定帧号或行范围。

在此任务中,你运行一个命令通过取回 Pod 的日志来收集其输出。 在真实应用场景中,Job 的每个 Pod 都会在结束之前将其输出写入到某持久性存储中。 你可以为每个 Job 指定 PersistentVolume 卷,或者使用其他外部存储服务。 例如,如果你在渲染视频帧,你可能会使用 HTTP 协议将渲染完的帧数据 用 'PUT' 请求发送到某 URL,每个帧使用不同的 URl。

Job 和 Pod 上的标签

你创建了 Job 之后,Kubernetes 自动为 Job 的 Pod 添加 标签,以便能够将一个 Job 的 Pod 与另一个 Job 的 Pod 区分开来。

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

Kubernetes 自身对标签名 jobgroup 没有什么要求。 为创建自同一模板的所有 Job 使用同一标签使得我们可以方便地同时操作组中的所有作业。 在第一个例子中,你使用模板来创建了若干 Job。 模板确保每个 Pod 都能够获得相同的标签,这样你可以用一条命令检查这些模板化 Job 所生成的全部 Pod。

替代方案

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

  • 即使使用标签,管理这么多 Job 对象也很麻烦。
  • 如果你一次性创建很多 Job,很可能会给 Kubernetes 控制面带来很大压力。 一种替代方案是,Kubernetes API 可能对请求施加速率限制,通过 429 返回 状态值临时拒绝你的请求。
  • 你可能会受到 Job 相关的资源配额 限制:如果你在一个批量请求中触发了太多的任务,API 服务器会永久性地拒绝你的某些请求。

还有一些其他作业模式 可供选择,这些模式都能用来处理大量任务而又不会创建过多的 Job 对象。

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