介绍
Field Notes 有二十张获得许可的花园照片,需要制作一个可供员工下载的小型图库。你将先试着处理一组,再使用 Kubernetes Job 运行全部四组。最后一组只有两张照片,但仍需要一个成功完成的独立索引。
你应当已经认识 Pod、Deployment、Service、标签、简单的 YAML 和基本终端路径。下文将介绍 Job、完成索引、本课使用的共享卷以及编辑命令。集群、处理器镜像、原始照片、网站和存储均已准备就绪。你无需编写 Python、构建镜像或拥有云账户。
使用 Workspace 执行命令,使用 Gallery 访问网站。两个入口使用同一台训练 VM。保持 starter/ 和 inputs/ 下提供的文件、源照片、参考示例、处理器、服务和存储不变。45 分钟的时长是作者估计,包括阅读和处理普通错误的时间。
先试着处理一组
在这一步中,你将把一个正常的成品示例与其源图比较,然后使用只有一个索引的 Job 处理单独的样本。
打开 Gallery。首页大幅照片的标签是 Source collection · licensed garden photographs:它是输入素材,并不是你已完成的作品。选择 See the reference。其中的 Thumbnail 是提供的 320 × 220 JPEG;Licensed source copy 保留原始比例。裁剪使两者的边缘有所不同。在页面导航中选择 Gallery,它最初显示 0 of 20 images available。
切换到 Workspace。这个终端的起始目录是 /home/labex/project/photo-gallery。pwd 打印该工作目录:
pwd
如果你已切换到其他位置,请使用 cd /home/labex/project/photo-gallery 返回。下文所有相对文件名均以此目录为起点。文件会保留在本 VM 中,但其他实验将使用各自独立的文件和集群资源。
Deployment 使服务持续运行,而 Job 运行 Pod,直到一项有限的工作完成。我们提供的 worker 处理一组照片后便退出。在 Indexed Job 中,Kubernetes 为每个要求完成的任务分配一个从零开始的编号。worker 读取原生的 JOB_COMPLETION_INDEX 值,并选择对应的分组。你需要配置 Job,而不必编写 worker。
读取提供的样本清单。cat 显示文件内容,不会修改文件:
cat starter/sample-job.yaml
其中的重要字段是 completionMode: Indexed、completions: 1 和 parallelism: 1。它们只请求索引 0,期望同时运行一个 worker。restartPolicy: Never 表示失败的容器会在其 Pod 中保持失败状态,由 Job 策略决定是否需要另一个 Pod。backoffLimit: 0 表示在这个源服务正常的练习中不进行重试,activeDeadlineSeconds: 180 则限制 Job 的活动存续时间。其余字段连接准备好的镜像、输入计划和输出卷;请保持不变。
PersistentVolumeClaim,简称 PVC,用于申请持久存储。准备好的 gallery-output PVC 会在 worker 退出后保留文件。这里的 ReadWriteOnce 卷可由同一节点上的多个 Pod 挂载,但这并不保证多节点间的共享写入。样本写入独立的 sample 目录,因此不会悄悄补齐最终图库。
应用样本。apply -f 读取指定的 YAML 文件;-n 选择命名空间,命名空间用于将本项目的 Kubernetes 资源归为一组:
kubectl apply -n photo-workshop -f starter/sample-job.yaml
等待 Job 的原生 Complete 条件成立。--timeout=180s 限制这条命令的等待时间,并不改变 Job 的截止时限:
kubectl wait -n photo-workshop --for=condition=Complete job/gallery-sample --timeout=180s
预期末尾输出为 job.batch/gallery-sample condition met。仅仅超时并不能证明失败:请先使用第 3 步教的命令检查 Job 及其日志,再决定要修改什么。不要重新运行 setup 或删除提供的资源。
切换到 Gallery,选择 Sample,确认显示 6 of 6 images available。打开 Windmill garden,比较缩略图与源图,再选择 Back to sample。在页面导航中选择 Gallery:最终集合仍为空。区分这两者,可以避免把一次成功的练习运行误认为完整交付。
规划每个完成索引
在这一步中,你将把输入分组转换为保存的 Job 清单。这一步还不会启动最终任务。
切换到 Workspace,起始目录为 /home/labex/project/photo-gallery。读取完整的输入映射:
cat inputs/groups.txt
其中有四行,从 Index 0 到 Index 3。前三组各有六张照片;索引 3 包含 photo-19 和 photo-20。completions 计算的是分组数,而不是照片数:本次交付需要 4 个成功索引,不是 20,也不是 3。
读取员工需求说明:
cat inputs/brief.txt
说明允许请求 1 到 2 的并行度。我们将请求 2,让两个分组可以同时推进。资源不足时,Kubernetes 可能运行更少的 Pod;请求的并行度并不是对重试或终止期间所有进程的绝对上限。本项目使用一个准备好的小型节点,并非分布式处理平台。
将准备好的最终 Job 模板复制为可编辑文件。cp 复制文件,同时保留 starter/ 下的源文件:
cp starter/gallery-job.yaml gallery-job.yaml
如果 cp 报告 No such file or directory,请检查 pwd,返回上述目录,再使用准确的文件名重复同一复制操作。不要创建空文件代替模板。
使用 sed 修改复制文件中的两个字段字面值。-i 直接编辑该文件。每个表达式的形式为 s/old/new/:把旧文本替换为新文本。单引号让 shell 按字面值传递表达式。这两个确切字段在提供的模板中各出现一次:
sed -i 's/completions: 1/completions: 4/' gallery-job.yaml
sed -i 's/parallelism: 1/parallelism: 2/' gallery-job.yaml
应用之前读取保存的结果:
cat gallery-job.yaml
确认其中包含 metadata.name: gallery-batch、completionMode: Indexed、completions: 4 和 parallelism: 2。保留准备好的处理器镜像、命令、环境、挂载、资源设置、不重试策略和 180 秒截止时限。等效的 YAML 格式或另一个新的最终 Job 名称都可以接受;保存的文件必须描述你实际运行的 Job。
替换表达式即使拼写错误、没有匹配任何内容,也可能成功退出,所以读取结果很重要。如果尚未应用文件,可以重新复制原始模板,再重复两次编辑来恢复。应用 Job 后,避免重新复制一个名称冲突的清单:Job 的任务模板通常不可变,因此不同的尝试应使用新的 Job 名称。
运行完整批次
在这一步中,你将应用保存的 Job,并把原生完成证据与真实的 worker Pod 对应起来。
在 Workspace 中,位于 /home/labex/project/photo-gallery 时,应用保存的文件:
kubectl apply -n photo-workshop -f gallery-job.yaml
等待所有要求的索引完成:
kubectl wait -n photo-workshop --for=condition=Complete job/gallery-batch --timeout=180s
读取原生 Job 摘要:
kubectl get job gallery-batch -n photo-workshop
完成的 Job 应显示 4/4 个完成任务。describe 增加详情和事件:
kubectl describe job gallery-batch -n photo-workshop
查找 Completion Mode: Indexed 和 Completed Indexes: 0-3。范围包含两端,因此这里覆盖全部四组,包括较短的最后一组。仅凭完成证据不能确认图片内容正确;接下来你将检查并下载图片。
只列出属于这个 Job 的 Pod。-l 通过标签筛选;-L 添加显示另一个标签的列,这里显示的是完成索引:
kubectl get pods -n photo-workshop -l batch.kubernetes.io/job-name=gallery-batch -L batch.kubernetes.io/job-completion-index
预期看到索引 0、1、2、3 对应的已完成 Pod。表中的 Completed 对应 Pod 的成功阶段。读取它们实际的 worker 输出。--all-pods=true 包含这个 Job 的各个 Pod,--prefix=true 标明每行来自哪个 Pod:
kubectl logs -n photo-workshop job/gallery-batch --all-pods=true --prefix=true
每个 worker 都会列出分配给它的照片,并报告 320 x 220 的输出。索引 3 应完成两张照片。这些日志有助于解释发生的过程;功能检查还会检查实际文件、它们与源图的关系,以及属于该 Job 的成功 Pod。
如果等待超时,请使用 describe 以及相同的 Pod/日志命令,区分工作负载是尚未完成还是已经失败。未知 Job 名称通常表示尚未应用文件,或其名称不同:读取 gallery-job.yaml,并使用实际名称。不要清除已完成的 Pod 或样本。下一个引导实验会介绍如何从刻意设置的源服务临时故障中恢复。
交付真实图片
在这一步中,你将检查完整网站,并保存两个可用的交付文件。
切换到 Gallery,在页面导航中选择 Gallery,然后刷新。确认显示 20 of 20 images available。打开较短的最后一组中的 Blue and yellow flowers(photo-20)。将缩略图与它自己的源图比较;它不能是另一张花卉照片的重复副本。选择 Download thumbnail,然后选择 Back to gallery。

最终图库包含二十张不同的已处理照片,包括较短的最后一组中的两张图片。下载操作位于集合下方。
在 Take the collection with you 处,选择 Download source index CSV 和 Download gallery ZIP。CSV 将每张输出与其源图哈希、缩略图哈希、创作者和来源页面对应起来。ZIP 包含 index.csv 以及 photos/ 下的实际 JPEG。页面反映的是实际可用的文件,不会从原图重新生成缺失的作品。不完整的集合也可以下载,因此交付前要验证完整结果。
在页面导航中选择 Reference,确认原先提供的示例仍然可用。选择 Credits,查看创作者和 Wikimedia Commons 来源链接。这些照片是采用 CC0 许可的测试素材,并非 LabEx 拍摄的照片。
切换到 Workspace,位于 /home/labex/project/photo-gallery。在这里保存相同的真实下载文件。curl 请求一个 URL;--fail 在 HTTP 请求失败时返回错误,--silent --show-error 隐藏进度条但保留错误信息,-o 指定输出文件名。端口 8091 可从这台 VM 访问准备好的图库 Service:
curl --fail --silent --show-error http://127.0.0.1:8091/download/index.csv -o gallery-index.csv
curl --fail --silent --show-error http://127.0.0.1:8091/download/gallery.zip -o gallery.zip
浏览器下载的文件由浏览器保存;这两条命令则把要求的副本保存在 VM 工作区。如果请求失败,检查网站,解决问题后重新请求。不要编辑收到的索引来掩盖缺失的图片。
显示表头和前两行。head -n 3 读取前三行:
head -n 3 gallery-index.csv
表头以 id,title,source_file,thumbnail,width,height 开始。每行将一张照片映射到它自己的 photos/photo-NN.jpg,尺寸为 320,220,并保留实际来源署名。
列出归档内容而不解压。python3 -m zipfile 运行 Python 的标准 ZIP 工具;-l 列出成员:
python3 -m zipfile -l gallery.zip
找到 index.csv 和全部二十个 JPEG 路径,包括 photos/photo-20.jpg。仅凭名称或数量不能证明图片正确,因此功能检查还会解码实际 JPEG,并将每张图片与由它自己的源图生成的结果比较。请保留 gallery-job.yaml、两个下载文件、样本、已完成的 Pod、源文件、参考示例和准备好的服务,使它们继续可用。
总结
你使用 Indexed Job 交付了真实的照片图库。你将照片映射到完成索引,限制请求的并行度,检查原生 Pod 和 Job 证据,并交付带来源索引的实际图片。样本和参考示例始终与最终作品分开。



