From 59cac839d45088243313681afb5f91d8262ba16e Mon Sep 17 00:00:00 2001 From: Qiming Teng Date: Sun, 16 Aug 2020 19:18:33 +0800 Subject: [PATCH] [zh] Rework parallel processing expansion The English content was COMPLETELY REWRITTEN for DAMN REASONS. --- .../job/parallel-processing-expansion.md | 815 ++++++++++-------- 1 file changed, 474 insertions(+), 341 deletions(-) diff --git a/content/zh/docs/tasks/job/parallel-processing-expansion.md b/content/zh/docs/tasks/job/parallel-processing-expansion.md index b010ae6848..b4b4cc255b 100644 --- a/content/zh/docs/tasks/job/parallel-processing-expansion.md +++ b/content/zh/docs/tasks/job/parallel-processing-expansion.md @@ -1,341 +1,474 @@ ---- -title: 使用扩展进行并行处理 -content_type: concept -min-kubernetes-server-version: v1.8 -weight: 20 ---- - - - - - - -在这个示例中,我们将运行从一个公共模板创建的多个 Kubernetes Job。您可能需要先熟悉 [Jobs](/docs/concepts/workloads/controllers/jobs-run-to-completion/) 的基本概念、非并行以及如何使用它。 - - - - - - - - -## 基本模板扩展 - - -首先,将以下作业模板下载到名为 `job-tmpl.yaml` 的文件中。 - -{{< codenew file="application/job/job-tmpl.yaml" >}} - - -与 *pod 模板*不同,我们的 *job 模板*不是 Kubernetes API 类型。它只是 Job 对象的 yaml 表示, -YAML 文件有一些占位符,在使用它之前需要填充这些占位符。`$ITEM` 语法对 Kubernetes 没有意义。 - - -在这个例子中,容器所做的唯一处理是 `echo` 一个字符串并睡眠一段时间。 -在真实的用例中,处理将是一些重要的计算,例如渲染电影的一帧,或者处理数据库中的若干行。这时,`$ITEM` 参数将指定帧号或行范围。 - - -这个 Job 及其 Pod 模板有一个标签: `jobgroup=jobexample`。这个标签在系统中没有什么特别之处。 -这个标签使得我们可以方便地同时操作组中的所有作业。 -我们还将相同的标签放在 pod 模板上,这样我们就可以用一个命令检查这些 Job 的所有 pod。 -创建作业之后,系统将添加更多的标签来区分一个 Job 的 pod 和另一个 Job 的 pod。 -注意,标签键 `jobgroup` 对 Kubernetes 并无特殊含义。您可以选择自己的标签方案。 - - -下一步,将模板展开到多个文件中,每个文件对应要处理的项。 - -```shell -# 下载 job-templ.yaml -curl -L -s -O https://k8s.io/examples/application/job/job-tmpl.yaml - -# 创建临时目录,并且在目录中创建 job yaml 文件 -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) 或编写程序来生成 Job 对象。 - - -接下来,使用 kubectl 命令创建所有作业: - -```shell -kubectl create -f ./jobs -``` - - -输出类似以下内容: - -``` -job.batch/process-item-apple created -job.batch/process-item-banana created -job.batch/process-item-cherry created -``` - - -现在,检查这些作业: - -```shell -kubectl get jobs -l jobgroup=jobexample -``` - - -输出类似以下内容: - -``` -NAME COMPLETIONS DURATION AGE -process-item-apple 1/1 14s 20s -process-item-banana 1/1 12s 20s -process-item-cherry 1/1 12s 20s -``` - - -在这里,我们使用 `-l` 选项选择属于这组作业的所有作业。(系统中可能还有其他不相关的工作,我们不想看到。) - - -使用同样的标签选择器,我们还可以检查 pods: - -```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 -``` - - -我们可以使用以下操作命令一次性地检查所有作业的输出: - -```shell -kubectl logs -f -l jobgroup=jobexample -``` - - -输出内容为: - -``` -Processing item apple -Processing item banana -Processing item cherry -``` - - - -## 多个模板参数 - - -在第一个示例中,模板的每个实例都有一个参数,该参数也用作标签。 -但是标签的键名在[可包含的字符](/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set)方面有一定的约束。 - - -这个稍微复杂一点的示例使用 jinja2 模板语言来生成我们的对象。 -我们将使用一行 python 脚本将模板转换为文件。 - - -首先,粘贴 Job 对象的以下模板到一个名为 `job.yaml.jinja2` 的文件中: - -```liquid -{%- set params = [{ "name": "apple", "url": "https://www.orangepippin.com/varieties/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 字典列表(第 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 apply -f - -``` - - -## 替代方案 - - -如果您有大量作业对象,您可能会发现: - - - -- 即使使用标签,管理这么多 Job 对象也很麻烦。 -- 在一次创建所有作业时,您超过了资源配额,可是您也不希望以递增方式创建 Job 并等待其完成。 -- 同时创建大量作业会使 Kubernetes apiserver、控制器或者调度器负压过大。 - - - -在这种情况下,您可以考虑其他的[作业模式](/docs/concepts/jobs/run-to-completion-finite-workloads/#job-patterns)。 - - +--- +title: 使用展开的方式进行并行处理 +content_type: concept +min-kubernetes-server-version: v1.8 +weight: 20 +--- + + + + + + +本任务展示基于一个公共的模板运行多个{{< glossary_tooltip text="Jobs" term_id="job" >}}。 +你可以用这种方法来并行执行批处理任务。 + +在本任务示例中,只有三个工作条目:_apple_、_banana_ 和 _cherry_。 +示例任务处理每个条目时仅仅是打印一个字符串之后结束。 +参考[在真实负载中使用 Job](#using-jobs-in-real-workloads)了解更适用于真实使用场景的模式。 + +## {{% heading "prerequisites" %}} + + +你应先熟悉基本的、非并行的 [Job](/zh/docs/concepts/workloads/controllers/job/) +的用法。 + +{{< include "task-tutorial-prereqs.md" >}} + + +任务中的基本模板示例要求安装命令行工具 `sed`。 +要使用较高级的模板示例,你需要安装 [Python](https://www.python.org/), +并且要安装 Jinja2 模板库。 + +一旦 Python 已经安装好,你可以运行下面的命令安装 Jinja2: + +```shell +pip install --user jinja2 +``` + + + + + +## 基于模板创建 Job {#create-jobs-based-on-a-template} + + +首先,将以下作业模板下载到名为 `job-tmpl.yaml` 的文件中。 + +{{< codenew file="application/job/job-tmpl.yaml" >}} + +```shell +# 使用 curl 下载 job-tmpl.yaml +curl -L -s -O https://k8s.io/examples/application/job/job-tmpl.yaml +``` + + +你所下载的文件不是一个合法的 Kubernetes {{< glossary_tooltip text="清单" term_id="manifest" >}}。 +这里的模板只是 Job 对象的 yaml 表示,其中包含一些占位符,在使用它之前需要被填充。 +`$ITEM` 语法对 Kubernetes 没有意义。 + + +### 基于模板创建清单 + +下面的 Shell 代码片段使用 `sed` 将字符串 `$ITEM` 替换为循环变量,并将结果 +写入到一个名为 `jobs` 的临时目录。 + +```shell +# 展开模板文件到多个文件中,每个文件对应一个要处理的条目 +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 +``` + + +你可以使用任何一种模板语言(例如:Jinja2、ERB),或者编写一个程序来 +生成 Job 清单。 + + +### 基于清单创建 Job + +接下来用一个 kubectl 命令创建所有的 Job: + +```shell +kubectl create -f ./jobs +``` + + + +输出类似于: + +``` +job.batch/process-item-apple created +job.batch/process-item-banana created +job.batch/process-item-cherry created +``` + + +现在检查 Job: + +```shell +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)。 + +你可以使用相同的 {{< glossary_tooltip text="标签选择算符" term_id="selector" >}} +来过滤 Pods: + +```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 +``` + + +我们可以用下面的命令查看所有 Job 的输出: + +```shell +kubectl logs -f -l jobgroup=jobexample +``` + + +输出类似于: + +``` +Processing item apple +Processing item banana +Processing item cherry +``` + + +### 清理 {#cleanup-1} + +```shell +# 删除所创建的 Job +# 集群会自动清理 Job 对应的 Pod +kubectl delete job -l jobgroup=jobexample +``` + + +## 使用高级模板参数 + +在[第一个例子](#create-jobs-based-on-a-template)中,模板的每个示例都有一个参数 +而该参数也用在 Job 名称中。不过,对象 +[名称](/zh/docs/concepts/overview/working-with-objects/names/#names) +被限制只能使用某些字符。 + + +这里的略微复杂的例子使用 [Jinja 模板语言](https://palletsprojects.com/p/jinja/) +来生成清单,并基于清单来生成对象,每个 Job 都有多个参数。 + +在本任务中,你将会使用一个一行的 Python 脚本,将模板转换为一组清单文件。 + +首先,复制下面的 Job 对象模板到一个名为 `job.yaml.jinja2` 的文件。 + +```liquid +{%- 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 + command: ["sh", "-c", "echo Processing URL {{ url }} && sleep 5"] + restartPolicy: Never +{%- endfor %} +``` + + +上面的模板使用 python 字典列表(第 1-4 行)定义每个作业对象的参数。 +然后使用 for 循环为每组参数(剩余行)生成一个作业 yaml 对象。 +我们利用了多个 YAML 文档(这里的 Kubernetes 清单)可以用 `---` 分隔符连接的事实。 +我们可以将输出直接传递给 kubectl 来创建对象。 + +接下来我们用单行的 Python 程序将模板展开。 + +```shell +alias render_template='python -c "from jinja2 import Template; import sys; print(Template(sys.stdin.read()).render());"' +``` + + +使用 `render_template` 将参数和模板转换成一个 YAML 文件,其中包含 Kubernetes +资源清单: + +```shell +# 此命令需要之前定义的别名 +cat job.yaml.jinja2 | render_template > jobs.yaml +``` + + +你可以查看 `jobs.yaml` 以验证 `render_template` 脚本是否正常工作。 + +当你对输出结果比较满意时,可以用管道将其输出发送给 kubectl,如下所示: + +```shell +cat job.yaml.jinja2 | render_template | kubectl apply -f - +``` + + +Kubernets 接收清单文件并执行你所创建的 Job。 + + + + +## 在真实负载中使用 Job + +在真实的负载中,每个 Job 都会执行一些重要的计算,例如渲染电影的一帧, +或者处理数据库中的若干行。这时,`$ITEM` 参数将指定帧号或行范围。 + +在此任务中,你运行一个命令通过取回 Pod 的日志来收集其输出。 +在真实应用场景中,Job 的每个 Pod 都会在结束之前将其输出写入到某持久性存储中。 +你可以为每个 Job 指定 PersistentVolume 卷,或者使用其他外部存储服务。 +例如,如果你在渲染视频帧,你可能会使用 HTTP 协议将渲染完的帧数据 +用 'PUT' 请求发送到某 URL,每个帧使用不同的 URl。 + + +## Job 和 Pod 上的标签 + +你创建了 Job 之后,Kubernetes 自动为 Job 的 Pod 添加 +{{< glossary_tooltip text="标签" term_id="label" >}},以便能够将一个 Job +的 Pod 与另一个 Job 的 Pod 区分开来。 + +在本例中,每个 Job 及其 Pod 模板有一个标签: `jobgroup=jobexample`。 + +Kubernetes 自身对标签名 `jobgroup` 没有什么要求。 +为创建自同一模板的所有 Job 使用同一标签使得我们可以方便地同时操作组中的所有作业。 +在[第一个例子](#create-jobs-based-on-a-template)中,你使用模板来创建了若干 Job。 +模板确保每个 Pod 都能够获得相同的标签,这样你可以用一条命令检查这些模板化 +Job 所生成的全部 Pod。 + + +{{< note >}} +标签键 `jobgroup` 没什么特殊的,也不是保留字。 你可以选择你自己的标签方案。 +如果愿意,有一些[建议的标签](/zh/docs/concepts/overview/working-with-objects/common-labels/#labels) +可供使用。 +{{< /note >}} + + +## 替代方案 + +如果你有计划创建大量 Job 对象,你可能会发现: + + +- 即使使用标签,管理这么多 Job 对象也很麻烦。 +- 如果你一次性创建很多 Job,很可能会给 Kubernetes 控制面带来很大压力。 + 一种替代方案是,Kubernetes API 可能对请求施加速率限制,通过 429 返回 + 状态值临时拒绝你的请求。 +- 你可能会受到 Job 相关的{{< glossary_tooltip text="资源配额" term_id="resource-quota" >}} + 限制:如果你在一个批量请求中触发了太多的任务,API 服务器会永久性地拒绝你的某些请求。 + + +还有一些其他[作业模式](/zh/docs/concepts/workloads/controllers/job/#job-patterns) +可供选择,这些模式都能用来处理大量任务而又不会创建过多的 Job 对象。 + +你也可以考虑编写自己的[控制器](/zh/docs/concepts/architecture/controller/) +来自动管理 Job 对象。 +