一、项目背景与核心诉求
• 适配环境:小皮面板 + 低版本 Supervisor + Linux 服务器(最终适配 Ubuntu 22.04)
• 核心问题:Nginx 端口占用、手动 stop 不重启、面板显示“运行中”但网站实际挂掉(僵尸主进程)
• 最终目标:实现 Nginx 稳定运行,精准检测网站可用性,自动处理异常场景
二、配置迭代时间线与分析思路
版本1:最初版
分析思路:聚焦核心问题,先解决端口占用,保留基础启动逻辑
核心实现:保留强杀端口命令、Nginx启动命令和基础参数
存在缺陷:仅监控 bash 进程,无法检测 Nginx 实际状态,管理权归属混乱
版本2:功能扩展版
分析思路:拆分功能模块,增强鲁棒性,将配置拆分为主配置+脚本文件
核心实现:新增启动前置清理脚本、健康检查脚本、一键管理脚本
优化点:实现进程清理、端口检测、日志记录的分离管理
版本3:Ubuntu 22.04适配版
分析思路:解决系统兼容性问题,替换Ubuntu 22.04缺失/失效的命令
核心修改:netstat→ss、pkill→pgrep+xargs、单引号→双引号
适配点:确保在目标系统中所有命令正常执行,无解析错误
版本4:888端口适配版
分析思路:补充端口覆盖范围,解决888端口占用导致的启动失败问题
核心修改:在端口清理、检测逻辑中新增888端口,排查命令同步更新
扩展点:覆盖业务所需的所有端口(80/443/888),避免遗漏场景
版本5:边界修复版
分析思路:优化极端场景处理,提升配置鲁棒性和运维体验
核心修改:新增PID合法性校验、端口检测逻辑优化、9001端口冲突检测
修复点:解决非数字PID导致脚本异常、单一端口通即误判等边界问题
版本6:URL检测版
分析思路:切换检测维度,从“端口监听”转为“实际业务可用性”
核心修改:用curl检测URL返回码替代端口监听检测,按返回码执行不同逻辑
核心优化:更贴近实际业务场景,避免“端口通但网站挂”的误判
版本7:最终版
分析思路:极简优化,保留核心功能,删除冗余逻辑,精准匹配需求
核心修改:仅保留URL返回码判定+端口强杀兜底,删除所有无用监控项
最终优化:逻辑清晰、执行高效、维护成本低,完全满足核心诉求
三、关键版本核心差异对比
<<<<
| 对比维度</ | 最初版</ | 中间优化版</ | 最终版</ |
|---|---|---|---|
| 检测逻辑 | 监控bash进程 | 端口监听检测 → URL返回码检测 | 仅URL返回码检测(http://182.106.136.56) |
| 端口处理 | 80/443/888强杀 | 端口清理+监听检测+冲突检测 | 仅80/443/888强杀(兜底启动) |
| 功能模块 | 单行命令配置 | 主配置+4个脚本+日志轮转 | 主配置+2个核心脚本(极简) |
| 异常处理 | 仅解决端口占用 | 多场景异常处理+失败计数+超时控制 | 按返回码精准处理(200/404/500/其他) |
| 管理权 | 小皮面板为主 | Supervisor完全接管 | Supervisor完全接管(无冗余依赖) |
四、核心修改点详细说明
1. 检测逻辑演进(最关键修改)
• 从“监控bash进程”→“端口监听检测”→“URL返回码检测”
• 原因:端口监听正常不代表网站可用(如僵尸主进程、应用层异常),URL返回码能直接反映业务状态
# 最终版核心检测逻辑
curl_check() {
local code=$(curl $CHECK_URL -m $CURL_TIMEOUT -s -w "%{http_code}" -o /dev/null)
echo "$code"
}
curl_check() {
local code=$(curl $CHECK_URL -m $CURL_TIMEOUT -s -w "%{http_code}" -o /dev/null)
echo "$code"
}
2. 系统兼容性适配
• netstat → ss:Ubuntu 22.04默认无netstat,ss命令功能一致且更高效
• pkill → pgrep+xargs:解决pkill在低版本Supervisor中匹配失效问题
• 单引号 → 双引号:解决Supervisor解析命令时的语法错误
3. 端口管理优化
• 新增888端口:覆盖业务所需端口,避免因888端口占用导致启动失败
• 删除端口监听检测:仅保留端口强杀功能,作为启动兜底(解决端口占用坑)
• 原因:端口监听状态无实际业务意义,仅端口占用会影响启动
4. 异常处理规则优化
• 返回码200:静默运行,重置失败计数(正常业务状态)
• 返回码404/500:记录日志,不重启(业务异常需手动处理)
• 其他返回码:连续2次失败→强杀端口+重启Nginx(系统层面异常)
• 原因:区分业务异常和系统异常,避免无效重启和数据丢失
五、采用最终方案的原因
1. 精准匹配核心诉求
• 最终方案仅聚焦“网站可用”这一核心目标,所有逻辑围绕URL返回码展开,解决了最初的所有问题(端口占用、自动重启、防僵尸进程)
2. 逻辑极简,维护成本低
• 删除了中间版本中冗余的端口监听检测、PID校验、9001端口冲突检测等功能,仅保留核心必要逻辑,后期维护无需关注过多细节
3. 执行高效,资源占用少
• 检测间隔15秒,curl超时2秒,无复杂循环和多重判断,对服务器资源占用极低,适合长期稳定运行
4. 异常处理精准
• 按返回码区分不同异常场景,避免了“一刀切”的重启策略,减少因不必要的重启导致的业务中断
5. 完全适配目标环境
• 解决了Ubuntu 22.04的兼容性问题,同时适配低版本Supervisor,无依赖冲突,部署后可直接稳定运行
六、最终方案核心配置(备查)
1. Supervisor主配置文件(supervisord.conf)
[inet_http_server]
port=127.0.0.1:9001
[supervisord]
logfile=/xp/server/supervisor/log/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/xp/server/supervisor/supervisord.pid
nodaemon=false
minfds=1024
minprocs=200
user=root
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=http://127.0.0.1:9001
[program:nginx]
command=/xp/server/nginx/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
startretries=5
startsecs=10
stopwaitsecs=15
user=root
redirect_stderr=true
stdout_logfile=/xp/server/supervisor/log/nginx_main.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stopasgroup=true
killasgroup=true
stopsignal=TERM
process_name=%(program_name)s
numprocs=1
[program:nginx_health]
command=/bin/bash /xp/server/nginx/nginx_health.sh
autostart=true
autorestart=true
startretries=3
startsecs=5
user=root
redirect_stderr=true
stdout_logfile=/xp/server/supervisor/log/nginx_health.log
stdout_logfile_maxbytes=5MB
stdout_logfile_backups=5
stopasgroup=false
killasgroup=false
process_name=%(program_name)s
numprocs=1
port=127.0.0.1:9001
[supervisord]
logfile=/xp/server/supervisor/log/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/xp/server/supervisor/supervisord.pid
nodaemon=false
minfds=1024
minprocs=200
user=root
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=http://127.0.0.1:9001
[program:nginx]
command=/xp/server/nginx/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
startretries=5
startsecs=10
stopwaitsecs=15
user=root
redirect_stderr=true
stdout_logfile=/xp/server/supervisor/log/nginx_main.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stopasgroup=true
killasgroup=true
stopsignal=TERM
process_name=%(program_name)s
numprocs=1
[program:nginx_health]
command=/bin/bash /xp/server/nginx/nginx_health.sh
autostart=true
autorestart=true
startretries=3
startsecs=5
user=root
redirect_stderr=true
stdout_logfile=/xp/server/supervisor/log/nginx_health.log
stdout_logfile_maxbytes=5MB
stdout_logfile_backups=5
stopasgroup=false
killasgroup=false
process_name=%(program_name)s
numprocs=1
2. 健康检查脚本(nginx_health.sh)
#!/bin/bash
# ========== 核心配置 ==========
CHECK_URL="http://182.106.136.56" # 唯一检测目标
CHECK_INTERVAL=15 # 每15秒检测1次
CURL_TIMEOUT=2 # curl超时2秒
LOG_FILE="/xp/server/supervisor/log/nginx_health.log"
MAX_FAIL_TIMES=2 # 连续2次失败触发重启
FAIL_COUNT=0 # 失败计数初始化
# ========== 初始化 ==========
[ ! -d "$(dirname $LOG_FILE)" ] && mkdir -p "$(dirname $LOG_FILE)" && chmod 755 "$(dirname $LOG_FILE)"
# ========== 函数:执行curl检测 ==========
curl_check() {
local code=$(curl $CHECK_URL -m $CURL_TIMEOUT -s -w "%{http_code}" -o /dev/null)
echo "$code"
}
# ========== 函数:强杀80/443/888端口+Nginx进程(兜底) ==========
force_clean() {
echo "[$(date)] 开始强制清理80/443/888端口+Nginx进程" >> "$LOG_FILE"
for PORT in 80 443 888; do
PID=$(ss -tunlp 2>/dev/null | grep -E ":$PORT\s" | awk '{print $7}' | cut -d'/' -f1 | head -1)
if [ -n "$PID" ] && [[ "$PID" =~ ^[0-9]+$ ]]; then
kill -9 "$PID" 2>/dev/null
echo "[$(date)] 强制杀死端口$PORT占用进程:$PID" >> "$LOG_FILE"
fi
done
pgrep -f "nginx: master process" | xargs -r kill -9 2>/dev/null
pgrep -f "nginx: worker process" | xargs -r kill -9 2>/dev/null
echo "[$(date)] 强制清理完成:80/443/888端口+Nginx进程已杀死" >> "$LOG_FILE"
}
# ========== 函数:重启Nginx(通过Supervisor) ==========
restart_nginx() {
echo "[$(date)] 触发Nginx重启(通过Supervisor)" >> "$LOG_FILE"
/xp/server/supervisor/supervisorctl -c /xp/server/supervisor/supervisord.conf restart nginx 2>> "$LOG_FILE"
echo "[$(date)] Nginx重启命令已执行" >> "$LOG_FILE"
}
# ========== 启动提示 ==========
echo "[$(date)] Nginx健康检查启动,仅检测URL:$CHECK_URL 返回码,每${CHECK_INTERVAL}秒检测1次" >> "$LOG_FILE"
# ========== 主循环(核心逻辑) ==========
while true; do
CODE=$(curl_check)
if [ "$CODE" = "200" ]; then
FAIL_COUNT=0
elif [ "$CODE" = "404" ] || [ "$CODE" = "500" ]; then
echo "[$(date)] 业务异常:URL返回码$CODE,需手动处理(不触发重启)" >> "$LOG_FILE"
FAIL_COUNT=0
else
FAIL_COUNT=$((FAIL_COUNT + 1))
echo "[$(date)] 检测失败:URL返回码$CODE,失败计数+1(当前:$FAIL_COUNT)" >> "$LOG_FILE"
if [ $FAIL_COUNT -eq $MAX_FAIL_TIMES ]; then
echo "[$(date)] 连续${MAX_FAIL_TIMES}次检测失败,触发修复逻辑" >> "$LOG_FILE"
force_clean
restart_nginx
FAIL_COUNT=0
echo "[$(date)] 修复逻辑执行完成,重置失败计数为0" >> "$LOG_FILE"
fi
fi
sleep $CHECK_INTERVAL
done
# ========== 核心配置 ==========
CHECK_URL="http://182.106.136.56" # 唯一检测目标
CHECK_INTERVAL=15 # 每15秒检测1次
CURL_TIMEOUT=2 # curl超时2秒
LOG_FILE="/xp/server/supervisor/log/nginx_health.log"
MAX_FAIL_TIMES=2 # 连续2次失败触发重启
FAIL_COUNT=0 # 失败计数初始化
# ========== 初始化 ==========
[ ! -d "$(dirname $LOG_FILE)" ] && mkdir -p "$(dirname $LOG_FILE)" && chmod 755 "$(dirname $LOG_FILE)"
# ========== 函数:执行curl检测 ==========
curl_check() {
local code=$(curl $CHECK_URL -m $CURL_TIMEOUT -s -w "%{http_code}" -o /dev/null)
echo "$code"
}
# ========== 函数:强杀80/443/888端口+Nginx进程(兜底) ==========
force_clean() {
echo "[$(date)] 开始强制清理80/443/888端口+Nginx进程" >> "$LOG_FILE"
for PORT in 80 443 888; do
PID=$(ss -tunlp 2>/dev/null | grep -E ":$PORT\s" | awk '{print $7}' | cut -d'/' -f1 | head -1)
if [ -n "$PID" ] && [[ "$PID" =~ ^[0-9]+$ ]]; then
kill -9 "$PID" 2>/dev/null
echo "[$(date)] 强制杀死端口$PORT占用进程:$PID" >> "$LOG_FILE"
fi
done
pgrep -f "nginx: master process" | xargs -r kill -9 2>/dev/null
pgrep -f "nginx: worker process" | xargs -r kill -9 2>/dev/null
echo "[$(date)] 强制清理完成:80/443/888端口+Nginx进程已杀死" >> "$LOG_FILE"
}
# ========== 函数:重启Nginx(通过Supervisor) ==========
restart_nginx() {
echo "[$(date)] 触发Nginx重启(通过Supervisor)" >> "$LOG_FILE"
/xp/server/supervisor/supervisorctl -c /xp/server/supervisor/supervisord.conf restart nginx 2>> "$LOG_FILE"
echo "[$(date)] Nginx重启命令已执行" >> "$LOG_FILE"
}
# ========== 启动提示 ==========
echo "[$(date)] Nginx健康检查启动,仅检测URL:$CHECK_URL 返回码,每${CHECK_INTERVAL}秒检测1次" >> "$LOG_FILE"
# ========== 主循环(核心逻辑) ==========
while true; do
CODE=$(curl_check)
if [ "$CODE" = "200" ]; then
FAIL_COUNT=0
elif [ "$CODE" = "404" ] || [ "$CODE" = "500" ]; then
echo "[$(date)] 业务异常:URL返回码$CODE,需手动处理(不触发重启)" >> "$LOG_FILE"
FAIL_COUNT=0
else
FAIL_COUNT=$((FAIL_COUNT + 1))
echo "[$(date)] 检测失败:URL返回码$CODE,失败计数+1(当前:$FAIL_COUNT)" >> "$LOG_FILE"
if [ $FAIL_COUNT -eq $MAX_FAIL_TIMES ]; then
echo "[$(date)] 连续${MAX_FAIL_TIMES}次检测失败,触发修复逻辑" >> "$LOG_FILE"
force_clean
restart_nginx
FAIL_COUNT=0
echo "[$(date)] 修复逻辑执行完成,重置失败计数为0" >> "$LOG_FILE"
fi
fi
sleep $CHECK_INTERVAL
done
3. 一键管理脚本(nginx_manage.sh)
#!/bin/bash
LOG_DIR="/xp/server/supervisor/log"
[ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR" && chmod 755 "$LOG_DIR"
SUPERVISORCTL_BIN="/xp/server/supervisor/supervisorctl"
SUPERVISOR_CONF="/xp/server/supervisor/supervisord.conf"
# 基础依赖检查
check_dependencies() {
local error=0
if [ ! -x "$SUPERVISORCTL_BIN" ]; then
echo "[$(date)] 错误:$SUPERVISORCTL_BIN 不存在或无执行权限" >> "$LOG_DIR/nginx_manage.log"
error=1
fi
if [ ! -f "$SUPERVISOR_CONF" ]; then
echo "[$(date)] 错误:$SUPERVISOR_CONF 配置文件不存在" >> "$LOG_DIR/nginx_manage.log"
error=1
fi
if [ $error -ne 0 ]; then
exit 1
fi
}
check_dependencies
case "$1" in
start)
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF start nginx nginx_health 2>> "$LOG_DIR/nginx_manage.log"
echo "[$(date)] Nginx启动完成,状态如下:" >> "$LOG_DIR/nginx_manage.log"
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status >> "$LOG_DIR/nginx_manage.log" 2>&1
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status
;;
restart)
for PORT in 80 443 888; do
PID=$(ss -tunlp 2>/dev/null | grep -E ":$PORT\s" | awk '{print $7}' | cut -d'/' -f1 | head -1)
if [ -n "$PID" ] && [[ "$PID" =~ ^[0-9]+$ ]]; then
kill -9 "$PID" 2>/dev/null
fi
done
pgrep -f "nginx: master process" | xargs -r kill -9 2>/dev/null
pgrep -f "nginx: worker process" | xargs -r kill -9 2>/dev/null$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF restart nginx nginx_health 2>> "$LOG_DIR/nginx_manage.log"
echo "[$(date)] Nginx重启完成,状态如下:" >> "$LOG_DIR/nginx_manage.log"
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status >> "$LOG_DIR/nginx_manage.log" 2>&1
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status
;;
status)
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status nginx nginx_health
;;
*)
echo "使用方法:$0 {start|restart|status}"
exit 1
;;
esac
exit 0
LOG_DIR="/xp/server/supervisor/log"
[ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR" && chmod 755 "$LOG_DIR"
SUPERVISORCTL_BIN="/xp/server/supervisor/supervisorctl"
SUPERVISOR_CONF="/xp/server/supervisor/supervisord.conf"
# 基础依赖检查
check_dependencies() {
local error=0
if [ ! -x "$SUPERVISORCTL_BIN" ]; then
echo "[$(date)] 错误:$SUPERVISORCTL_BIN 不存在或无执行权限" >> "$LOG_DIR/nginx_manage.log"
error=1
fi
if [ ! -f "$SUPERVISOR_CONF" ]; then
echo "[$(date)] 错误:$SUPERVISOR_CONF 配置文件不存在" >> "$LOG_DIR/nginx_manage.log"
error=1
fi
if [ $error -ne 0 ]; then
exit 1
fi
}
check_dependencies
case "$1" in
start)
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF start nginx nginx_health 2>> "$LOG_DIR/nginx_manage.log"
echo "[$(date)] Nginx启动完成,状态如下:" >> "$LOG_DIR/nginx_manage.log"
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status >> "$LOG_DIR/nginx_manage.log" 2>&1
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status
;;
restart)
for PORT in 80 443 888; do
PID=$(ss -tunlp 2>/dev/null | grep -E ":$PORT\s" | awk '{print $7}' | cut -d'/' -f1 | head -1)
if [ -n "$PID" ] && [[ "$PID" =~ ^[0-9]+$ ]]; then
kill -9 "$PID" 2>/dev/null
fi
done
pgrep -f "nginx: master process" | xargs -r kill -9 2>/dev/null
pgrep -f "nginx: worker process" | xargs -r kill -9 2>/dev/null$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF restart nginx nginx_health 2>> "$LOG_DIR/nginx_manage.log"
echo "[$(date)] Nginx重启完成,状态如下:" >> "$LOG_DIR/nginx_manage.log"
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status >> "$LOG_DIR/nginx_manage.log" 2>&1
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status
;;
status)
$SUPERVISORCTL_BIN -c $SUPERVISOR_CONF status nginx nginx_health
;;
*)
echo "使用方法:$0 {start|restart|status}"
exit 1
;;
esac
exit 0
七、部署与验证命令(最终版)
# 1. 给脚本赋执行权限
chmod +x /xp/server/nginx/nginx_health.sh
chmod +x /xp/server/nginx/nginx_manage.sh# 2. 停止旧进程(如有)
/xp/server/supervisor/supervisorctl -c /xp/server/supervisor/supervisord.conf stop all# 3. 重启Supervisor加载新配置
pkill -9 supervisord
/xp/server/supervisor/supervisord -c /xp/server/supervisor/supervisord.conf# 4. 启动Nginx+健康检查
/xp/server/nginx/nginx_manage.sh start# 5. 验证检测是否生效(手动执行curl)
curl http://182.106.136.56 -m 2 -s -w "%{http_code}" -o /dev/null# 6. 常用排查命令
tail -f /xp/server/supervisor/log/nginx_health.log # 实时查看健康检查日志
/xp/server/nginx/nginx_manage.sh status # 查看Nginx状态
/xp/server/nginx/nginx_manage.sh restart # 一键重启Nginx
chmod +x /xp/server/nginx/nginx_health.sh
chmod +x /xp/server/nginx/nginx_manage.sh# 2. 停止旧进程(如有)
/xp/server/supervisor/supervisorctl -c /xp/server/supervisor/supervisord.conf stop all# 3. 重启Supervisor加载新配置
pkill -9 supervisord
/xp/server/supervisor/supervisord -c /xp/server/supervisor/supervisord.conf# 4. 启动Nginx+健康检查
/xp/server/nginx/nginx_manage.sh start# 5. 验证检测是否生效(手动执行curl)
curl http://182.106.136.56 -m 2 -s -w "%{http_code}" -o /dev/null# 6. 常用排查命令
tail -f /xp/server/supervisor/log/nginx_health.log # 实时查看健康检查日志
/xp/server/nginx/nginx_manage.sh status # 查看Nginx状态
/xp/server/nginx/nginx_manage.sh restart # 一键重启Nginx
继续阅读




