|
|
你有没有遇到过这样的情况:服务器上的容器看起来好好的,显示 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": "<!DOCTYPE html>..."
- }
- ]
- }
复制代码
模拟故障
现在我们来模拟一下应用"假死"的场景:
- # 进入容器,停掉 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。这就是健康检查的价值所在。
清理实验环境:
━━━━━━━━━━━━━━━━━━━━
三、Restart Policy:让容器自动"复活"
光发现问题还不够,我们还需要自动恢复。这就是 restart policy 的用武之地。
四种重启策略
- +------------------+------------------------------------------+
- | 策略 | 行为 |
- +------------------+------------------------------------------+
- | no | 默认值,不自动重启 |
- | on-failure[:N] | 非正常退出时重启,可选最多重启 N 次 |
- | always | 总是重启(除非手动 docker stop) |
- | unless-stopped | 类似 always,但 docker stop 后重启不恢复 |
- +------------------+------------------------------------------+
复制代码
这四种策略怎么选?我给你一个简单的决策思路:
开发环境:用,容器挂了你正好去查原因
有状态服务(数据库等):用,避免数据损坏后反复重启
无状态服务(Web 应用、API):用或生产环境推荐:,因为它在 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 应用先起来,连不上数据库直接就崩了。
以前大家用各种脚本来解决这个问题,现在 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 的健康检查通过之后才会启动。
完整实操:三层应用健康检查
让我们搭建一个更贴近真实场景的例子。创建:
- 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
复制代码 的效果。
清理环境:
━━━━━━━━━━━━━━━━━━━━
五、生产环境最佳实践
健康检查脚本设计原则
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 ||
| Redis ||
| 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 的服务编排,或者使用第三方工具如、等。在 Swarm 模式下,unhealthy 的容器会被自动替换。
Q2:start_period 和 start_interval 有什么区别?
是宽限期,在这段时间内健康检查失败不会计入 retries。这是为了给应用足够的启动时间。(Docker Engine 25.0+ 支持)是宽限期内检查的间隔,默认 5 秒。宽限期结束后,就使用指定的间隔。
比如一个 Java 应用启动需要 60 秒,你可以设置,这样前 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 等工具集中管理容器日志。 |
|