「Ready」和「真的能用」是两个命题
一句话总结
Probe 是把应用状态翻译为 Kubernetes 能理解的语言的接口。liveness 表示“已经死了,请重启”,readiness 表示“现在不要发送流量”,startup 表示“仍在启动,请等待”;三种结果会触发完全不同的行为。
为什么需要它
容器进程存活不等于服务正常。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——容器启动后到首次检查的等待时间(默认 0)periodSeconds——检查周期(默认 10)timeoutSeconds——单次检查时限(默认 1)failureThreshold——连续失败多少次后采取动作(默认 3)successThreshold——连续成功多少次才视为恢复(liveness/startup 固定为 1)
最坏检测延迟 ≈ initialDelaySeconds + periodSeconds × failureThreshold。重启过于频繁就提高 failureThreshold;发现故障过慢就缩短 periodSeconds。startup probe 的预算是 periodSeconds × failureThreshold,必须大于应用最长启动时间。
终止侧也对称设计。删除 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 exec。kubectl logs --previous、-c、--since、--tail、kubectl exec -it POD -c CONTAINER -- sh、kubectl debug 等调查命令必须掌握,但这里无法亲手运行,所以通过测验学习。考试会直接出现这些命令,请准确记住语法。