3. 容器健康检查:HEALTHCHECK 指令详解、健康检查策略设计、自定义健康检查脚本、健康检查失败后的自动恢复机制

在生产环境中,容器进程“活着”并不等于“可用”。一个 Java 应用进程可能仍在运行,但 JVM 已陷入死锁或线程池耗尽,无法响应业务请求。Docker 的 HEALTHCHECK 指令正是为解决此问题而设计,它允许平台层感知应用的真实健康状态,并触发自动恢复动作。

3.1 HEALTHCHECK 指令详解

HEALTHCHECK 是 Dockerfile 中的指令,用于定义如何检测容器内的应用是否正常工作。Docker 引擎会定期执行该检查,并根据退出码更新容器的健康状态。

3.1.1 指令语法与参数

参数 默认值 说明
--interval=DURATION 30s 两次健康检查的间隔时间
--timeout=DURATION 30s 单次检查的超时时间,超时视为失败
--start-period=DURATION 0s 容器启动后等待多久才开始检查,用于规避启动阶段的误判
--retries=N 3 连续失败 N 次后,容器状态标记为 unhealthy
# Dockerfile 示例:对 Nginx 容器执行 HTTP 健康检查
FROM nginx:alpine
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=2 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:80/health || exit 1

3.1.2 健康状态三态模型

Docker 将容器健康状态分为三种,可通过 docker inspect 或 docker ps 查看:

  • starting:容器已启动,但仍在 start-period 窗口内,尚未执行首次检查。
  • healthy:最近一次检查通过。
  • unhealthy:连续失败次数达到 retries 阈值。
最佳实践: 在 Kubernetes 环境中,start-period 应设置为应用平均启动时间的 1.5 倍,避免因启动慢导致 Pod 被反复重启。
HEALTHCHECK 三态转换与检查周期 starting healthy unhealthy start-period 结束 连续 retries 次失败 检查通过后恢复 检查周期示例(interval=15s, timeout=5s, retries=2) T0: 容器启动 → start-period(10s) → T10: 首次检查 → T25: 第二次检查 → T40: 第三次检查(连续失败2次) → unhealthy

3.2 健康检查策略设计

不同业务场景需要不同的检查策略。设计时应平衡检查的准确性、资源消耗和响应速度。

3.2.1 检查类型对比

检查类型 适用场景 优点 缺点
进程检查 基础容器,无应用接口 零开销,无需额外工具 无法感知应用逻辑状态
TCP 端口检查 数据库、缓存中间件 简单快速,仅需端口可达 端口开放不等于服务可用
HTTP 端点检查 Web 应用、API 服务 可验证业务逻辑(如 /health) 需要应用暴露健康端点
自定义脚本检查 复杂业务、多组件依赖 灵活,可模拟真实请求 脚本维护成本高

3.2.2 策略设计原则

  1. 轻量化:检查命令应快速执行(timeout 建议 5-10s),避免消耗过多 CPU/内存。
  2. 业务相关性:检查应覆盖核心链路。例如,Web 服务不仅检查 200 状态码,还应验证响应体包含关键字段。
  3. 避免级联效应:健康检查不应依赖外部服务(如数据库),否则外部故障会导致所有容器被标记为 unhealthy,引发大规模重启。
  4. 启动宽容:合理设置 start-period,避免应用启动阶段的短暂不可用被误判。
避坑指南: 不要使用 curl -f http://localhost 作为默认检查命令,因为 curl 在容器内可能未安装。优先使用 wget、nc 或应用自带的健康端点检查工具。

3.3 自定义健康检查脚本

当内置命令无法满足复杂业务逻辑时,需要编写自定义脚本。脚本应遵循“退出码 0 表示健康,非 0 表示不健康”的契约。

3.3.1 脚本示例:Java 应用深度检查

#!/bin/sh
# custom_healthcheck.sh
# 检查 Java 进程的线程池状态和 JVM 内存

# 1. 检查进程是否存在
if ! pgrep -f "java.*myapp" > /dev/null 2>&1; then
    exit 1
fi

# 2. 通过 JMX 或 HTTP 端点获取线程池活跃度
THREAD_POOL_USAGE=$(curl -s http://localhost:8080/actuator/health | jq -r '.components.threadPool.status')
if [ "$THREAD_POOL_USAGE" != "UP" ]; then
    exit 1
fi

# 3. 检查堆内存使用率是否超过 90%
HEAP_USAGE=$(curl -s http://localhost:8080/actuator/metrics/jvm.memory.used | jq '.measurements[0].value')
HEAP_MAX=$(curl -s http://localhost:8080/actuator/metrics/jvm.memory.max | jq '.measurements[0].value')
if [ -n "$HEAP_MAX" ] && [ "$HEAP_MAX" -ne 0 ]; then
    USAGE_PERCENT=$(echo "scale=2; $HEAP_USAGE / $HEAP_MAX * 100" | bc)
    if [ "$(echo "$USAGE_PERCENT > 90" | bc)" -eq 1 ]; then
        exit 1
    fi
fi

exit 0

3.3.2 Dockerfile 集成自定义脚本

FROM openjdk:11-jre-slim
COPY custom_healthcheck.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/custom_healthcheck.sh
HEALTHCHECK --interval=20s --timeout=10s --start-period=60s --retries=3 \
  CMD ["/usr/local/bin/custom_healthcheck.sh"]
脚本注意事项:
  • 脚本必须放在容器镜像内,且具有可执行权限。
  • 避免在脚本中使用交互式命令或长时间阻塞操作。
  • 日志输出应写入 stdout/stderr,便于 docker inspect 查看检查日志。

3.4 健康检查失败后的自动恢复机制

健康检查本身只负责“检测”,自动恢复需要依赖容器编排平台或 Docker 自身的重启策略。

3.4.1 Docker 原生重启策略

重启策略 行为 适用场景
no 不自动重启 一次性任务、调试
on-failure[:max-retries] 仅在容器退出码非 0 时重启 偶发故障,可限制重试次数
always 无论退出码如何,总是重启 守护进程类服务
unless-stopped 类似 always,但手动停止后不重启 需要手动干预的场景

关键点: Docker 的 --restart 策略基于进程退出码,而非健康检查状态。如果容器进程未退出但健康检查失败,Docker 不会自动重启容器。此时需要借助编排平台(如 Docker Swarm 或 Kubernetes)的探针机制。

3.4.2 Docker Compose 与 Swarm 中的恢复

在 Docker Compose 或 Swarm 中,健康检查失败会触发服务编排的自动恢复:

# docker-compose.yml 示例
version: '3.8'
services:
  web:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3

Swarm 模式下,如果容器被标记为 unhealthy,调度器会停止该容器并启动一个新副本,确保服务实例数满足期望值。

3.4.3 Kubernetes 中的 Liveness 与 Readiness 探针

Kubernetes 提供了更精细的探针机制,与 Docker HEALTHCHECK 互补:

  • Liveness Probe:检测容器是否存活,失败则重启 Pod。
  • Readiness Probe:检测容器是否就绪,失败则从 Service 端点中移除。
  • Startup Probe:用于慢启动容器,成功前禁用 Liveness/Readiness 检查。
迁移建议: 如果从 Docker 迁移到 Kubernetes,建议将 Dockerfile 中的 HEALTHCHECK 保留作为本地开发时的快速反馈,同时在 Kubernetes 部署 YAML 中定义独立的 Liveness 和 Readiness 探针,以获得更灵活的恢复控制。
健康检查失败后的自动恢复流程(Kubernetes 模式) 健康检查失败 连续 retries 次失败 Liveness 失败 → 重启 Pod Readiness 失败 → 摘除流量 Startup 失败 → 延长等待 恢复动作对比 Docker 原生:仅进程退出时重启 | Swarm:unhealthy 时重建服务实例 | K8s:Liveness 重启 / Readiness 摘流量 / Startup 延迟检查

3.5 生产环境最佳实践清单

  • 统一健康端点:所有微服务暴露统一的 /health 端点,返回 JSON 格式的状态和依赖组件状态。
  • 分级检查:Liveness 探针做轻量级检查(如进程存活),Readiness 探针做深度检查(如数据库连接池)。
  • 日志与告警:健康检查失败应输出结构化日志,并触发告警通知,避免“静默恢复”掩盖问题。
  • 避免检查风暴:大规模集群中,健康检查请求可能对应用造成压力。适当增大 interval 或使用缓存。
  • 测试检查脚本:在 CI/CD 流水线中模拟健康检查失败场景,验证自动恢复机制是否按预期工作。