Shell脚本如何配置容器健康检查

wen 实用脚本 1

Shell脚本如何配置容器健康检查:从入门到生产级实践

目录导读

  1. 为什么需要容器健康检查?
  2. Docker原生健康检查机制
  3. Shell脚本实现健康检查的核心模式
  4. 生产级健康检查脚本编写要点
  5. 常见问题与问答
  6. 实践:一个完整的Nginx健康检查脚本

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

容器化应用的普及让服务部署更加灵活,但动态调度环境也引入了新的风险:一个容器可能进程仍在运行,但内部服务已经挂死或响应缓慢。健康检查(Health Check) 就是解决这个问题的关键机制。

Shell脚本如何配置容器健康检查

核心价值包括:

  • 自动发现并重启故障容器
  • 避免负载均衡将流量路由到不可用的实例
  • 配合编排工具(如Kubernetes、Docker Compose)实现滚动更新与自愈

一个真实案例: 某电商平台曾因Redis容器进程存活但端口堵塞,导致上游服务大量超时,而健康检查在3秒内检测到失败并触发重启,避免了持续20分钟的故障。


Docker原生健康检查机制

Docker支持在Dockerfiledocker-compose.yml中声明健康检查指令:

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:8080/health || exit 1

关键参数解析:

  • --interval:检查间隔(默认30秒)
  • --timeout:单次检查超时时间(默认30秒)
  • --retries:连续失败次数后标记为unhealthy(默认3次)
  • --start-period:容器启动后的初始化宽限期(默认0秒)

Docker生成的检查状态:

  • starting:容器尚未通过健康检查
  • healthy:检查通过
  • unhealthy:检查失败

Shell脚本实现健康检查的核心模式

HEALTHCHECK CMD需要复杂逻辑时(如检查数据库连接、缓存状态、磁盘空间),Shell脚本是最灵活的实现方式。

基本结构模板:

#!/bin/sh
# healthcheck.sh
# 检查HTTP服务
curl -f http://localhost:80/ || exit 1
# 检查数据库连接(示例:MySQL)
mysqladmin ping -h localhost -u root -p$MYSQL_ROOT_PASSWORD || exit 1
# 检查磁盘使用率
disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 90 ]; then
    exit 1
fi
exit 0

脚本退出码含义:

  • 0:健康(healthy)
  • 1:不健康(unhealthy)

注意: 退出码范围0-255,exit 0表示正常,非0表示失败,Docker只关心是否为0。


生产级健康检查脚本编写要点

1 避免常见陷阱

问题 解决方案
脚本依赖外部工具 容器内确保curlwget等工具存在,或使用内置命令
超时导致阻塞 设置命令内超时(如timeout 2 curl ...
日志污染 健康检查日志应输出到/dev/null
误判 使用&&组合条件,避免因小问题触发重启

2 优化检查频率与资源消耗

# 每隔10秒检查一次,但只做简单的TCP连接测试
timeout 2 nc -zv localhost 3306

Kubernetes下更精细的控制: 使用livenessProbe(存活检查)和readinessProbe(就绪检查)分离,前者检测进程是否死锁,后者检测服务是否可接受流量。

3 多条件组合检查的写法

#!/bin/bash
# 检查三项核心指标,全部通过才算健康
check_http() {
    curl -f -s http://localhost:8080/api/ping > /dev/null 2>&1
}
check_mysql() {
    timeout 2 bash -c 'echo "select 1" | mysql -u root -p$DB_PASS -h localhost' > /dev/null 2>&1
}
check_disk() {
    [ $(df / | awk 'NR==2 {print $5}' | sed 's/%//') -lt 85 ]
}
# 只要一个失败就退出1
check_http && check_mysql && check_disk || exit 1
exit 0

常见问题与问答

问题1:为什么健康检查脚本在容器内运行正常,但Docker报告unhealthy?

答: 最常见原因是脚本依赖的路径或变量未在容器内正确设置

  • 使用curl但容器内未安装
  • 环境变量(如DB_PASS)未通过ENV声明或未传递
  • 脚本权限问题(需要chmod +x

排查方法: 手动进入容器执行脚本,观察退出码和错误信息。

问题2:Shell脚本健康检查和HTTP端点(如/health)哪种更好?

答: 取决于场景:

  • 适用于Shell脚本的场景: 需要检查服务内部状态(如缓存命中率、队列深度)、多个服务联动、无HTTP接口的服务(如Redis、MySQL)。
  • 适用于HTTP端点的场景: 应用已实现健康端点、需要返回详细状态码(如200表示健康,503表示负载过重)。

最佳实践:优先使用应用自身健康端点,复杂组合逻辑用Shell脚本。

问题3:如何避免健康检查脚本自身成为单点故障?

答: 采用以下策略:

  • 脚本保持简短、无循环、无持续连接
  • 使用内置命令替代复杂外部工具(如用echo > /dev/tcp/代替nc
  • 设置合理的start-period避免启动时误判

问题4:健康检查应该检测哪些指标?常见遗漏项

答: 除了基本的进程存活和端口监听,建议检测:

  • 数据库连接可达性
  • 最近一次日志更新时间(心跳机制)
  • 关键文件是否存在且未过期
  • 认证令牌是否有效
  • 缓存服务(如Redis)是否可用

实践:一个完整的Nginx健康检查脚本

以下脚本适用于Nginx容器,检查所有上游后端服务的健康状态:

#!/bin/bash
# nginx-health.sh —— 用于Nginx容器的综合健康检查
UPSTREAM_URLS=(
    "http://app1:3000/health"
    "http://app2:3000/health"
    "http://backend:8080/api/status"
)
all_healthy=true
for url in "${UPSTREAM_URLS[@]}"; do
    status_code=$(curl -o /dev/null -s -w "%{http_code}" --max-time 2 "$url" 2>/dev/null)
    if [ "$status_code" -ne 200 ]; then
        echo "上游 $url 返回 $status_code" >&2
        all_healthy=false
    fi
done
if [ "$all_healthy" = true ]; then
    echo "Nginx及所有上游健康"
    exit 0
else
    echo "部分上游不可用,容器标记为不健康"
    exit 1
fi

Dockerfile集成:

FROM nginx:alpine
COPY nginx-health.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/nginx-health.sh
HEALTHCHECK --interval=15s --timeout=5s --retries=2 \
    CMD /usr/local/bin/nginx-health.sh

关键设计点:

  1. 使用--max-time避免curl无限等待
  2. 检查失败时输出错误信息,方便容器日志排查
  3. 循环所有上游,而非仅检查Nginx的本地socket
  4. 退出码与健康状态严格对应

延伸阅读:

  • 在Kubernetes中,进一步使用startupProbe避免慢启动容器被杀死
  • 对于需要持续运行的后台任务容器,可使用livenessProbe检查是否有僵尸进程

通过系统化配置健康检查,你可以构建一个自愈的容器集群,将故障恢复时间从分钟级降低到秒级。

抱歉,评论功能暂时关闭!