共计 6645 个字符,预计需要花费 17 分钟才能阅读完成。
文章目录 [显示]
你有没有遇到过这样的情况:服务器上的容器看起来好好的,docker ps 显示 Up 了好几天,但实际上里面的应用早就挂了?用户打电话过来说 " 你们网站怎么打不开 ",你一看容器状态还是 running,一脸懵逼。
这就好比你请了个保安看门,保安人站在那里,但其实已经睡着了。容器的进程还在,但服务已经不响应了。这种 " 假活着 " 的状态,是生产环境中最让人头疼的问题之一。
今天我们就来解决这个问题——让 Docker 学会给容器 " 量体温 ",发现容器 " 生病 " 了就自动帮它 " 治疗 "。
本文你将学到
- HEALTHCHECK 指令的工作原理和配置方法
- 如何编写有效的健康检查脚本
- restart policy 的四种策略及选择依据
- depends_on 配合 condition 实现启动顺序控制
- 生产环境中健康检查的最佳实践
阅读时间 : 约 10 分钟
实操时间 : 约 20 分钟
难度等级 : 中级
一、理解健康检查:从 " 活着 " 到 " 健康 "
容器状态的三层含义
很多人以为容器 running 就万事大吉了,其实容器的状态至少有三层含义:
+--------------------------------------------------+
| 第一层:进程存活(PID 1 还在跑)|
| └── docker ps 显示 Up |
| |
| 第二层:端口可达(网络层面能连上)|
| └── telnet/curl 能连上端口 |
| |
| 第三层:业务健康(服务真的能正常处理请求)|
| └── 健康检查接口返回 200,数据库连接正常 |
+--------------------------------------------------+
Docker 默认只关心第一层。而 HEALTHCHECK 的作用,就是让 Docker 能感知到第三层的状态。
打个比方:你去医院体检,护士先看你人来了没有(进程存活),然后量了个血压(端口可达),最后抽血化验(业务健康)。HEALTHCHECK 就是那个抽血化验的环节。
二、HEALTHCHECK 指令详解
基本语法
在 Dockerfile 中添加 HEALTHCHECK 指令:
HEALTHCHECK [OPTIONS] CMD command
支持的选项:
| 参数 | 默认值 | 说明 |
|---|---|---|
| –interval | 30s | 每次检查的间隔时间 |
| –timeout | 30s | 单次检查的超时时间 |
| –start-period | 0s | 容器启动后的宽限期 |
| –start-interval | 5s | 宽限期内的检查间隔 |
| –retries | 3 | 连续失败多少次判定为不健康 |
返回值约定
健康检查命令的退出码决定了容器的健康状态:
- 0 – healthy(健康)
- 1 – unhealthy(不健康)
- 2 – reserved(保留,暂不使用)
实操:给 Nginx 加上健康检查
我们来写一个带健康检查的 Nginx 镜像。
创建项目目录:
mkdir -p ~/docker-health-demo && cd ~/docker-health-demo
编写 Dockerfile:
FROM nginx:alpine
# 安装 curl 用于健康检查
RUN apk add --no-cache curl
# 健康检查:每 10 秒检查一次,超时 3 秒,启动宽限 5 秒,连续 3 次失败则不健康
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
EXPOSE 80
构建并运行:
docker build -t nginx-health .
docker run -d --name web-health nginx-health
观察容器状态变化:
# 刚启动时,状态是 health: starting
docker ps
# CONTAINER ID IMAGE STATUS NAMES
# a1b2c3d4e5f6 nginx-health Up 3 seconds (health: starting) web-health
# 等待约 15 秒后,状态变为 healthy
docker ps
# CONTAINER ID IMAGE STATUS NAMES
# a1b2c3d4e5f6 nginx-health Up 18 seconds (healthy) web-health
查看健康检查的详细记录:
docker inspect --format='{{json .State.Health}}' web-health | python3 -m json.tool
预期输出类似:
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2024-01-15T10:00:05.123Z",
"End": "2024-01-15T10:00:05.234Z",
"ExitCode": 0,
"Output": "..."
}
]
}
模拟故障
现在我们来模拟一下应用 " 假死 " 的场景:
# 进入容器,停掉 nginx(但容器不会退出,因为还有 sleep 等方式维持 PID 1)docker exec web-health nginx -s stop
# 等待 30 秒左右(interval * retries),再看状态
docker ps
# CONTAINER ID IMAGE STATUS NAMES
# a1b2c3d4e5f6 nginx-health Up 1 minute (unhealthy) web-health
看到了吧?容器还在 running,但是状态已经变成了 unhealthy。这就是健康检查的价值所在。
清理实验环境:
docker rm -f web-health
三、Restart Policy:让容器自动 " 复活 "
光发现问题还不够,我们还需要自动恢复。这就是 restart policy 的用武之地。
四种重启策略
+------------------+------------------------------------------+
| 策略 | 行为 |
+------------------+------------------------------------------+
| no | 默认值,不自动重启 |
| on-failure[:N] | 非正常退出时重启,可选最多重启 N 次 |
| always | 总是重启(除非手动 docker stop)|
| unless-stopped | 类似 always,但 docker stop 后重启不恢复 |
+------------------+------------------------------------------+
这四种策略怎么选?我给你一个简单的决策思路:
- 开发环境 :用
no,容器挂了你正好去查原因 - 有状态服务 (数据库等):用
on-failure,避免数据损坏后反复重启 - 无状态服务 (Web 应用、API):用
always或unless-stopped - 生产环境推荐 :
unless-stopped,因为它在 Docker daemon 重启后也能恢复容器
实操:健康检查 + 自动重启
在 docker run 时配合使用:
docker run -d \
--name web-auto \
--restart unless-stopped \
nginx-health
但这里有个关键问题:restart policy 只在容器退出时生效,HEALTHCHECK 标记为 unhealthy 并不会自动重启容器。
那怎么办?有两种方案。
方案一 :在健康检查失败时让容器退出
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || kill 1
不太推荐这种方式,因为直接 kill PID 1 比较暴力。
方案二 :使用 Docker Compose + autoheal(推荐)
这个我们在 Compose 部分会详细讲。先来看 Compose 中的健康检查配置。
四、Docker Compose 中的健康检查
基本配置
version: "3.8"
services:
web:
image: nginx:alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
restart: unless-stopped
depends_on + condition:优雅的启动顺序
这是个非常实用的功能。想想这个场景:你的 Web 应用依赖 MySQL,但 MySQL 需要几十秒才能完全启动。如果 Web 应用先起来,连不上数据库直接就崩了。
以前大家用各种 wait-for-it.sh 脚本来解决这个问题,现在 Compose 原生支持了:
version: "3.8"
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret123
MYSQL_DATABASE: myapp
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-psecret123"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s
restart: unless-stopped
web:
image: nginx:alpine
depends_on:
db:
condition: service_healthy
restart: unless-stopped
这段配置的意思是:web 服务要等到 db 的健康检查通过之后才会启动。
完整实操:三层应用健康检查
让我们搭建一个更贴近真实场景的例子。创建 docker-compose.yml:
version: "3.8"
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: demo123
MYSQL_DATABASE: app
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-pdemo123"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s
volumes:
- db-data:/var/lib/mysql
restart: unless-stopped
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 5s
restart: unless-stopped
web:
image: nginx:alpine
ports:
- "8080:80"
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s
restart: unless-stopped
volumes:
db-data:
启动并观察:
docker compose up -d
# 查看启动状态,注意观察启动顺序
docker compose ps
预期输出:
NAME STATUS PORTS
docker-health-db-1 Up 35 seconds (healthy) 3306/tcp
docker-health-redis-1 Up 35 seconds (healthy) 6379/tcp
docker-health-web-1 Up 5 seconds (healthy) 0.0.0.0:8080->80/tcp
你会发现 db 和 redis 先启动,等它们 healthy 之后,web 才开始启动。这就是 condition: service_healthy 的效果。
清理环境:
docker compose down -v
五、生产环境最佳实践
健康检查脚本设计原则
1. 检查要有意义
不要只检查端口是否开放,要检查业务是否真的可用:
# 不够好:只检查端口
HEALTHCHECK CMD curl -f http://localhost:8080/ || exit 1
# 更好:检查专用健康接口
HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
2. 健康接口要轻量
健康检查接口不应该执行复杂的业务逻辑,建议检查:
- 应用进程是否正常
- 关键依赖是否可连接(数据库、缓存)
- 磁盘空间是否充足
3. 合理设置超时和间隔
应用启动时间 → 决定 start_period
请求响应时间 → 决定 timeout(通常设为 P99 延迟的 2-3 倍)故障发现速度 → 决定 interval 和 retries
一个经验公式: 故障发现时间 = interval x retries。如果你希望 30 秒内发现故障,可以设置 interval=10s, retries=3。
4. 不要在健康检查中做副作用操作
# 错误:健康检查触发了写操作
HEALTHCHECK CMD curl -X POST http://localhost/api/heartbeat || exit 1
# 正确:只做只读检查
HEALTHCHECK CMD curl -f http://localhost/health || exit 1
常见的健康检查命令
| 服务 | 健康检查命令 |
|---|---|
| Nginx | curl -f http://localhost/ |
| MySQL | mysqladmin ping -h localhost |
| PostgreSQL | pg_isready -U postgres |
| Redis | redis-cli ping |
| MongoDB | mongosh --eval "db.runCommand('ping')" |
| Elasticsearch | curl -f http://localhost:9200/_cluster/health |
| RabbitMQ | rabbitmq-diagnostics -q check_running |
六、常见问题 Q&A
Q1:HEALTHCHECK 标记 unhealthy 后,Docker 会自动重启容器吗?
不会。Docker 原生的 restart policy 只在容器退出时触发。HEALTHCHECK 只是标记状态,不会导致容器退出。要实现 unhealthy 自动重启,可以用 Docker Swarm 的服务编排,或者使用第三方工具如 autoheal、watchtower 等。在 Swarm 模式下,unhealthy 的容器会被自动替换。
Q2:start_period 和 start_interval 有什么区别?
start_period 是宽限期,在这段时间内健康检查失败不会计入 retries。这是为了给应用足够的启动时间。start_interval(Docker Engine 25.0+ 支持)是宽限期内检查的间隔,默认 5 秒。宽限期结束后,就使用 interval 指定的间隔。
比如一个 Java 应用启动需要 60 秒,你可以设置 start_period=60s,这样前 60 秒的检查失败都不会导致容器被标记为 unhealthy。
Q3:多个服务互相依赖怎么处理?
如果 A 依赖 B,B 依赖 C,用 depends_on 的链式配置就行。但如果 A 和 B 互相依赖(循环依赖),那就说明架构有问题,需要解耦。通常的做法是引入消息队列或者让服务具备重试能力,而不是强依赖启动顺序。
小结
今天我们学了 Docker 健康检查体系的三大核心组件:
- HEALTHCHECK 指令 :让 Docker 能感知容器内应用的真实健康状态,从 " 活着 " 升级到 " 健康 "
- Restart Policy:四种重启策略,根据服务特性选择合适的策略
- depends_on + condition:在 Compose 中实现优雅的启动顺序控制
记住一个核心思想: 不要信任容器的运行状态,要验证应用的健康状态 。就像那句老话说的," 信任,但要验证 "。
健康检查看起来是个小功能,但它是构建高可用系统的基石。没有健康检查的容器化部署,就像没有体检的员工管理——出了问题才知道,已经晚了。
明日预告
Day 20 我们将学习容器日志管理。容器跑起来了,健康检查也配好了,但出了问题怎么排查?日志是你最好的朋友。我们会学习 Docker 的日志驱动机制、日志轮转配置,以及如何用 ELK/Loki 等工具集中管理容器日志。