Shell脚本如何配置容器健康检查:从入门到生产级实践
目录导读
为什么需要容器健康检查?
容器化应用的普及让服务部署更加灵活,但动态调度环境也引入了新的风险:一个容器可能进程仍在运行,但内部服务已经挂死或响应缓慢。健康检查(Health Check) 就是解决这个问题的关键机制。

核心价值包括:
- 自动发现并重启故障容器
- 避免负载均衡将流量路由到不可用的实例
- 配合编排工具(如Kubernetes、Docker Compose)实现滚动更新与自愈
一个真实案例: 某电商平台曾因Redis容器进程存活但端口堵塞,导致上游服务大量超时,而健康检查在3秒内检测到失败并触发重启,避免了持续20分钟的故障。
Docker原生健康检查机制
Docker支持在Dockerfile或docker-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 避免常见陷阱
| 问题 | 解决方案 |
|---|---|
| 脚本依赖外部工具 | 容器内确保curl、wget等工具存在,或使用内置命令 |
| 超时导致阻塞 | 设置命令内超时(如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
关键设计点:
- 使用
--max-time避免curl无限等待 - 检查失败时输出错误信息,方便容器日志排查
- 循环所有上游,而非仅检查Nginx的本地socket
- 退出码与健康状态严格对应
延伸阅读:
- 在Kubernetes中,进一步使用
startupProbe避免慢启动容器被杀死 - 对于需要持续运行的后台任务容器,可使用
livenessProbe检查是否有僵尸进程
通过系统化配置健康检查,你可以构建一个自愈的容器集群,将故障恢复时间从分钟级降低到秒级。