LabHub
学习 学习路径 课程

CKAD — Kubernetes 应用开发者

「Ready」和「真的能用」是两个命题

在 LabHub 中继续学习

一句话总结

Probe 是把应用状态翻译为 Kubernetes 能理解的语言的接口。liveness 表示“已经死了,请重启”,readiness 表示“现在不要发送流量”,startup 表示“仍在启动,请等待”;三种结果会触发完全不同的行为。

概念图: 翻译为 Kubernetes 能理解的语言的接口 · 重启会改善的状态 · 等待会改善的状态 · 重启

为什么需要它

容器进程存活不等于服务正常。JVM 死锁时进程和 PID 都还存在;正在填充 cache 的应用也活着且端口开放,却可能让所有请求报错。Kubernetes 无法窥视容器内部,因此应用需要一个报告自身状态的窗口。

但“状态”不止一种。重启会改善的状态(死锁、connection pool 耗尽)与等待会改善的状态(cache warm-up、依赖服务短暂故障)需要相反处置。对后者执行重启,它会永远无法就绪,因此 probe 分成两种。

第三种后来加入的原因也很明确。legacy 应用可能需要 5 分钟启动;若把 liveness 的 initialDelaySeconds 设为 300 秒,正常运行中发现故障也会慢 5 分钟。startup probe 将两者分开——启动期间宽松,启动后严格。

它如何运作

probe 失败后 成功前
livenessProbe 重启容器 持续检查
readinessProbe 从 Service endpoint 移除(不重启) 不接收流量
startupProbe 重启容器 完全不启动 liveness/readiness

三者检查方式相同:httpGet(200~399 成功)、tcpSocket(连接成功即成功)、exec(退出码 0 成功),还有用于 gRPC health check 的 grpc

timing 字段必须经过计算。

最坏检测延迟 ≈ initialDelaySeconds + periodSeconds × failureThreshold。重启过于频繁就提高 failureThreshold;发现故障过慢就缩短 periodSeconds。startup probe 的预算是 periodSeconds × failureThreshold,必须大于应用最长启动时间。

Probe 与终止时间线——startup probe 的预算为 periodSeconds × failureThreshold,在此期间不启动 liveness、readiness。liveness 的 period 为 10 秒、threshold 为 3 时,连续失败 3 次后重启。终止时先发送 SIGTERM,等待 terminationGracePeriodSeconds(默认 30 秒)后发送 SIGKILL;preStop 的执行时间也消耗这段预算

终止侧也对称设计。删除 Pod 时,kubelet 发送 SIGTERM,等待 terminationGracePeriodSeconds(默认 30)后发送 SIGKILL。Pod 同时从 endpoint 移除,但两件事异步进行,已路由的请求可能短暂继续进入。因此常在 preStop hook 中短暂 sleep,等待 endpoint 传播。不要忘记 preStop 也消耗 grace period。

容器死亡原因保存在 terminationMessagePath(默认 /dev/termination-log)。设置 terminationMessagePolicy: FallbackToLogsOnError 后,该文件为空时会改用容器日志末尾。这就是 kubectl describe pod 的 Last State 中显示的值。

PodDisruptionBudget 只适用于自愿中断(node drain、升级),无法阻止节点突然死亡等非自愿中断。给 3 个副本设置 minAvailable: 2,drain 每次只能进行一个。

在实际工作中会遇到的情况

家庭实验室三次确认了这个教训。安装 KubeVirt 时所有组件都是 AllComponentsReady,VM 却无法启动。拆开 virt-launcher Pod 后发现,缺少挂载 init container 所需 binary 的 volume。**状态字段 Ready 与功能实际可用是不同命题。**所以安装 GPU Operator 后,也没有停在“32 个 Pod 全部 Running”,而是实际在 Pod 内确认 nvidia-smi 能看到 GPU。结果为 NVIDIA GeForce RTX 5090, 32607 MiB, 570.195.03,到这里验证才结束。

日志同样遵循该原则。故障中真正需要的不是阅读,而是过滤与聚合,所以 message 字符串保持常量,所有变化值都提取为字段。

// 나쁨 — 같은 사건을 세려면 정규식이 필요하다
logger.info(`user ${userId} checkout failed after ${ms}ms`)

// 좋음 — 메시지는 상수, 값은 필드
logger.error({ event: 'checkout.failed', user_id: userId, duration_ms: ms }, 'checkout failed')

日志级别判断标准固定为一条,争论就会结束:ERROR 表示我们的系统没有履行自身责任。400 段响应(错误请求、过期 token、无权限)是系统正常工作的结果,不属于 ERROR。把它们记录为 ERROR,正常流量会淹没真正缺陷。

调查顺序也应固定为:metric → trace → log。metric 回答“从何时开始、什么、多少”,trace 回答“哪个服务的哪个区段”,log 回答“其中具体用了什么值、走了哪个分支”。用 trace_id 过滤后,需要阅读的内容会缩减到几十行。

后续实验要做什么

ckad-obs namespace 中分别创建 httpGet、tcpSocket、exec probe,计算并填写 startup probe 启动预算,为 Deployment 加 probe 控制 readiness,再设置 PodDisruptionBudget。

本实验环境无法实际查询日志,也无法运行 kubectl execkubectl logs --previous-c--since--tailkubectl exec -it POD -c CONTAINER -- shkubectl debug 等调查命令必须掌握,但这里无法亲手运行,所以通过测验学习。考试会直接出现这些命令,请准确记住语法。