用 HEALTHCHECK 给 Docker 容器加健康检查
前几篇分别讲了用 nginx 托管静态页时的端口映射与文件挂载、练习后的清理、-p 左侧绑定地址、镜像标签与 digest、volume 与 bind mount、tmpfs,以及自定义网络里用容器名互访。容器能启动、docker ps 里也能看到 Up,只说明主进程还在;本篇只复查另一件事:怎样用 HEALTHCHECK 探测容器里的服务是不是真的能响应。下列命令均为本机说明示例,请按自己的环境判断是否执行;示例输出为示意,字段以你本机 docker 实际打印为准。
一个常见场景是:应用进程还在,但配置写错、监听地址不对,或依赖尚未就绪,外面已经开始往里面发请求。没有健康检查时,docker ps 的 STATUS 只显示 Up 和运行时长,看不出服务是否可用。加上 HEALTHCHECK 之后,Docker 会在容器内周期执行一条探测命令,并把结果记成 starting、healthy 或 unhealthy。这条命令跑在容器自己的网络命名空间里,所以探测地址应写容器内的监听地址(通常是 127.0.0.1 加上容器内端口),而不是主机上 -p 映射出来的端口,也不要写成旁边另一个容器的名字——除非你明确是在检查对端,那就已经超出「检查自己是否就绪」的范围。
1. 启动时加上健康检查,先看 STATUS
仍用官方 nginx:stable-alpine 做被探测方(该镜像里有 BusyBox 的 wget)。启动时通过 docker run 的运行时参数加上探测,不必先写 Dockerfile:
docker run -d --name demo-web \
--health-cmd='wget -qO- http://127.0.0.1/ >/dev/null || exit 1' \
--health-interval=5s \
--health-timeout=3s \
--health-retries=3 \
--health-start-period=5s \
nginx:stable-alpine
这里没有 -p,因为探测发生在容器内部,访问的是 nginx 默认监听的 80 端口。--health-cmd 退出码为 0 视为成功,非 0 视为失败(文档里还保留了退出码 2,实际编写时按成功/失败处理即可)。--health-interval 是两次探测的间隔,--health-timeout 是单次探测最多等多久,--health-retries 是连续失败多少次才标为 unhealthy。--health-start-period 给启动留一段宽限期:在这段时间内的失败一般不计入重试次数,避免进程还在初始化就被判为不健康。若不写这些参数,引擎侧常见默认值是间隔 30 秒、超时 30 秒、重试 3 次、启动宽限期 0 秒,以你本机 docker run --help 和实际行为为准。练习里把间隔写成 5 秒,只为尽快看到状态变化;日常服务通常会用更长的间隔,以免探测本身成为负担。
查看状态:
docker ps --filter name=demo-web
STATUS 栏在刚启动时常见类似 Up ... (health: starting) 的写法(示例),若干次探测成功后会变成 Up ... (healthy)(示例)。具体何时切换,取决于 --health-start-period、--health-interval 和探测是否成功,以你本机实际输出为准。宽限期结束后,连续失败次数达到 --health-retries 才会标为 unhealthy;期间只要有一次探测成功,状态就会回到 healthy,连续失败计数也会清零。docker ps --filter name=demo-web 是按名称子串过滤的,若本机还有名字里带 demo-web 的其他容器,也会出现在列表里,对照时请确认 NAME 列就是你这次创建的那一只。若启动时不加任何 --health-* 参数,同一镜像通常只显示 Up ...,没有括号里的健康状态——不是服务一定有问题,只是没有配置健康检查。
2. 用 inspect 看探测记录,再对比一次失败情形
比 docker ps 更完整的是 inspect:
docker inspect -f '{{.State.Health.Status}}' demo-web
docker inspect -f '{{json .State.Health}}' demo-web
第一行应打印 starting、healthy 或 unhealthy 之一。第二行是 JSON,常见字段包括 Status、FailingStreak(连续失败次数)和 Log(最近几次探测的起止时间、退出码和输出)。Log 的内容和条数以本机为准,不要把某次练习里看到的时间戳或退出码抄成固定结论。
若要对照「进程仍在、探测失败」的情形,可以再开一只容器,把探测目标改成一个不存在的路径(nginx 对这类请求会返回 4xx,BusyBox wget 通常会以非 0 退出;若你本机的 wget 行为不同,也可以把 --health-cmd 改成 exit 1 来演示):
docker run -d --name demo-unhealthy \
--health-cmd='wget -qO- http://127.0.0.1/this-path-should-not-exist >/dev/null || exit 1' \
--health-interval=5s \
--health-timeout=3s \
--health-retries=2 \
--health-start-period=1s \
nginx:stable-alpine
等过了启动宽限期,并且连续失败次数达到 --health-retries 之后,再看:
docker ps --filter name=demo-unhealthy
docker inspect -f '{{.State.Health.Status}}' demo-unhealthy
此时 STATUS 里应出现 unhealthy(示例),但容器主进程往往仍是 Up——这正是健康检查要揭示的差别:不健康不等于已经退出。看完后不要对这只容器做业务访问假设,它只用于对照状态。
已经在跑、当初没加健康检查的容器,不能靠 docker update 补上 --health-cmd(docker update 不支持这类参数)。需要停掉并删除后,用带健康检查的参数重新 docker run,或在镜像的 Dockerfile 里写好再构建。
3. 写进 Dockerfile,以及和重启策略的差别
若希望每次用该镜像启动都带上同一种探测,把指令写进 Dockerfile:
FROM nginx:stable-alpine
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1
HEALTHCHECK 在一个 Dockerfile 里只保留最后一条;写成 HEALTHCHECK NONE 可禁用基础镜像自带的健康检查。docker run 的 --health-cmd 等参数会覆盖镜像里的设置;若只想临时关掉,可用 --no-healthcheck。较新的 Docker 还提供启动期内单独的探测间隔(Dockerfile 的 --start-interval、运行时的 --health-start-interval);本机 docker run --help 里若没有该选项,不必强行加上。
需要分清健康检查和 --restart:no、on-failure、always、unless-stopped 管的是容器进程退出之后要不要再拉起来,并不会因为状态变成 unhealthy 就自动重启。本机 Docker Engine 默认不会仅因不健康而销毁或替换容器。编排工具(例如 Compose 里等依赖方 service_healthy 再启动,或 Swarm / Kubernetes 的健康与就绪探针)才可能按自己的规则处理不健康实例。如果使用 Compose,服务里的 healthcheck 与这里是同一套概念,字段名写成 test、interval、timeout、retries、start_period。依赖方可以写 depends_on 并带 condition: service_healthy,让被依赖的服务先变为 healthy 再启动;这是编排层的等待策略,同样不会在 Engine 里单凭不健康就自动重启。不要把「已经配置 HEALTHCHECK」理解成「服务挂了会自动恢复」。
探测命令必须存在于镜像里。nginx:stable-alpine 有 wget,但不一定有 curl;换了更精简的镜像,可能两者都没有,这时要在镜像里安装探测工具,或改用应用自带的探测方式(例如访问本机一个健康检查接口)。命令应尽量轻量、不要有交互,并且尽量只检查「本容器服务是否可接受请求」,避免把一次缓慢的外部依赖失败误判成自己不健康——那会放大故障面。
4. 常见误区
- 看到 Up 就以为服务可用:没有 HEALTHCHECK 时,Up 只表示主进程还在。
- 探测地址写成主机映射端口,或把
localhost理解成宿主机:HEALTHCHECK 在容器内执行,127.0.0.1指容器自己;应使用容器内监听端口(本例是 80),而不是-p左侧的主机端口。 - 镜像里没有探测命令:
curl/wget不存在会导致探测一直失败,容器会被标成unhealthy,其实是检查命令写错了。 - 把不健康和已退出混为一谈:
unhealthy时容器通常仍在运行;--restart也不会仅因此触发。 - 启动阶段间隔太短、重试太少:应用还没听端口就被连续判失败。需要时加大
--health-start-period,或等初始化完成后再标记就绪。 - 用 ping 或只检查进程,作为 HTTP 服务的健康标准:ICMP 通、进程在,都不等于应用端口能正确响应。
- 在已经运行的容器上指望改参数立刻生效:健康检查配置属于创建时的设定,改完需要重建容器(或改 Dockerfile 后重新构建再运行)。
练习看完后,停掉并删除演示容器(不要删别人的容器,也不必为此删除镜像):
docker container stop demo-web demo-unhealthy
docker container rm demo-web demo-unhealthy
若某只容器创建失败,或你没用 demo-unhealthy 这个名字,只清理实际存在的那一只即可。可用 docker ps -a --filter name=demo-web 和同样的过滤条件确认练习容器已经不在列表里。
小结
docker ps 里的 Up 只说明主进程还在。需要知道容器内的服务能否响应时,用 HEALTHCHECK(或 docker run 的 --health-cmd 一组参数)在容器内周期探测,再通过 STATUS 和 docker inspect 的 .State.Health 查看 starting / healthy / unhealthy。探测要写容器内地址和端口,命令必须在镜像里存在;--restart 管的是进程退出后的重启,不会单凭不健康状态就拉起容器。端口映射、数据挂载和自定义网络解决的是「怎么进、数据放哪、名字怎么找」;健康检查回答的是「进去之后,服务现在能不能用」。
- 点赞
- 收藏
- 关注作者
评论(0)