1. 为什么需要容器健康检查?

想象一下你正在管理一个电商网站的微服务架构,某个商品服务容器虽然还在运行,但实际上已经无法处理请求了。如果没有健康检查机制,流量仍然会被路由到这个"僵尸"容器,导致用户看到错误页面。这就是健康检查要解决的核心问题。

Docker健康检查就像给容器安装了一个"体检仪",定期检查容器内部服务的真实状态。它不仅仅是看容器进程是否存活,更重要的是验证容器内的应用是否真正健康、能否正常提供服务。

在实际生产环境中,我发现很多团队只关注容器是否运行(running),而忽略了容器内应用的实际健康状态。这就像只检查汽车发动机是否在转,而不看它能不能正常行驶一样危险。

2. 健康检查脚本编写实战

2.1 基础健康检查示例

让我们从一个简单的Node.js服务开始,展示如何在Dockerfile中定义健康检查。这个示例使用Node.js技术栈。

# 使用Node.js官方镜像作为基础
FROM node:16-alpine

# 创建工作目录
WORKDIR /usr/src/app

# 复制package.json并安装依赖
COPY package*.json ./
RUN npm install

# 复制应用源代码
COPY . .

# 暴露应用端口
EXPOSE 3000

# 定义健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

# 启动应用
CMD ["node", "server.js"]

注释说明:

  • --interval=30s:每30秒检查一次
  • --timeout=3s:超时时间为3秒
  • --start-period=5s:容器启动后等待5秒才开始检查
  • --retries=3:连续3次失败才标记为不健康
  • curl -f:静默模式,失败时返回非零状态码

对应的Node.js健康检查端点实现:

// server.js
const express = require('express');
const app = express();

// 模拟数据库连接状态
let dbConnected = true;

// 健康检查端点
app.get('/health', (req, res) => {
  if (!dbConnected) {
    return res.status(503).json({ status: 'DOWN' });
  }
  res.json({ status: 'UP' });
});

// 模拟数据库连接失败
app.get('/simulate-failure', (req, res) => {
  dbConnected = false;
  res.json({ message: 'Database connection marked as down' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

2.2 高级健康检查脚本

对于更复杂的应用,我们可能需要编写自定义的健康检查脚本。下面是一个检查多个依赖服务的Bash脚本示例:

HEALTHCHECK --interval=1m --timeout=10s --start-period=2m --retries=2 \
  CMD /healthcheck.sh

对应的healthcheck.sh脚本:

#!/bin/bash

# 检查Web服务是否响应
if ! curl -sSf http://localhost:3000/health > /dev/null; then
  echo "Web service is down"
  exit 1
fi

# 检查数据库连接
if ! pg_isready -h localhost -p 5432 -U postgres > /dev/null; then
  echo "Database connection failed"
  exit 1
fi

# 检查磁盘空间
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
  echo "Disk space critically low"
  exit 1
fi

# 所有检查通过
exit 0

注释说明:

  • 这个脚本检查了三个关键指标:Web服务、数据库连接和磁盘空间
  • 使用curl检查Web端点
  • 使用pg_isready检查PostgreSQL数据库
  • 使用df命令检查磁盘使用情况
  • 任何一项检查失败都会导致整个健康检查失败

3. Docker重启策略详解

健康检查只有与重启策略配合使用才能发挥最大价值。Docker提供了三种重启策略:

3.1 no策略

docker run --restart=no my-app
  • 容器退出时不自动重启
  • 适合开发环境或不需要高可用的场景

3.2 on-failure策略

docker run --restart=on-failure:5 my-app
  • 容器以非零状态退出时自动重启
  • 冒号后的数字表示最大重启次数
  • 适合处理临时性错误的场景

3.3 always策略

docker run --restart=always my-app
  • 容器退出时总是重启,无论退出状态如何
  • 适合必须保持运行的关键服务
  • 注意:可能导致无限重启循环

3.4 unless-stopped策略

docker run --restart=unless-stopped my-app
  • 类似于always,但不会在Docker守护进程重启时重启已停止的容器
  • 适合需要持久运行但允许手动停止的服务

4. 故障自动恢复的完整方案

结合健康检查和重启策略,我们可以构建一个完整的故障自愈系统。下面是一个实际生产环境的示例:

version: '3.8'

services:
  web:
    image: my-node-app:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 1m
    ports:
      - "8080:3000"
    
  db:
    image: postgres:13
    restart: on-failure:5
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 1m
      timeout: 10s
      retries: 3
      start_period: 2m
    environment:
      POSTGRES_PASSWORD: example

注释说明:

  • Web服务使用unless-stopped策略,确保服务持续运行
  • 数据库使用on-failure:5策略,避免无限重启消耗资源
  • 两个服务都配置了详细的健康检查参数
  • 健康检查考虑了服务的启动时间(start_period)

5. 应用场景分析

5.1 微服务架构

在微服务架构中,健康检查可以:

  • 实现服务自动发现和负载均衡
  • 配合服务网格(如Istio)实现流量自动切换
  • 实现金丝雀发布和蓝绿部署

5.2 数据库服务

对于数据库容器,健康检查可以:

  • 检测数据库连接池状态
  • 监控复制延迟(在集群环境中)
  • 防止查询堆积导致的雪崩效应

5.3 批处理作业

对于定时运行的批处理作业容器:

  • 可以检查上次作业执行是否成功
  • 监控作业执行时间是否超时
  • 确保资源释放是否完全

6. 技术优缺点

6.1 优点

  • 提高可用性:自动检测和恢复故障
  • 减少人工干预:运维团队无需24/7待命
  • 精细化监控:比简单的进程检查更准确
  • 资源优化:自动重启失败容器,避免资源浪费

6.2 缺点

  • 配置复杂:需要合理设置检查间隔和超时
  • 可能误判:网络抖动可能导致误报
  • 资源消耗:频繁检查会增加系统负载
  • 启动顺序问题:依赖服务未就绪可能导致误判

7. 注意事项

7.1 检查频率设置

  • 太频繁:增加系统负载
  • 太稀疏:故障检测延迟高
  • 建议:关键服务30秒,非关键服务1-5分钟

7.2 超时时间设置

  • 根据服务响应时间合理设置
  • 通常设置为正常响应时间的2-3倍
  • 示例:API平均响应1秒,超时可设3秒

7.3 启动等待时间

  • 给容器足够的初始化时间
  • 特别是数据库等启动慢的服务
  • 避免启动过程中被误判为不健康

7.4 检查脚本优化

  • 脚本执行要快,避免长时间运行
  • 减少外部依赖,提高可靠性
  • 添加适当的日志输出,便于调试

8. 文章总结

Docker健康检查是构建可靠容器化应用的关键技术。通过本文的详细讲解,你应该已经掌握了:

  1. 如何编写各种复杂度的健康检查脚本
  2. 如何合理配置检查参数(间隔、超时等)
  3. 如何选择最适合的重启策略
  4. 如何构建完整的故障自愈系统

记住,好的健康检查应该像优秀的医生一样:检查全面但不扰民,判断准确但不武断,处理及时但不慌乱。

在实际应用中,建议从小规模开始,逐步完善健康检查机制。先实现基本的存活检查,再添加就绪检查,最后考虑全面的健康检查。同时要密切监控健康检查的效果,根据实际情况不断调整参数。