Kubernetes 健康检查机制
探针类型 | 目的 | 失败后果 | 典型使用场景 |
---|---|---|---|
就绪探针(Readiness Probe) | 检测 Pod 是否准备好接收流量 | 从 Service 的 Endpoints 中移除该 Pod | 应用启动慢(如加载大文件)、依赖未就绪 |
存活探针 (Liveness Probe) | 检测 Pod 是否处于运行状态(防止死锁或僵死) | 重启容器(kubelet 触发) | 应用死锁、内存泄漏、内部线程阻塞 |
就绪探针 = 流量开关,存活探针 = 进程守护
这句话精准概括了 Kubernetes 中两种探针的核心职责,我们可以从行为机制和故障影响两个维度深入理解:
就绪探针 = 流量开关(Traffic Switch)
核心逻辑:控制流量进出
✅ 作用机制
- 探针通过 → 打开开关 → Pod 被加入 Service 的 Endpoints → 接收流量
- 探针失败 → 关闭开关 → Pod 从 Endpoints 移除 → 流量被切断
✅ 关键特性
- 非破坏性:不会重启容器,仅影响网络流量
- 可逆操作:故障恢复后自动重新接入流量
- 场景驱动:bash
# 典型触发场景(不影响进程) ▸ 应用启动初始化耗时较长(如加载 1GB 数据到内存) ▸ 临时依赖故障(数据库连接中断) ▸ 高负载时主动限流(自我保护)
存活探针 = 进程守护(Process Guardian)
核心逻辑:保障应用存活
✅ 作用机制
- 探针通过 → 守护状态正常 → 无操作
- 探针失败 → 判定进程异常 → kubelet 杀死容器并重启
✅ 关键特性
- 破坏性操作:触发容器重建(
kubectl describe pod
可见Restart Count
增加) - 终极手段:用于处理无法自我恢复的僵死状态
- 场景驱动:bash
# 典型触发场景(需强制重启) ▸ 进程死锁(线程阻塞导致无响应) ▸ 内存泄漏(进程占用 99% 内存无法释放) ▸ 文件描述符耗尽(无法处理新请求)
- 破坏性操作:触发容器重建(
场景化对比:一次数据库故障中的双探针协作
假设某订单服务 Pod 依赖 MySQL 数据库:
关键结论
探针 | 数据库故障时的行为 | 结果 |
---|---|---|
就绪探针 | 切断流量入口 | 业务层优雅降级 |
存活探针 | 未重启容器(内部线程仍健康) | 避免不必要的重启风暴 |
终极设计原则
就绪探针(开关)
yamlreadinessProbe: # 必须包含外部依赖检查(DB、缓存、API) httpGet: path: /readyz # 明确语义:专为流量开关设计 port: 8080 initialDelaySeconds: 5 # 启动缓冲期 periodSeconds: 5 # 高频检测(快速响应状态变化)
存活探针(守护)
yamllivenessProbe: # 仅检测进程内部状态(禁止检查DB等外部依赖!) httpGet: path: /livez # 明确语义:进程健康检查 port: 8080 initialDelaySeconds: 30 # 预留充足启动时间 periodSeconds: 10 # 避免过于频繁触发重启 failureThreshold: 3 # 防止瞬时故障误重启
💡 一句话记忆:
- 就绪探针是门口的保安:“服务暂时不可用,请勿入内”(流量屏蔽)
- 存活探针是急救医生:“病人心跳停止,立即抢救!”(容器重启)
最佳实践
以下是对 Kubernetes 就绪探针和存活探针最佳实践的深度解析,结合具体场景和底层原理说明:
1. 路径分离:专用端点设计
为什么必须分离?
yaml
# 反模式:混用同一端点
livenessProbe:
httpGet:
path: /healthz # 危险!
# 正解:语义化分离
readinessProbe:
httpGet:
path: /readyz # 就绪专属
livenessProbe:
httpGet:
path: /livez # 存活专属
实现方案对比
方案 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
物理分离 (/readyz /livez) | 逻辑清晰,K8s日志易排查 | 需开发两个端点 | 新建项目/核心服务 |
逻辑分离 (/healthz?type=readiness) | 代码改动小 | 故障时难以定位问题 | 存量系统改造 |
Header区分 (X-Probe-Type: readiness) | 无URL污染 | 依赖Header传递,调试复杂 | 特殊安全要求场景 |
终极建议: ✅ 物理分离是黄金标准 - 开发成本可被运维收益抵消
2. 超时设置:毫秒级精准控制
底层网络原理
- 网络延迟:跨节点通信可能增加 50-100ms 延迟
- 应用延迟:GC暂停可能导致线程阻塞数百毫秒
推荐配置公式
python
# 就绪探针超时 = 平均网络延迟 + 应用延迟 + 安全余量
readiness_timeout = (node_latency * 2) + app_max_delay + 100ms
# 存活探针超时 = 就绪超时 * 2 + 重试缓冲
liveness_timeout = readiness_timeout * 2 + 200ms
示例场景:
- 网络环境:跨可用区延迟 30ms
- 应用特性:Java服务GC暂停最大 200ms
- 计算结果:yaml
readinessProbe: timeoutSeconds: 1 # 30*2 + 200 + 100 ≈ 360ms → 取整1秒 periodSeconds: 5 # 高频检测 livenessProbe: timeoutSeconds: 3 # 360*2 + 200 ≈ 920ms → 取整3秒 periodSeconds: 10 # 避免频繁触发重启
3. 依赖隔离:存活探针的"防火墙"规则
为什么必须隔离?
假设存活探针检查数据库:
结果: ⚠️ CrashLoopBackOff 状态,服务完全瘫痪
存活探针的安全检查清单
允许检查 | 禁止检查 | 替代方案 |
---|---|---|
内存使用率 | 数据库连接状态 | 就绪探针检查DB |
线程池活跃线程数 | Redis/PG等外部依赖 | Sidecar健康检查 |
内部队列深度 | 第三方API响应 | 熔断机制 + 就绪探针 |
文件描述符数量 | 网络存储挂载状态 | initContainer验证存储 |
关键代码示例(Go语言):
go
// /livez 端点实现 - 仅检查内部状态
func LivezHandler(w http.ResponseWriter, r *http.Request) {
// 1. 检查内存
if memUsage > 90% {
w.WriteHeader(503)
return
}
// 2. 检查Goroutine泄漏
if runtime.NumGoroutine() > 1000 {
w.WriteHeader(503)
return
}
// 3. 通过检查
w.WriteHeader(200)
}
4. 雪崩故障防御机制
典型雪崩场景复现
bash
时间线:
T0: 数据库故障
T1: 就绪探针失败 → Pod移出Endpoints ✅
T2: 存活探针检测DB → 失败 ❌
T3: kubelet重启容器
T4: 新容器启动中 → 存活探针立即检测 → 失败(DB未恢复)
T5: 进入CrashLoopBackOff状态 → 所有Pod不可用 💥
这个序列图清晰地展示了雪崩故障的完整过程:
- 初始故障 (T0):数据库发生故障
- 正确响应 (T1):就绪探针检测到DB故障,Pod被移出服务端点
- 设计缺陷 (T2):存活探针错误地检查外部DB
- 重启风暴 (T3-T4):容器不断重启并立即检测
- 崩溃循环 (T5):进入CrashLoopBackOff状态
- 服务瘫痪:所有Pod最终不可用
关键问题点突出显示:
- ✅ 就绪探针正确行为:隔离故障Pod
- ❌ 存活探针设计错误:检查外部依赖
- 💥 最终结果:级联故障导致服务完全瘫痪
这种场景完美解释了为什么存活探针必须只检查内部状态 - 任何外部依赖检查都会在依赖服务故障时触发灾难性的重启循环。
三维防御策略
存活探针避障
yamllivenessProbe: initialDelaySeconds: 30 # 跳过启动期 failureThreshold: 5 # 连续5次失败才重启
就绪探针熔断
go// /readyz 端点增加熔断器 if dbConnFailCount > 10 { // 主动返回503,避免持续重试冲击DB w.WriteHeader(503) }
Sidecar 守护
yaml# 使用辅助容器隔离检测 - name: probe-agent image: probe-agent:1.0 command: ["/agent", "--liveness=/proc/self/health"]
终极配置模板
yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: main-app
# 就绪探针 - 流量开关
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5 # 等待基础启动
periodSeconds: 2 # 高频检测
timeoutSeconds: 1 # 快速响应
successThreshold: 1
failureThreshold: 3 # 连续3次失败才剔除
# 存活探针 - 进程守护
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 30 # 预留充足启动时间
periodSeconds: 10 # 避免频繁检测
timeoutSeconds: 3 # 容忍GC暂停
failureThreshold: 5 # 防止瞬时故障误重启
💡 运维口诀: 就绪勤(高频),存活稳(低频); 路径分,依赖离; 超时短就绪,阈值高存活; 混用酿雪崩,分离保平安。