Docker 容器启动了,不代表服务真的能用:Healthcheck 实战
2026/9/8 15:49:54 网站建设 项目流程

很多刚接触 Docker 的人都会看这个命令:

dockerps

只要看到:

STATUS Up 10 seconds

就觉得:

服务启动成功了。

但实际上:

容器启动成功,不代表容器里的服务已经可以正常使用。

这也是 Docker 项目里一个非常常见的问题。

最近我在维护开源项目 ShiyuAdmin:

https://github.com/Rodert/ShiyuAdmin

里面就用到了一个非常实用的 Docker 功能:

Healthcheck

今天只讲这一个知识点。


一、先看一个真实问题

假设我们的系统有三个服务:

Go Backend PostgreSQL Redis

通过 Docker Compose 一起启动:

dockercompose up-d

Docker 可能按照下面的顺序启动:

PostgreSQL 容器启动 Redis 容器启动 Go 容器启动

看起来没有问题。

但是这里有一个坑。

PostgreSQL 容器虽然已经启动了:

Container Running

但 PostgreSQL 自己可能还在:

初始化数据库 创建用户 加载数据文件 启动数据库服务

这个时候 Go 后端马上连接:

PostgreSQL:5432

就可能直接报:

connection refused

所以我们需要区分两个概念。

容器启动成功

和:

容器里的服务已经准备好

完全不是一回事。


二、Healthcheck 是干什么的?

Docker Healthcheck 就是用来检查:

容器里面的服务到底还能不能正常工作。

比如 PostgreSQL。

我们可以这样配置:

services:postgres:image:postgres:15healthcheck:test:["CMD-SHELL","pg_isready -U postgres"]interval:10stimeout:5sretries:5

Docker 会定期在容器里面执行:

pg_isready-Upostgres

如果 PostgreSQL 已经可以接受连接:

healthy

如果还没有准备好:

starting

如果连续多次检查失败:

unhealthy

三、Docker 容器其实有三种健康状态

配置 Healthcheck 以后,我们执行:

dockerps

可能看到:

Up 10 seconds (health: starting)

过一会:

Up 30 seconds (healthy)

如果服务异常:

Up 2 minutes (unhealthy)

所以容器状态不再只是:

Running

还多了一层:

starting healthy unhealthy

这就比单纯判断进程是否存在准确很多。


四、ShiyuAdmin 是怎么做的?

以 PostgreSQL 为例:

shiyu-postgres:image:postgres:15healthcheck:test:["CMD-SHELL","pg_isready -U shiyu -d shiyu_admin_scaffold"]interval:10stimeout:5sretries:5

这里:

interval:10s

表示:

每隔 10 秒检查一次
timeout:5s

表示:

一次检查超过 5 秒算失败
retries:5

表示:

连续失败 5 次以后认为 unhealthy

整体逻辑就是:

Docker ↓ 每 10 秒 ↓ 执行 pg_isready ↓ 成功? ┌───────┴───────┐ 是 否 ↓ ↓ healthy 继续重试

五、Redis 更简单

Redis 可以直接使用:

redis-cliping

正常情况下会返回:

PONG

因此 Docker Compose 可以写:

shiyu-redis:image:redis:7healthcheck:test:["CMD","redis-cli","ping"]interval:10stimeout:5sretries:5

这其实就是 Docker Healthcheck 最核心的思想:

找一个真正能代表服务正常工作的命令。


六、Web 服务怎么检查?

假设我们的 Go 后端提供:

/api/v1/system/health

这个接口正常时返回:

{"status":"ok"}

我们就可以直接访问这个接口判断服务状态。

比如:

healthcheck:test:["CMD-SHELL","wget --quiet --tries=1 --spider http://127.0.0.1/api/v1/system/health || exit 1"]interval:30stimeout:10sretries:3

这比判断:

Go 进程是否存在

靠谱得多。

因为下面这种情况非常常见:

Go 进程存在 数据库连接挂了 Redis 挂了 接口已经不能正常访问

从操作系统角度:

Process Running

但是从用户角度:

Service Down

Healthcheck 应该尽可能检查:

服务能力

而不仅仅是:

进程能力

七、Healthcheck 最大的搭档:depends_on

Healthcheck 单独使用已经很有价值。

但是在 Docker Compose 里,它还有一个经典搭配:

depends_on

比如:

shiyu-app:depends_on:shiyu-postgres:condition:service_healthyshiyu-redis:condition:service_healthy

什么意思?

Go 后端不会简单地等:

PostgreSQL 容器启动

而是等:

PostgreSQL 健康检查通过

Redis 同理。

于是启动顺序变成:

PostgreSQL 启动 ↓ Healthcheck ↓ PostgreSQL healthy Redis 启动 ↓ Healthcheck ↓ Redis healthy ↓ Go Backend 启动

这就可以减少很多:

connection refused

之类的问题。


八、没有 Healthcheck 会发生什么?

如果只写:

depends_on:-postgres-redis

它通常只能保证:

postgres 容器先启动 redis 容器先启动

但是不能保证:

PostgreSQL 已经可以连接 Redis 已经可以访问

比如:

00:00 PostgreSQL 容器启动 00:01 Go Backend 启动 00:02 Go 尝试连接数据库 00:03 PostgreSQL 还在初始化 00:03 connection refused 00:05 PostgreSQL 才真正 Ready

这就是为什么:

启动顺序

和:

服务就绪顺序

不是一个概念。


九、自己写一个健康检查接口

如果你是 Java、Go、Node.js 开发者,我非常建议自己的项目提供一个:

/health

接口。

最简单的 Go 示例:

packagemainimport("encoding/json""net/http")funchealthHandler(w http.ResponseWriter,r*http.Request){w.Header().Set("Content-Type","application/json")json.NewEncoder(w).Encode(map[string]string{"status":"ok",})}funcmain(){http.HandleFunc("/health",healthHandler)http.ListenAndServe(":8080",nil)}

然后 Docker:

healthcheck:test:["CMD-SHELL","wget --quiet --spider http://127.0.0.1:8080/health || exit 1"]interval:30stimeout:5sretries:3

这样就完成了一个最简单的服务健康检查。


十、再进一步:不要只返回 ok

真正生产环境里,可以检查:

数据库 Redis 磁盘 关键依赖

比如:

funchealthHandler(w http.ResponseWriter,r*http.Request){iferr:=db.Ping();err!=nil{w.WriteHeader(http.StatusServiceUnavailable)json.NewEncoder(w).Encode(map[string]string{"status":"error","database":"down",})return}json.NewEncoder(w).Encode(map[string]string{"status":"ok","database":"ok",})}

这样:

Go 进程活着

但是数据库挂了的时候:

/health

依然会返回失败。

Docker 就能检测出来:

unhealthy

十一、怎么查看具体健康检查结果?

先执行:

dockerps

如果看到:

unhealthy

可以进一步执行:

dockerinspect 容器名

或者:

dockerinspect\--format='{{json .State.Health}}'\容器名

里面可以看到最近几次 Healthcheck 的:

执行时间 退出码 输出结果

排查起来非常方便。


十二、一个完整例子

下面是一个简化版 Docker Compose:

services:postgres:image:postgres:15environment:POSTGRES_PASSWORD:123456healthcheck:test:["CMD-SHELL","pg_isready -U postgres"]interval:10stimeout:5sretries:5redis:image:redis:7healthcheck:test:["CMD","redis-cli","ping"]interval:10stimeout:5sretries:5app:image:my-app:latestdepends_on:postgres:condition:service_healthyredis:condition:service_healthyhealthcheck:test:["CMD-SHELL","wget --quiet --spider http://127.0.0.1:8080/health || exit 1"]interval:30stimeout:5sretries:3

整个启动逻辑非常清晰:

PostgreSQL ↓ healthy Redis ↓ healthy ↓ App ↓ /health ↓ healthy

这已经是一套非常标准的 Docker 服务启动方案。


十三、Healthcheck 解决的其实不是 Docker 问题

表面上看:

Healthcheck

是一个 Docker 配置。

但它解决的实际上是一个非常重要的分布式系统问题:

服务存在

不等于:

服务可用

以后学 Kubernetes,你还会看到类似的东西:

Liveness Probe Readiness Probe Startup Probe

其实思路都是一样的:

你不能只判断一个进程有没有启动。 你还要知道: 它能不能真正提供服务。

所以如果你的 Docker Compose 里现在只有:

image:ports:environment:volumes:

可以看看自己的项目是不是应该补一个:

healthcheck:

这个配置看起来只有几行。

但对于数据库、Redis、后端服务这些存在启动依赖的系统来说,往往非常有价值。

Docker 容器 Running,只能证明容器还活着。

Healthcheck Healthy,才更接近我们真正想知道的事情:服务能不能用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询