让Pod下班 — 先完成响应再退出
目标
在个人 k3s VM 中终止 Pod,同时让已经处理中的 HTTP 请求继续完成,并把应用的终止处理、EndpointSlice 与终止宽限期联系起来进行说明。本实验使用真实进程,而不是 kwok 状态模拟。
为什么重要
Deployment 显示成功并不能保证处理中的请求已经完成。这里运行一个由只读代码构成的小型 HTTP 服务器,辅助程序会严格按照学员编写的 Pod 声明创建它。先确认 Service 路径和请求已被接受,再在启动 Pod watch 的状态下删除,因此可以把同一 UID 的响应、断连和退出代码关联起来。
本实验会比较正常终止和宽限期不足的情形,但不是要求精确匹配小数级时间。它不是通过复制输入文件或观测文件来拼出成功语句,而是检查实际运行结果是否与解释相符。拒绝新请求和终止现有连接并不是同一件事,而且本实验也不能证明包括外部负载均衡器在内的整个服务实现了零停机。
预计用时 80 分钟。会话默认持续 60 分钟,请在到期前通过+时间延长(最长 180 分钟)。会话结束后,VM 和文件都会被回收。请在结束前另行保存需要保留的结果。
步骤
- 将个人 VM 的 node_name、server_version、namespace_uid、image 记录到 /root/ckad-inflight-termination/environment.json,并运行 run 1。namespace 为 ckad-inflight-termination,image 为所提供 pod-template.json 中的服务器镜像。
- 创建 immediate.json。Pod 名称和 case 标签为 immediate,MODE 为 immediate,WORK_SECONDS 为字符串 8,terminationGracePeriodSeconds 为整数 15,并且没有 lifecycle。通过 run 2 在终止期间观测服务器已经接受的请求。
- 在 immediate-analysis.json 中填写观测到的 pod_uid、client_error、exit_code,以及对 accepted_before_delete、readiness_alone_drains_requests 的判断,然后运行 run 3。区分请求是否在删除前已被接受,以及仅靠 readiness 是否能结束请求。
- 创建 graceful.json。名称和 case 标签为 graceful,MODE 为 graceful,WORK_SECONDS 为字符串 8,宽限期为整数 15。保留模板中的 preStop,使其在调用 /drain 后等待 2 秒,然后运行 run 4。
- 在 graceful-analysis.json 中写入 pod_uid,并将第一个 terminating endpoint 的完整 conditions 写入 first_terminating_conditions。结合观测结果判断 hook_before_term、accepted_response_complete、endpoint_ready_false_means_connection_closed,然后运行 run 5。
- 创建 exhausted.json。名称和 case 标签为 exhausted,MODE 为 graceful,WORK_SECONDS 为字符串 10,宽限期为整数 5。只把 preStop 中 /drain 后的 sleep 改为 3 秒,然后运行 run 6。观察即使终止模式相同,请求结果是否也会不同。
- 创建 repaired.json。名称和 case 标签为 repaired,MODE 为 graceful,WORK_SECONDS 为字符串 10,宽限期为整数 15,preStop 在 /drain 后等待 3 秒。通过 run 7 确认真正完成的响应。不要修改已有观测文件。
- 在 comparison.json 的 outcomes 中,分别记录 immediate、graceful、exhausted、repaired 的 pod_uid、client_completed、exit_code。判断 grace_includes_prestop、api_delete_is_exact_deadline、service_zero_downtime_proven,然后运行 run 8。
参考
- 工作目录是 /root/ckad-inflight-termination。所有步骤都使用 python3 /opt/fixtures/ckad_termination_lab.py run 步骤编号运行。评分不会重新执行观测,而是读取保存的文件。
- 复制 pod-template.json,只修改必要字段。image、command、安全设置和 run 标签是实验的控制条件。不要预先使用 kubectl apply 创建 Pod。run 会根据学员声明,在同一流程中观测创建、请求和删除。
- JSON 是 Kubernetes 接受的清单格式。环境变量 WORK_SECONDS 必须是字符串,终止宽限期必须是整数。不要用示例 JSON 覆盖模板原件。
- preStop 的 exec 命令位于模板中。保留 /drain 调用,仅按步骤要求修改最后一个 time.sleep 的数字。在立即终止步骤中,应删除整个 lifecycle。
- 前置步骤可通过 prepare 步骤编号准备。它不会生成当前步骤的答案,也不会覆盖学员已经编写的文件。如果前面步骤的文件写错了,请先修正。
- 重新运行中断的观测无法恢复之前的 Pod。请保留错误记录;如果辅助程序要求开始新的实验,请不要随意删除标记,而应重新启动环境。
- 退出代码 17 是该服务器选定的对比标记。不要仅凭 137 就断定发生了 OOM 或宽限期不足;应同时查看 watch 的 reason、实际设置、hook 和 term。
- 参考:https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/ · https://kubernetes.io/docs/tutorials/services/pods-and-endpoint-termination-flow/
确认这里是真正的运行环境
将个人 VM 的 node_name、server_version、namespace_uid、image 记录到 /root/ckad-inflight-termination/environment.json,并运行 run 1。namespace 为 ckad-inflight-termination,image 为所提供 pod-template.json 中的服务器镜像。
读取 kubectl get nodes、kubectl version -o json、kubectl get namespace 的 metadata.uid 以及 Pod 模板。不要连接外部集群。
留下客人,立即下班
创建 immediate.json。Pod 名称和 case 标签为 immediate,MODE 为 immediate,WORK_SECONDS 为字符串 8,terminationGracePeriodSeconds 为整数 15,并且没有 lifecycle。通过 run 2 在终止期间观测服务器已经接受的请求。
只修改 pod-template.json 中的名称、case 标签、MODE、WORK_SECONDS、终止宽限期和 preStop。run 会根据该声明观测真实请求和删除过程。
Ready 标记不会替你完成订单
在 immediate-analysis.json 中填写观测到的 pod_uid、client_error、exit_code,以及对 accepted_before_delete、readiness_alone_drains_requests 的判断,然后运行 run 3。区分请求是否在删除前已被接受,以及仅靠 readiness 是否能结束请求。
关联 observation-2.json 的 observation 中的 initial.events、delete_at、client 和 pod_watch。退出代码位于最后一个容器的 terminated 状态中。
完成已接订单后再下班
创建 graceful.json。名称和 case 标签为 graceful,MODE 为 graceful,WORK_SECONDS 为字符串 8,宽限期为整数 15。保留模板中的 preStop,使其在调用 /drain 后等待 2 秒,然后运行 run 4。
只修改 pod-template.json 中的名称、case 标签、MODE、WORK_SECONDS、终止宽限期和 preStop。run 会根据该声明观测真实请求和删除过程。
门已关闭,但响应仍能送达
在 graceful-analysis.json 中写入 pod_uid,并将第一个 terminating endpoint 的完整 conditions 写入 first_terminating_conditions。结合观测结果判断 hook_before_term、accepted_response_complete、endpoint_ready_false_means_connection_closed,然后运行 run 5。
按时间顺序读取 observation-4.json 中的 endpoints。比较 server_events 中的 hook、term 与 client.completed,不要把 ready=false 等同于现有连接断开。
终止宽限期设置得过短
创建 exhausted.json。名称和 case 标签为 exhausted,MODE 为 graceful,WORK_SECONDS 为字符串 10,宽限期为整数 5。只把 preStop 中 /drain 后的 sleep 改为 3 秒,然后运行 run 6。观察即使终止模式相同,请求结果是否也会不同。
只修改 pod-template.json 中的名称、case 标签、MODE、WORK_SECONDS、终止宽限期和 preStop。run 会根据该声明观测真实请求和删除过程。
补足宽限期后重新观测
创建 repaired.json。名称和 case 标签为 repaired,MODE 为 graceful,WORK_SECONDS 为字符串 10,宽限期为整数 15,preStop 在 /drain 后等待 3 秒。通过 run 7 确认真正完成的响应。不要修改已有观测文件。
只修改 pod-template.json 中的名称、case 标签、MODE、WORK_SECONDS、终止宽限期和 preStop。run 会根据该声明观测真实请求和删除过程。
结论不得超出观测范围
在 comparison.json 的 outcomes 中,分别记录 immediate、graceful、exhausted、repaired 的 pod_uid、client_completed、exit_code。判断 grace_includes_prestop、api_delete_is_exact_deadline、service_zero_downtime_proven,然后运行 run 8。
比较第 2、4、6、7 步的观测。宽限期预算包含 hook,但 API 删除时刻并不保证就是精确终止时刻,单次请求也不能证明整个服务的可用性。