冷蟊初退 孤灯野澜 志起鸡鸣 墓不悲秋 技术交流 软件开发 商业合作 加Q:411239339

Day 19 Docker容器健康检查与自动重启

浏览:13次阅读
没有评论

共计 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):用 alwaysunless-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 的服务编排,或者使用第三方工具如 autohealwatchtower 等。在 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 健康检查体系的三大核心组件:

  1. HEALTHCHECK 指令 :让 Docker 能感知容器内应用的真实健康状态,从 " 活着 " 升级到 " 健康 "
  2. Restart Policy:四种重启策略,根据服务特性选择合适的策略
  3. depends_on + condition:在 Compose 中实现优雅的启动顺序控制

记住一个核心思想: 不要信任容器的运行状态,要验证应用的健康状态 。就像那句老话说的," 信任,但要验证 "。

健康检查看起来是个小功能,但它是构建高可用系统的基石。没有健康检查的容器化部署,就像没有体检的员工管理——出了问题才知道,已经晚了。

明日预告

Day 20 我们将学习容器日志管理。容器跑起来了,健康检查也配好了,但出了问题怎么排查?日志是你最好的朋友。我们会学习 Docker 的日志驱动机制、日志轮转配置,以及如何用 ELK/Loki 等工具集中管理容器日志。

正文完
创作不易,扫码加点动力
post-qrcode
 0
果较瘦
版权声明:本站原创文章,由 果较瘦 于2026-03-29发表,共计6645字。
转载说明:除特殊说明外本站文章皆由果较瘦原创发布,转载请注明出处。