云上健康检查与自动恢复实战:让你的系统学会自愈

去年一个客户,凌晨被拉起来处理故障。应用假死,进程还在,但不处理请求。监控显示CPU、内存都正常,负载均衡也把流量正常转发过来。但用户收到全是超时。
查了半天,发现是应用内部死锁,线程池全堵住了。进程活着,但什么事也干不了。负载均衡的健康检查用的是TCP连接检测,能连上就认为健康。进程没挂,端口开着,TCP检查通过了。但应用已经废了。
这是健康检查最常见的误区:能连上不代表能服务。
今天聊聊健康检查与自动恢复。不是那种“检查很重要”的废话,而是帮你理清楚:怎么检查才能真正反映服务健康?阈值怎么设?怎么让系统自己恢复,不用半夜爬起来?
01 TCP通不代表服务能用
很多负载均衡和K8s的默认健康检查是TCP探针。只检查端口能不能连上。能连上就标记为“健康”。
但实际场景中,很多故障和端口无关:
应用死锁,端口还开着
数据库连接池耗尽,但端口正常
内部依赖挂掉,服务对外端口还通
内存泄漏,GC频繁,端口仍可连接
TCP通,不代表服务能处理请求。
反常识观点:健康检查要检查的是“能不能正常工作”,不是“端口能不能连上”。
那家客户后来把TCP检查改成了HTTP检查,调用/health接口。接口内部检查:数据库连接、依赖服务、线程池状态。任何一个不正常,返回500。负载均衡收到500就把节点摘掉。
改了之后,假死节点自动摘流,不用半夜人工排查。
02 三种探针:存活、就绪、启动
K8s里有三种探针,分别解决不同阶段的问题。
存活探针(livenessProbe):判断容器是否还活着。挂了就重启。
检测失败,Kubelet杀掉容器,按重启策略重建
适合检测死锁、无限循环等让进程卡死的问题
不适合检测依赖故障(比如数据库连不上重启也没用)
就绪探针(readinessProbe):判断容器是否准备好接收流量。失败就从Service摘掉,不接收请求,但不重启。
用于启动预热、依赖加载等场景
启动慢的服务,readinessProbe初始延迟设大点
检测失败只摘流,不重启,避免反复重启治不好
启动探针(startupProbe):保护启动慢的服务。
启动阶段用startupProbe,成功后才切换成livenessProbe
适合启动需要几十秒甚至几分钟的服务
避免启动期间被livenessProbe误杀
03 阈值调优:太敏感和太迟钝都不行
健康检查的参数设置,直接影响系统稳定性。
参数说明:
initialDelaySeconds(初始延迟):容器启动后等多久才开始检查。设太短,服务还没起来就被判死亡。
periodSeconds(检查间隔):每隔几秒检查一次。设太短,增加负载;设太长,故障发现慢。
timeoutSeconds(超时):检查请求最大等待时间。设太短,慢启动服务被误判。
failureThreshold(失败阈值):连续失败几次,才认为不健康。设太小,临时抖动就踢掉;设太大,故障反应慢。
推荐配置(以K8s为例):
| 探针类型 | initialDelay | period | timeout | failureThreshold |
|---|---|---|---|---|
| liveness | 30-60秒 | 10秒 | 5秒 | 3次 |
| readiness | 0-10秒 | 5秒 | 5秒 | 3次 |
| startupProbe | 0秒 | 5秒 | 5秒 | 30次(按启动时长调) |
核心原则:readiness可以快一点,及时摘掉坏节点;liveness放松一点,避免误杀。
04 自动恢复不是只有重启
很多人理解的自动恢复就是“挂了重启”。但重启不是万能的。
重启能解决的问题:
内存泄漏(重启后内存释放)
死锁(重启打破僵局)
临时状态错乱
重启解决不了的问题:
配置错误(重启完还是错的)
依赖故障(下游挂了,重启没用)
数据损坏(重启也恢复不了)
自动恢复的几种手段:
摘流量:最温和。readiness失败,不重启,只是不接流量。适合依赖故障,等依赖恢复自动就好。
重启:中等。liveness失败,杀掉重建。适合进程挂掉、死锁。
替换实例:K8s自动重建,可能漂移到其他节点。
告警+人工:有些问题不适合自动恢复,比如数据损坏、配置错误。自动恢复搞不定,需要人工介入。
那家客户后来设计了分层恢复策略:
数据库连接失败 → 只摘流,不重启(重启没用)
应用死锁 → 重启(liveness检测失败)
连续重启3次 → 告警人工介入
05 常见坑与解法
坑一:探针调用依赖了其他服务
/health接口里调用了数据库、Redis、下游API。数据库重启时短暂不可用,/health返回500,所有Pod被摘流。依赖恢复后,readiness恢复,流量回来。但数据库重启期间服务全挂。
解法:/health只检查本进程。依赖的健康由调用方处理,不在健康检查里做。
坑二:initialDelaySeconds设太短
Java应用启动要30-60秒,initialDelaySeconds只设了10秒。livenessProbe开始检查,服务还没起来,连续失败3次,Pod被杀死,反复重启,永远起不来。
解法:用startupProbe保护启动阶段,或把initialDelaySeconds设大。
坑三:failureThreshold设太小,抖动就重启
网络闪断2秒,livenessProbe超时一次,failureThreshold=1,直接重启。生产环境网络不可能零抖动。
解法:failureThreshold设3次,容忍短暂抖动。
06 一个真实案例:readiness探针错误配置的惨案
某公司K8s集群,readinessProbe配置了initialDelaySeconds=5,failureThreshold=3。听起来正常。
问题出在应用启动就30秒。启动期间,readinessProbe开始检查,连续失败3次,Pod被标记NotReady,摘流。摘流后过了几秒,应用终于启动完成,readiness恢复。但被摘流的那段窗口,大量请求失败。
用户投诉,排查发现是readiness探针太早开始检查。
修复方案:
增加
startupProbe,设failureThreshold=30,periodSeconds=5,给150秒启动时间startupProbe成功后,readinessProbe和livenessProbe才开始工作
启动期间不再被误判
写在最后
健康检查和自动恢复,是系统自愈的基石。但用错了,可能比不用还糟。
那位客户的运维负责人后来总结:“健康检查不是点一下开关就行,要根据自己的服务特性配置。太严了,动不动就重启;太松了,挂了也不知道。”
你的健康检查,配对了吗?