--- title: 使用扩展进行并行处理 content_template: templates/concept weight: 20 --- {{% capture overview %}} 在这个示例中,我们将运行从一个公共模板创建的多个 Kubernetes 作业。您可能希望熟悉 [Jobs](/docs/concepts/workloads/controllers/jobs-run-to-completion/) 的基本、非并行使用。 {{% /capture %}} {{% capture body %}} ## 基本模板扩展 首先,将以下作业模板下载到名为 `job-tmpl.yaml` 的文件中 {{< codenew file="application/job/job-tmpl.yaml" >}} 与 *pod 模板*不同,我们的 *job 模板*不是 Kubernetes API 类型。它只是作业对象的 yaml 表示, YAML 文件有一些占位符,在使用它之前需要填充这些占位符。`$ITEM` 语法对 Kubernetes 没有意义。 在这个例子中,容器所做的唯一处理是 `echo` 一个字符串并休眠一段时间。 在真实的用例中,处理将是一些重要的计算,例如呈现电影的帧,或者处理数据库中的一系列行。例如,`$ITEM` 参数将指定帧号或行范围。 这个作业及其 Pod 模板有一个标签: `jobgroup=jobexample`。这个标签在系统中没有什么特别之处。 这个标签使得我们可以方便地同时操作组中的所有作业。 我们还将相同的标签放在 pod 模板上,这样我们就可以用一个命令检查这些作业的所有 pod。 创建作业之后,系统将添加更多的标签来区分一个作业的 pod 和另一个作业的 pod。 注意,标签键 `jobgroup` 对 Kubernetes 并无特殊含义。您可以选择自己的标签方案。 下一步,将模板展开到多个文件中,每个文件对应要处理的项。 ```shell # Expand files into a temporary directory $ mkdir ./jobs $ for i in apple banana cherry do cat job-tmpl.yaml | sed "s/\$ITEM/$i/" > ./jobs/job-$i.yaml done ``` 检查是否工作正常: ```shell $ ls jobs/ job-apple.yaml job-banana.yaml job-cherry.yaml ``` 在这里,我们使用 `sed` 将字符串 `$ITEM` 替换为循环变量。 您可以使用任何类型的模板语言(jinja2, erb) 或编写程序来生成作业对象。 接下来,使用 kubectl 命令创建所有作业: ```shell $ kubectl create -f ./jobs job "process-item-apple" created job "process-item-banana" created job "process-item-cherry" created ``` 现在,检查这些作业: ```shell $ kubectl get jobs -l jobgroup=jobexample NAME DESIRED SUCCESSFUL AGE process-item-apple 1 1 31s process-item-banana 1 1 31s process-item-cherry 1 1 31s ``` 在这里,我们使用 `-l` 选项选择属于这组作业的所有作业。(系统中可能还有其他不相关的工作,我们不想看到。) 我们可以检查 pod 以及使用同样地标签选择器: ```shell $ 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 ``` 没有一个命令可以一次检查所有作业的输出,但是循环遍历所有 pod 非常简单: ```shell $ for p in $(kubectl get pods -l jobgroup=jobexample -o name) do kubectl logs $p done Processing item apple Processing item banana Processing item cherry ``` ## 多个模板参数 在第一个示例中,模板的每个实例都有一个参数,该参数也用作标签。 但是标签的键名在[可包含的字符](/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set)方面有一定的约束。 这个稍微复杂一点的示例使用 jinja2 板语言来生成我们的对象。 我们将使用一行 python 脚本将模板转换为文件。 首先,粘贴作业对象的以下模板到一个名为 `job.yaml.jinja2` 的文件中: ```liquid {%- set params = [{ "name": "apple", "url": "http://www.orangepippin.com/apples", }, { "name": "banana", "url": "https://en.wikipedia.org/wiki/Banana", }, { "name": "raspberry", "url": "https://www.raspberrypi.org/" }] %} {%- 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 command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"] restartPolicy: Never --- {%- endfor %} ``` 上面的模板使用 python dicts 列表(第1-4行)定义每个作业对象的参数。 然后 for 循环为每组参数(剩余行)生成一个作业 yaml 对象。 我们利用了多个 yaml 文档可以与 `---` 分隔符连接的事实(倒数第二行)。 我们可以将输出直接传递给 kubectl 来创建对象。 如果您还没有 jinja2 包则需要安装它: `pip install --user jinja2`。 现在,使用这个一行 python 程序来展开模板: ```shell alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"' ``` 输出可以保存到一个文件,像这样: ```shell cat job.yaml.jinja2 | render_template > jobs.yaml ``` 或直接发送到 kubectl,如下所示: ```shell cat job.yaml.jinja2 | render_template | kubectl create -f - ``` ## 替代方案 如果您有大量作业对象,您可能会发现: - 即使使用标签,管理这么多作业对象也很麻烦。 - 在一次创建所有作业时,您超过了资源配额,可是您也不希望以递增方式创建作业并等待其完成。 - 同时创建的大量作业会使 Kubernetes apiserver、控制器或调度程序过载。 在这种情况下,您可以考虑 其他[工作模式](/docs/concepts/jobs/run-to-completion-finite-workloads/#job-patterns)。 {{% /capture %}}