别再用 sleep 10 了:用 Docker Compose healthcheck 解决“容器起来了但服务没就绪”
很多同学在本地跑 docker compose up 时都会遇到一个现象:容器状态已经是 Up,应用却连不上。翻一遍日志才发现,数据库还在初始化、后端还在跑迁移。于是有人在启动脚本里塞一串 sleep 10,似乎能用,但它解决的是「等多久」,不是「等什么」。
真正的解法是让编排器知道服务到底有没有就绪,这就是 healthcheck。
一、healthcheck 到底在做什么
它是一条在容器内部周期性执行的命令。退出码 0 表示健康,1 表示不健康。Docker 根据最近若干次结果,给容器打上 starting 或 healthy 标记。注意是容器内部执行,所以命令必须存在于镜像里,你要保证 curl、wget 或对应客户端已经装好——不少 slim 镜像里没有 curl,这是最常见的坑。
services:
api:
image: my-api:1.0
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/health || exit 1"]
interval: 10s
timeout: 3s
retries: 5
start_period: 30s
几个参数值得单独说:interval 是探测间隔;retries 表示连续失败几次才判定 unhealthy;start_period 经常被忽略,它给了一段「免责期」,期间失败只算 starting,不计入重试次数,正好用来覆盖迁移、预热这类慢启动场景。没有它,慢启动服务很容易在起来之前就被判死。
二、depends_on 也要跟着升级
很多人以为写了 depends_on 就等于等服务就绪,其实它默认只保证「容器被创建」。要依赖健康状态,得用长语法:
web:
image: nginx:alpine
depends_on:
api:
condition: service_healthy
这样 Compose 会先让 api 达到 healthy,再启动 web。数据库依赖——Postgres 用 pg_isready,MySQL 用 mysqladmin ping,Redis 用 redis-cli ping——同理:
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
start_period: 20s
三、/health 接口本身别写成摆设
最常见的错误是把健康检查写成只返回一个 200 的空接口,那样它和 TCP 探活没区别。一个合格的实现至少要碰一下真实依赖:查一条 SELECT 1,或者读一次磁盘上的关键目录。代价很低,但能在连接池阻塞、磁盘写满这类「进程还活着,服务已经废了」的故障里提前暴露问题。反过来也要注意别在探测里做重操作——健康检查每秒可能有多次,别把它变成压力源。
四、排查 unhealthy
容器被打上 unhealthy 后,用 docker inspect --format '{{json .State.Health}}' <容器名> 看最后几条输出的 Log 字段,命令的报错内容就写在里面。多数情况不外乎两类:命令不存在,或者容器里压根监听在别的端口。
小结
把 sleep 10 换成 healthcheck,本质上是从「猜一个时间」变成「声明一个条件」。改动不大,一个下午就能把整套 Compose 文件改完,但它消除的是一整类只在 CI 上偶发、本地却复现不了的玄学失败,值得。
- 点赞
- 收藏
- 关注作者
评论(0)