根本原因是docker healthcheck仅标记状态而不触发恢复动作,需依赖上层编排系统(如kubernetes的livenessprobe)或反向代理(如traefik)响应unhealthy状态来隔离流量或重启容器。

Healthcheck 脚本为什么总返回 unhealthy 却不重启容器
根本原因不是脚本写错了,而是 Docker 的 HEALTHCHECK 默认只做状态标记,不触发任何恢复动作——它不会自动 stop、restart 或 drain 流量。你看到 unhealthy 状态,只是 docker ps 里多了一列标记,容器照常接收请求,除非上层编排系统(如 Swarm、Kubernetes)明确监听并响应这个状态。
实操建议:
- 确认你的运行时是否真正消费 health status:Swarm 中 service update 可设
--health-start-period和--restart-condition on-failure,但on-unhealthy并不存在;K8s 则需用LivenessProbe配合failureThreshold触发 kill+recreate - 本地单机调试时,别依赖
docker ps状态判断服务可用性,要用真实请求验证(比如 curlhttp://localhost:8080/health) -
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3中的start-period必须覆盖应用冷启动耗时,否则初始阶段大量 false unhealthy
自定义 healthcheck 脚本如何检测“业务挂死”而非进程存活
用 kill -0 $PID 或 ps aux | grep myapp 只能判断进程没崩,但业务可能卡在死锁、GC 暂停、线程池耗尽或 HTTP handler 无限等待——这些场景下进程仍在,但已不响应新请求。
真正有效的检测必须走业务语义层:
在 Linux 上通过 Docker 运行 OpenClaw,并使用 Tailscale 实现远程访问。⚠️ 涉及 sudo、Docker、Tailscale和凭证挂载——请先查阅安全章节...
- HTTP 服务:脚本应
curl -f -s -o /dev/null --max-time 2 http://127.0.0.1:8080/health,且后端/health接口需主动检查数据库连接、缓存连通性、核心队列积压水位 - 长连接/非 HTTP 服务:改用
echo "PING" | nc -w 2 localhost 9000,并在服务端实现带超时的 echo handler,避免 TCP 连接建连成功但业务逻辑无响应 - 避免在脚本里调用 heavy operation(如读大文件、查慢 SQL),health check 频次高,会反向拖垮服务
Dockerfile 里 HEALTHCHECK CMD 的路径和权限坑
脚本放在镜像里却提示 executable file not found in $PATH,大概率是路径没写对或缺少执行权限。
正确做法:
- 脚本必须用绝对路径:
HEALTHCHECK CMD ["/usr/local/bin/check-health.sh"],不能写./check-health.sh(Docker 不在 WORKDIR 下执行) - 构建时显式加权限:
RUN chmod +x /usr/local/bin/check-health.sh,Alpine 镜像尤其容易漏这步 - 脚本第一行必须是正确的 shebang:
#!/bin/sh(不要用#!/usr/bin/env sh,某些精简镜像没env) - 如果脚本依赖外部命令(如
curl、jq),确保基础镜像已安装,或改用busybox wget等更轻量替代
如何让健康检查失败时真正隔离流量
单靠 Docker 自身机制做不到“自动隔离”,必须组合外围控制点:
- 在反向代理层(Nginx / Traefik)配置健康检查上游:Traefik 的
service.healthCheck.path会定期探测,自动摘除unhealthy实例;Nginx 需配合nginx-upsync-module或外部脚本轮询docker inspect输出 - Swarm 模式下,用 placement constraint + label 配合外部监控:给 unhealthy 容器打 label
health=unhealthy,再通过docker service update --constraint-rm ...动态调整调度策略 - 最直接的方式:在 healthcheck 脚本末尾加逻辑,一旦连续失败 N 次,就
kill -SIGUSR2 1(假设主进程支持优雅退出),或写个 sidecar 容器监听docker events --filter 'event=health_status'并调 API 更新服务注册
真正难的不是写脚本,是把容器健康状态映射到流量路由、实例生命周期、告警分级这三个不同平面的动作上——每个环节都得手动对齐,Docker 本身只管打标。










