LabHub
学习 学习路径 课程

把K8s弄坏吧 — 原来是我的YAML

原来是我的YAML — 三起故障实验笔记

在 LabHub 中继续学习

目标

在真实的 k3s 中制造 selector 不匹配、错误的 readiness 路径和进程退出, 保存观测依据后以最小改动恢复。本实验面向了解 kubectl get/apply 与 YAML 基本结构的中级学习者,预计用时 50 分钟。

为什么重要

Running、Ready 和用户请求成功是彼此不同的事实。即使存在正常 Pod, Service selector 或端口错误时,客户仍会遇到失败。反之,进程可能仍然存活, 却因不满足就绪条件而被排除在流量目标之外。每次只修改一个字段并比较前后状态, 就能说明原因,而不是反复重启。区分三个事件后返回已确认的正常 revision, 即为本实验的范围。

安全范围

仅使用临时 VM 内的 API https://127.0.0.1:6443과 labhub-mystery 命名空间。 不要在生产集群或其他学员的环境中执行。所提供应用使用单个 replica 和 Recreate,避免原有正常 Pod 掩盖故障。这并不表示它是推荐的零停机运行配置。 应用使用 image digest 固定。会话结束后,VM 和观测文件都会消失。如有需要, 请在到期前延长时间并另行保存观测。观测文件是可编辑的实验笔记,并非不可伪造的证据。

步骤

  1. 使用 kubectl apply 应用所提供的 /opt/fixtures/k8s-mystery-app.json。查询 labhub-mystery 命名空间中的 parcel Deployment 和 Service,确认 Pod 与 Service 两侧的 8080 端口 / 路径都返回正文 labhub-mystery-v1,并将正常状态记录为 baseline。记录命令:python3 /opt/fixtures/k8s_mystery.py observe baseline /root/k8s-mystery/01-baseline.json
  2. 只把 parcel Service 的 selector 改为 app=missing,保持 Pod 标签和 Deployment 不变。此时 Pod 为 Ready 且可直接响应,但 Service 选中的 endpoint 为 0,请求失败;将该状态记录为 selector-broken。记录命令:python3 /opt/fixtures/k8s_mystery.py observe selector-broken /root/k8s-mystery/02-selector.json
  3. 将 Service selector 恢复为 app=parcel。确认有 1 个选中的 Ready endpoint,且 Pod 与 Service 两侧 HTTP 都正常,并记录为 selector-restored。不要删除并重新创建 Deployment。记录命令:python3 /opt/fixtures/k8s_mystery.py observe selector-restored /root/k8s-mystery/03-selector-fixed.json
  4. 只把 Deployment 中 parcel 容器的 readinessProbe.httpGet.path 改为 /missing。新 Pod 处于 Running,可直接响应 /,但 Ready=false、重启次数为 0,当前 Pod 有 404 事件,Ready endpoint 为 0;将该状态记录为 readiness-broken。记录命令:python3 /opt/fixtures/k8s_mystery.py observe readiness-broken /root/k8s-mystery/04-readiness.json
  5. 将同一个 readiness 路径恢复为 /。不要删除 probe,也不要替换成始终成功的 exec。确认 Pod 与 Service 的 HTTP 正常且有 1 个 Ready endpoint,并记录为 readiness-restored。记录命令:python3 /opt/fixtures/k8s_mystery.py observe readiness-restored /root/k8s-mystery/05-readiness-fixed.json
  6. 查询正常的 Deployment revision,并保存到 Deployment annotation labhub.io/known-good-revision。随后把 parcel 的 command 替换为 ["sh","-ec","echo intentional-exit-17 >&2; exit 17"]。确认 CrashLoopBackOff、上一次退出码为 17、重启次数至少为 1,且上一份日志包含 intentional-exit-17,并记录为 crash。记录命令:python3 /opt/fixtures/k8s_mystery.py observe crash /root/k8s-mystery/06-crash.json
  7. 查看 rollout history 和保存的 labhub.io/known-good-revision,然后 rollout undo 到该 revision。在保留现有 Deployment 的情况下,确认正常 command、probe /、Service selector app=parcel、targetPort 8080 以及两侧 HTTP 都已恢复,并记录为 rollback。记录命令:python3 /opt/fixtures/k8s_mystery.py observe rollback /root/k8s-mystery/07-rollback.json
  8. 保留前 7 个观测文件,并在 /root/k8s-mystery/report.json 中为 selector、readiness、crash 事件分别关联 cause、prevention、evidence。原因和预防代码见下方参考,evidence 使用发生故障时的文件名。当前 Service 也必须正常。最后点击完整评分,同时检查历史观测与当前恢复状态。

参考

观测工具的 observe 只执行查询和文件保存,不会代替你注入或恢复请求的故障。 如果 45 秒内未看到条件,请检查值和事件后再次观测。前 7 步的评分读取已保存记录, 因此恢复后也不会取消过去步骤。最终评分会两次确认同一 Deployment 的记录关联以及 当前 Pod、Service 的 HTTP。

包裹正常送达的基线

使用 kubectl apply 应用所提供的 /opt/fixtures/k8s-mystery-app.json。查询 labhub-mystery 命名空间中的 parcel Deployment 和 Service,确认 Pod 与 Service 两侧的 8080 端口 / 路径都返回正文 labhub-mystery-v1,并将正常状态记录为 baseline。记录命令:python3 /opt/fixtures/k8s_mystery.py observe baseline /root/k8s-mystery/01-baseline.json

不要只看 Running 标记,要区分 Ready、EndpointSlice 与实际 HTTP。观测工具不会创建期望状态。

配送车辆还活着,却没有目的地

只把 parcel Service 的 selector 改为 app=missing,保持 Pod 标签和 Deployment 不变。此时 Pod 为 Ready 且可直接响应,但 Service 选中的 endpoint 为 0,请求失败;将该状态记录为 selector-broken。记录命令:python3 /opt/fixtures/k8s_mystery.py observe selector-broken /root/k8s-mystery/02-selector.json

比较 Service 的 selector 与 Pod 的 labels。更改选择条件并不会同时更改现有 Pod 标签。

只修正配送名单

将 Service selector 恢复为 app=parcel。确认有 1 个选中的 Ready endpoint,且 Pod 与 Service 两侧 HTTP 都正常,并记录为 selector-restored。不要删除并重新创建 Deployment。记录命令:python3 /opt/fixtures/k8s_mystery.py observe selector-restored /root/k8s-mystery/03-selector-fixed.json

有重启应用的依据吗?只恢复被错误修改的对象字段,并通过实际请求确认效果。

因不存在的体检室而停止配送

只把 Deployment 中 parcel 容器的 readinessProbe.httpGet.path 改为 /missing。新 Pod 处于 Running,可直接响应 /,但 Ready=false、重启次数为 0,当前 Pod 有 404 事件,Ready endpoint 为 0;将该状态记录为 readiness-broken。记录命令:python3 /opt/fixtures/k8s_mystery.py observe readiness-broken /root/k8s-mystery/04-readiness.json

修改的是 readiness 路径,而不是 liveness。还要检查事件目标 UID 是否与当前 Pod 相同。

让体检契约符合实际路径

将同一个 readiness 路径恢复为 /。不要删除 probe,也不要替换成始终成功的 exec。确认 Pod 与 Service 的 HTTP 正常且有 1 个 Ready endpoint,并记录为 readiness-restored。记录命令:python3 /opt/fixtures/k8s_mystery.py observe readiness-restored /root/k8s-mystery/05-readiness-fixed.json

让实际请求与 probe 表达相同的健康状态。本案例是路径拼写错误,并非忽略真实依赖故障的案例。

配送员留下 17 号便签后下班

查询正常的 Deployment revision,并保存到 Deployment annotation labhub.io/known-good-revision。随后把 parcel 的 command 替换为 ["sh","-ec","echo intentional-exit-17 >&2; exit 17"]。确认 CrashLoopBackOff、上一次退出码为 17、重启次数至少为 1,且上一份日志包含 intentional-exit-17,并记录为 crash。记录命令:python3 /opt/fixtures/k8s_mystery.py observe crash /root/k8s-mystery/06-crash.json

不要把 revision 记成 1 之类的固定数字。应在确认正常后立即保存,并通过 logs --previous 读取上一次退出的依据。

返回已确认的 revision,而不是依靠记忆

查看 rollout history 和保存的 labhub.io/known-good-revision,然后 rollout undo 到该 revision。在保留现有 Deployment 的情况下,确认正常 command、probe /、Service selector app=parcel、targetPort 8080 以及两侧 HTTP 都已恢复,并记录为 rollback。记录命令:python3 /opt/fixtures/k8s_mystery.py observe rollback /root/k8s-mystery/07-rollback.json

Deployment 回滚不会修复 Service。请同时确认历史编号和实际请求。

依据证据而非绿灯结束事件

保留前 7 个观测文件,并在 /root/k8s-mystery/report.json 中为 selector、readiness、crash 事件分别关联 cause、prevention、evidence。原因和预防代码见下方参考,evidence 使用发生故障时的文件名。当前 Service 也必须正常。最后点击完整评分,同时检查历史观测与当前恢复状态。

区分 selector、probe 路径与进程命令。如果当前 Service 失败,只修改 JSON 也无法通过。