LabHub
学习 学习路径 课程

CKAD — Kubernetes 应用开发者

重复发货事件 — Job成功不等于业务成功

在 LabHub 中继续学习

目标

复现一个处理订单的 Job 虽然成功、却创建了两次配送的事件,并依据重试、幂等键和执行期限进行解释。

为什么重要

即使 completions 和 parallelism 都是 1,也不能保证外部业务只会处理一次,因为失败的进程可能已经完成了持久化。只有分别观测 Kubernetes 管理的执行生命周期与账本管理的业务生命周期,才能判断是否应该重新执行。

本实验使用个人 VM 中真实的 k3s,以及由 HTTP 和 SQLite 构成的合成账本,不涉及真实配送、支付或个人信息。账本的 safe 模式通过服务器端写事务和业务键唯一性合并重复行。本实验讲解该契约的应用和验证,并不保证整个分布式系统能够精确处理一次。删除账本 Pod 后,数据也会消失。

首次准备最多允许 6 分钟。所有文件均放在 /root/ckad-job-effects 下。会话结束后工作内容会消失,请提前下载需要的记录。实验按 55 分钟设计,如需更多时间,请在到期前延长。

使用的模板与运行辅助程序

job-template.json 是包含真实 worker 命令、固定镜像和权限限制的 Job JSON。每一步都复制该文件,然后修改 metadata.name、spec.backoffLimit,以及容器 env 中的 CASE_ID、MODE、FAULT。Pod 模板 spec.template.metadata.labels 中的 labhub.io/case 也应改为与 CASE_ID 相同的值。WORK_ID 保持 order-001,POD_UID 保持使用 Downward API。parallelism 和 completions 保持为 1,restartPolicy 保持 Never。其他安全和应用设置也不要修改。仅在需要期限的步骤中添加 spec.activeDeadlineSeconds。

不要直接使用 kubectl create 执行。编写文件后,运行 python3 /opt/fixtures/ckad_job_effects_lab.py run <단계>。辅助程序会将学员文件原样提交给 API,并保存执行期间的 Pod 观测。再次运行已经完成的同一步骤时,不会创建新 Job,只会进行评分。如果观测等待被中断,请用同一命令继续,不要删除或重新创建 Job。如果连创建响应本身都丢失了,应要求开始新的实验,而不是重复创建。

步骤

  1. baseline.json:name=baseline,CASE_ID=baseline,MODE=unsafe,FAULT=none,backoffLimit=0。通过 run 1 观测正常对照组。
  2. unsafe-retry.json:name=unsafe-retry,CASE_ID=unsafe-retry,MODE=unsafe,FAULT=after-commit,backoffLimit=1。通过 run 2 观测持久化后失败以及替代 Pod。
  3. 读取 observation-2.json,在 retry-analysis.json 中填写 job_uid、failed_pod_uid、succeeded_pod_uid、request_count、shipment_count、retry_layer。retry_layer 应根据观测填写 job-controller 或 kubelet-container。通过 run 3 记录分析。
  4. safe-retry.json:name=safe-retry,CASE_ID=safe-retry,MODE=safe,FAULT=after-commit,backoffLimit=1。通过 run 4 确认相同故障下的幂等结果。
  5. safe-replay.json:name=safe-replay,CASE_ID=safe-retry,MODE=safe,FAULT=none,backoffLimit=0。通过 run 5 检查在新 Job 中是否仍沿用相同业务键。
  6. no-retry.json:name=no-retry,CASE_ID=no-retry,MODE=unsafe,FAULT=after-commit,backoffLimit=0。通过 run 6 检查即使失败,配送是否仍然保留。
  7. deadline.json:name=deadline,CASE_ID=deadline,MODE=safe,FAULT=deadline,backoffLimit=0,activeDeadlineSeconds=20。通过 run 7 观测超出期限及账本。即使当前 Pod 列表为空,也不要删除 observed_pods 中保存的历史观测。
  8. 比较六次执行并编写 final-analysis.json。request_count 和 shipment_count 是账本总计,safe_replay_shipment_id 是重新执行时保持不变的配送编号。timeout_cancels_effect 和 ledger_survives_ledger_pod_replacement 分别是关于超出期限是否取消业务、账本 Pod 被替换后数据是否保留的布尔值。idempotency_scope 应使用实际用于重复判定的字段名,并用 + 连接。outcomes 以六个 Job 名称为键,填写 condition(Complete/Failed)和 shipments(每次执行后该业务的配送数)。通过 run 8 记录综合分析。

参考

正常配送对照组

按照模板编写 /root/ckad-job-effects/baseline.json,并使用第 1 步指定的名称、业务键、模式、故障条件和重试预算运行 run 1。

即使是正常执行,也要分别保留 Job 和账本的观测。

明明成功,却有两条记录

在 /root/ckad-job-effects/unsafe-retry.json 中声明持久化后失败及一次重试,并运行 run 2。

Never 是同一 Pod 的容器重启策略。请查看替代 Pod 的 UID。

究竟是哪一层在重试

从 observation-2.json 中读取 Job、失败 Pod、成功 Pod 的 UID,以及请求数和配送数,将其记录到 retry-analysis.json 并运行 run 3。

同时比较相同的 ownerReferences、不同的 Pod UID 和 restartCount。

相同故障,只产生一次配送

在 /root/ckad-job-effects/safe-retry.json 中声明 safe 模式和稳定的业务键,并运行 run 4。

检查即使请求重复,是否仍关联到同一个配送编号。

使用另一个 Job 重新执行

以单独的 Job 名称编写 /root/ckad-job-effects/safe-replay.json,但保持 CASE_ID=safe-retry,并运行 run 5。

执行 ID 应当不同,而业务 ID 必须保持不变。

关闭重试后仍会留下什么

在 /root/ckad-job-effects/no-retry.json 中声明 backoffLimit=0 和持久化后失败,并运行 run 6。

Failed 条件并不表示持久化已回滚。

时间结束后仍会留下什么

在 /root/ckad-job-effects/deadline.json 中声明 activeDeadlineSeconds=20 和持久化后等待,并运行 run 7。

区分执行终止与业务取消,并保留删除前的 Pod 观测。

配送事故综合报告

根据第 1、2、4、5、6、7 步的观测,填写 final-analysis.json 中的总计、复用的配送编号、取消和保留判断、业务键范围以及六个 outcomes,然后运行 run 8。

总请求数、各业务的配送数和执行成功条件是不同的指标。