必须用业务级健康检查脚本并确保PID 1回收子进程才能防住“活着却卡死”:./health.sh需验证HTTP、DB、Redis三通路,Dockerfile中配置HEALTHCHECK并设--start-period=60s,容器启动加--init启用tini。

单纯靠进程存活检测无法发现死锁,必须让健康检查真正进入业务逻辑层——用可执行脚本验证关键路径是否畅通,同时确保容器内 PID 1 进程能回收子进程,双管齐下才能防住“活着却卡死”的僵尸态。
用 shell 脚本做业务级探针,绕过假阳性
死锁常表现为 HTTP 接口无响应、数据库查询 hang 住、缓存命令超时。单靠 curl -f /health 或 ps aux | grep java 容易误判。应封装多步验证逻辑:
- 调用应用自暴露的
/healthz端点,确认 Web 层可响应 - 执行轻量 DB 查询(如
mysql -h db -e "SELECT 1"),验证连接池和 SQL 执行通路 - 向 Redis 发送
PING并检查返回PONG,排除缓存阻塞 - 任意一步失败就
exit 1;全部通过才exit 0
把这个逻辑写进 ./health.sh,再在 Dockerfile 中声明:HEALTHCHECK --interval=20s --timeout=5s --start-period=60s --retries=3 CMD ["./health.sh"]
防止僵尸进程堆积,守住 PID 1 的责任
Java 或 Node.js 应用若派生子进程(比如调用外部命令、启动守护线程),而主进程未调用 wait() 回收,子进程退出后会变成僵尸进程。当数量累积,PID 资源耗尽,新请求无法 fork,服务就“静默卡死”——此时健康检查仍可能成功,因为主进程还在。
- 容器启动时加
--init参数,让tini作为 PID 1,自动收割僵尸进程 - 应用代码中显式处理子进程生命周期,例如 Java 用
Process.waitFor()+destroyForcibly() - 避免在容器里运行
supervisord或自建 init,它们常忽略信号转发,导致 SIGCHLD 不被正确处理
配合启动宽限期,避免误杀慢初始化服务
Spring Boot、Quarkus 等框架冷启动常需 30~50 秒,依赖组件(如数据库连接池填充、Redis 初始化)也可能延迟就绪。若健康检查在应用还没真正 ready 就开始跑,会反复失败并标记为 unhealthy,甚至触发重启循环。
- 务必设置
--start-period=60s,这期间所有失败不计入重试计数 -
--timeout=5s防止探针自身卡住,拖慢整体探测节奏 -
--interval=20s比默认 30s 更及时,但不低于 10s,避免对服务造成额外压力
查看与验证健康状态是否生效
别只信 docker ps 显示的 Up 状态。运行中随时查真实健康信息:
-
docker inspect myapp --format='{{.State.Health.Status}}'看当前状态(healthy/unhealthy) -
docker inspect myapp --format='{{json .State.Health.Log}}'查最近几次检查的命令输出、退出码和时间戳 - 手动进容器执行
./health.sh,确认脚本能快速返回且逻辑无误
只要脚本能真实反映业务链路是否通畅,加上 PID 1 正确履职,死锁引发的“伪运行”状态就能被准确识别并交由编排系统处理。


















