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 被反复重启。
3.2 健康检查策略设计
不同业务场景需要不同的检查策略。设计时应平衡检查的准确性、资源消耗和响应速度。
3.2.1 检查类型对比
| 检查类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 进程检查 | 基础容器,无应用接口 | 零开销,无需额外工具 | 无法感知应用逻辑状态 |
| TCP 端口检查 | 数据库、缓存中间件 | 简单快速,仅需端口可达 | 端口开放不等于服务可用 |
| HTTP 端点检查 | Web 应用、API 服务 | 可验证业务逻辑(如 /health) | 需要应用暴露健康端点 |
| 自定义脚本检查 | 复杂业务、多组件依赖 | 灵活,可模拟真实请求 | 脚本维护成本高 |
3.2.2 策略设计原则
- 轻量化:检查命令应快速执行(
timeout建议 5-10s),避免消耗过多 CPU/内存。 - 业务相关性:检查应覆盖核心链路。例如,Web 服务不仅检查 200 状态码,还应验证响应体包含关键字段。
- 避免级联效应:健康检查不应依赖外部服务(如数据库),否则外部故障会导致所有容器被标记为 unhealthy,引发大规模重启。
- 启动宽容:合理设置
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 探针,以获得更灵活的恢复控制。
3.5 生产环境最佳实践清单
- 统一健康端点:所有微服务暴露统一的
/health端点,返回 JSON 格式的状态和依赖组件状态。 - 分级检查:Liveness 探针做轻量级检查(如进程存活),Readiness 探针做深度检查(如数据库连接池)。
- 日志与告警:健康检查失败应输出结构化日志,并触发告警通知,避免“静默恢复”掩盖问题。
- 避免检查风暴:大规模集群中,健康检查请求可能对应用造成压力。适当增大
interval或使用缓存。 - 测试检查脚本:在 CI/CD 流水线中模拟健康检查失败场景,验证自动恢复机制是否按预期工作。