找回密码
 立即注册

QQ登录

只需一步,快速开始

peUfSYR.png
查看: 47|回复: 0

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

[复制链接]

876

主题

13

回帖

2808

积分

管理员

积分
2808
发表于 2026-3-29 16:17:14 | 显示全部楼层 |阅读模式
你有没有遇到过这样的情况:服务器上的容器看起来好好的,
  1. docker ps
复制代码
显示 Up 了好几天,但实际上里面的应用早就挂了?用户打电话过来说"你们网站怎么打不开",你一看容器状态还是 running,一脸懵逼。

这就好比你请了个保安看门,保安人站在那里,但其实已经睡着了。容器的进程还在,但服务已经不响应了。这种"假活着"的状态,是生产环境中最让人头疼的问题之一。

今天我们就来解决这个问题——让 Docker 学会给容器"量体温",发现容器"生病"了就自动帮它"治疗"。

本文你将学到

  • HEALTHCHECK 指令的工作原理和配置方法
  • 如何编写有效的健康检查脚本
  • restart policy 的四种策略及选择依据
  • depends_on 配合 condition 实现启动顺序控制
  • 生产环境中健康检查的最佳实践

    阅读时间: 约 10 分钟
    实操时间: 约 20 分钟
    难度等级: 中级

    ━━━━━━━━━━━━━━━━━━━━

    一、理解健康检查:从"活着"到"健康"

    容器状态的三层含义

    很多人以为容器 running 就万事大吉了,其实容器的状态至少有三层含义:
    1. +--------------------------------------------------+
    2. |  第一层:进程存活(PID 1 还在跑)                    |
    3. |    └── docker ps 显示 Up                          |
    4. |                                                    |
    5. |  第二层:端口可达(网络层面能连上)                    |
    6. |    └── telnet/curl 能连上端口                      |
    7. |                                                    |
    8. |  第三层:业务健康(服务真的能正常处理请求)              |
    9. |    └── 健康检查接口返回 200,数据库连接正常            |
    10. +--------------------------------------------------+
    复制代码

    Docker 默认只关心第一层。而 HEALTHCHECK 的作用,就是让 Docker 能感知到第三层的状态。

    打个比方:你去医院体检,护士先看你人来了没有(进程存活),然后量了个血压(端口可达),最后抽血化验(业务健康)。HEALTHCHECK 就是那个抽血化验的环节。

    ━━━━━━━━━━━━━━━━━━━━

    二、HEALTHCHECK 指令详解

    基本语法

    在 Dockerfile 中添加 HEALTHCHECK 指令:
    1. HEALTHCHECK [OPTIONS] CMD command
    复制代码

    支持的选项:

    | 参数 | 默认值 | 说明 |
    |------|--------|------|
    | --interval | 30s | 每次检查的间隔时间 |
    | --timeout | 30s | 单次检查的超时时间 |
    | --start-period | 0s | 容器启动后的宽限期 |
    | --start-interval | 5s | 宽限期内的检查间隔 |
    | --retries | 3 | 连续失败多少次判定为不健康 |

    返回值约定

    健康检查命令的退出码决定了容器的健康状态:

  • 0 - healthy(健康)
  • 1 - unhealthy(不健康)
  • 2 - reserved(保留,暂不使用)

    实操:给 Nginx 加上健康检查

    我们来写一个带健康检查的 Nginx 镜像。

    创建项目目录:
    1. mkdir -p ~/docker-health-demo && cd ~/docker-health-demo
    复制代码

    编写 Dockerfile:
    1. FROM nginx:alpine
    2. # 安装 curl 用于健康检查
    3. RUN apk add --no-cache curl
    4. # 健康检查:每 10 秒检查一次,超时 3 秒,启动宽限 5 秒,连续 3 次失败则不健康
    5. HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=3 \
    6.   CMD curl -f http://localhost/ || exit 1
    7. EXPOSE 80
    复制代码

    构建并运行:
    1. docker build -t nginx-health .
    2. docker run -d --name web-health nginx-health
    复制代码

    观察容器状态变化:
    1. # 刚启动时,状态是 health: starting
    2. docker ps
    3. # CONTAINER ID   IMAGE          STATUS                            NAMES
    4. # a1b2c3d4e5f6   nginx-health   Up 3 seconds (health: starting)   web-health
    5. # 等待约 15 秒后,状态变为 healthy
    6. docker ps
    7. # CONTAINER ID   IMAGE          STATUS                    NAMES
    8. # a1b2c3d4e5f6   nginx-health   Up 18 seconds (healthy)   web-health
    复制代码

    查看健康检查的详细记录:
    1. docker inspect --format='{{json .State.Health}}' web-health | python3 -m json.tool
    复制代码

    预期输出类似:
    1. {
    2.     "Status": "healthy",
    3.     "FailingStreak": 0,
    4.     "Log": [
    5.         {
    6.             "Start": "2024-01-15T10:00:05.123Z",
    7.             "End": "2024-01-15T10:00:05.234Z",
    8.             "ExitCode": 0,
    9.             "Output": "<!DOCTYPE html>..."
    10.         }
    11.     ]
    12. }
    复制代码

    模拟故障

    现在我们来模拟一下应用"假死"的场景:
    1. # 进入容器,停掉 nginx(但容器不会退出,因为还有 sleep 等方式维持 PID 1)
    2. docker exec web-health nginx -s stop
    3. # 等待 30 秒左右(interval * retries),再看状态
    4. docker ps
    5. # CONTAINER ID   IMAGE          STATUS                      NAMES
    6. # a1b2c3d4e5f6   nginx-health   Up 1 minute (unhealthy)     web-health
    复制代码

    看到了吧?容器还在 running,但是状态已经变成了 unhealthy。这就是健康检查的价值所在。

    清理实验环境:
    1. docker rm -f web-health
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    三、Restart Policy:让容器自动"复活"

    光发现问题还不够,我们还需要自动恢复。这就是 restart policy 的用武之地。

    四种重启策略
    1. +------------------+------------------------------------------+
    2. |  策略             |  行为                                    |
    3. +------------------+------------------------------------------+
    4. |  no              |  默认值,不自动重启                        |
    5. |  on-failure[:N]  |  非正常退出时重启,可选最多重启 N 次         |
    6. |  always          |  总是重启(除非手动 docker stop)            |
    7. |  unless-stopped  |  类似 always,但 docker stop 后重启不恢复   |
    8. +------------------+------------------------------------------+
    复制代码

    这四种策略怎么选?我给你一个简单的决策思路:

  • 开发环境:用
    1. no
    复制代码
    ,容器挂了你正好去查原因
  • 有状态服务(数据库等):用
    1. on-failure
    复制代码
    ,避免数据损坏后反复重启
  • 无状态服务(Web 应用、API):用
    1. always
    复制代码
    1. unless-stopped
    复制代码
  • 生产环境推荐
    1. unless-stopped
    复制代码
    ,因为它在 Docker daemon 重启后也能恢复容器

    实操:健康检查 + 自动重启

    在 docker run 时配合使用:
    1. docker run -d \
    2.   --name web-auto \
    3.   --restart unless-stopped \
    4.   nginx-health
    复制代码

    但这里有个关键问题:restart policy 只在容器退出时生效,HEALTHCHECK 标记为 unhealthy 并不会自动重启容器。

    那怎么办?有两种方案。

    方案一:在健康检查失败时让容器退出
    1. HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    2.   CMD curl -f http://localhost/ || kill 1
    复制代码

    不太推荐这种方式,因为直接 kill PID 1 比较暴力。

    方案二:使用 Docker Compose + autoheal(推荐)

    这个我们在 Compose 部分会详细讲。先来看 Compose 中的健康检查配置。

    ━━━━━━━━━━━━━━━━━━━━

    四、Docker Compose 中的健康检查

    基本配置
    1. version: "3.8"
    2. services:
    3.   web:
    4.     image: nginx:alpine
    5.     healthcheck:
    6.       test: ["CMD", "curl", "-f", "http://localhost/"]
    7.       interval: 10s
    8.       timeout: 3s
    9.       retries: 3
    10.       start_period: 5s
    11.     restart: unless-stopped
    复制代码

    depends_on + condition:优雅的启动顺序

    这是个非常实用的功能。想想这个场景:你的 Web 应用依赖 MySQL,但 MySQL 需要几十秒才能完全启动。如果 Web 应用先起来,连不上数据库直接就崩了。

    以前大家用各种
    1. wait-for-it.sh
    复制代码
    脚本来解决这个问题,现在 Compose 原生支持了:
    1. version: "3.8"
    2. services:
    3.   db:
    4.     image: mysql:8.0
    5.     environment:
    6.       MYSQL_ROOT_PASSWORD: secret123
    7.       MYSQL_DATABASE: myapp
    8.     healthcheck:
    9.       test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-psecret123"]
    10.       interval: 5s
    11.       timeout: 3s
    12.       retries: 10
    13.       start_period: 30s
    14.     restart: unless-stopped
    15.   web:
    16.     image: nginx:alpine
    17.     depends_on:
    18.       db:
    19.         condition: service_healthy
    20.     restart: unless-stopped
    复制代码

    这段配置的意思是:web 服务要等到 db 的健康检查通过之后才会启动。

    完整实操:三层应用健康检查

    让我们搭建一个更贴近真实场景的例子。创建
    1. docker-compose.yml
    复制代码

    1. version: "3.8"
    2. services:
    3.   db:
    4.     image: mysql:8.0
    5.     environment:
    6.       MYSQL_ROOT_PASSWORD: demo123
    7.       MYSQL_DATABASE: app
    8.     healthcheck:
    9.       test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-pdemo123"]
    10.       interval: 5s
    11.       timeout: 3s
    12.       retries: 10
    13.       start_period: 30s
    14.     volumes:
    15.       - db-data:/var/lib/mysql
    16.     restart: unless-stopped
    17.   redis:
    18.     image: redis:7-alpine
    19.     healthcheck:
    20.       test: ["CMD", "redis-cli", "ping"]
    21.       interval: 5s
    22.       timeout: 3s
    23.       retries: 5
    24.       start_period: 5s
    25.     restart: unless-stopped
    26.   web:
    27.     image: nginx:alpine
    28.     ports:
    29.       - "8080:80"
    30.     depends_on:
    31.       db:
    32.         condition: service_healthy
    33.       redis:
    34.         condition: service_healthy
    35.     healthcheck:
    36.       test: ["CMD-SHELL", "curl -f http://localhost/ || exit 1"]
    37.       interval: 10s
    38.       timeout: 3s
    39.       retries: 3
    40.       start_period: 5s
    41.     restart: unless-stopped
    42. volumes:
    43.   db-data:
    复制代码

    启动并观察:
    1. docker compose up -d
    2. # 查看启动状态,注意观察启动顺序
    3. docker compose ps
    复制代码

    预期输出:
    1. NAME                  STATUS                   PORTS
    2. docker-health-db-1    Up 35 seconds (healthy)  3306/tcp
    3. docker-health-redis-1 Up 35 seconds (healthy)  6379/tcp
    4. docker-health-web-1   Up 5 seconds (healthy)   0.0.0.0:8080->80/tcp
    复制代码

    你会发现 db 和 redis 先启动,等它们 healthy 之后,web 才开始启动。这就是
    1. condition: service_healthy
    复制代码
    的效果。

    清理环境:
    1. docker compose down -v
    复制代码

    ━━━━━━━━━━━━━━━━━━━━

    五、生产环境最佳实践

    健康检查脚本设计原则

    1. 检查要有意义

    不要只检查端口是否开放,要检查业务是否真的可用:
    1. # 不够好:只检查端口
    2. HEALTHCHECK CMD curl -f http://localhost:8080/ || exit 1
    3. # 更好:检查专用健康接口
    4. HEALTHCHECK CMD curl -f http://localhost:8080/health || exit 1
    复制代码

    2. 健康接口要轻量

    健康检查接口不应该执行复杂的业务逻辑,建议检查:
  • 应用进程是否正常
  • 关键依赖是否可连接(数据库、缓存)
  • 磁盘空间是否充足

    3. 合理设置超时和间隔
    1. 应用启动时间   → 决定 start_period
    2. 请求响应时间   → 决定 timeout(通常设为 P99 延迟的 2-3 倍)
    3. 故障发现速度   → 决定 interval 和 retries
    复制代码

    一个经验公式:故障发现时间 = interval x retries。如果你希望 30 秒内发现故障,可以设置 interval=10s, retries=3。

    4. 不要在健康检查中做副作用操作
    1. # 错误:健康检查触发了写操作
    2. HEALTHCHECK CMD curl -X POST http://localhost/api/heartbeat || exit 1
    3. # 正确:只做只读检查
    4. HEALTHCHECK CMD curl -f http://localhost/health || exit 1
    复制代码

    常见的健康检查命令

    | 服务 | 健康检查命令 |
    |------|-------------|
    | Nginx |
    1. curl -f http://localhost/
    复制代码
    |
    | MySQL |
    1. mysqladmin ping -h localhost
    复制代码
    |
    | PostgreSQL |
    1. pg_isready -U postgres
    复制代码
    |
    | Redis |
    1. redis-cli ping
    复制代码
    |
    | MongoDB |
    1. mongosh --eval "db.runCommand('ping')"
    复制代码
    |
    | Elasticsearch |
    1. curl -f http://localhost:9200/_cluster/health
    复制代码
    |
    | RabbitMQ |
    1. rabbitmq-diagnostics -q check_running
    复制代码
    |

    ━━━━━━━━━━━━━━━━━━━━

    六、常见问题 Q&A

    Q1:HEALTHCHECK 标记 unhealthy 后,Docker 会自动重启容器吗?

    不会。Docker 原生的 restart policy 只在容器退出时触发。HEALTHCHECK 只是标记状态,不会导致容器退出。要实现 unhealthy 自动重启,可以用 Docker Swarm 的服务编排,或者使用第三方工具如
    1. autoheal
    复制代码
    1. watchtower
    复制代码
    等。在 Swarm 模式下,unhealthy 的容器会被自动替换。

    Q2:start_period 和 start_interval 有什么区别?
    1. start_period
    复制代码
    是宽限期,在这段时间内健康检查失败不会计入 retries。这是为了给应用足够的启动时间。
    1. start_interval
    复制代码
    (Docker Engine 25.0+ 支持)是宽限期内检查的间隔,默认 5 秒。宽限期结束后,就使用
    1. interval
    复制代码
    指定的间隔。

    比如一个 Java 应用启动需要 60 秒,你可以设置
    1. 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 等工具集中管理容器日志。
  • 您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    扫码关注微信公众号

    Archiver|手机版|小黑屋|风叶林

    GMT+8, 2026-5-8 23:00 , Processed in 0.157447 second(s), 20 queries .

    Powered by 风叶林

    © 2001-2026 Discuz! Team.

    快速回复 返回顶部 返回列表