bash
#!/bin/bash
# =============================================================================
# Jenkins 服务中断 · 系统化排查脚本(哲学增强版)
# 哲学基石:本体论定性 → 奥卡姆筛选 → 辩证决策 → 实用进化 → 无为防御 → 整体洞察
# =============================================================================
set -o pipefail
# ---------- 可配置变量(实用主义:根据你的环境调整)----------
JENKINS_HOME="${JENKINS_HOME:-/var/lib/jenkins}"
JENKINS_PORT="${JENKINS_PORT:-8080}"
JENKINS_USER="${JENKINS_USER:-jenkins}"
LOG_LINES=50 # 抓取日志行数
HIGH_DISK_THRESHOLD=90 # 磁盘使用率告警阈值%
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# ---------- 1. 本体论定性:精确锁定"中断"的本质 ----------
echo -e "${GREEN}=== 第一步:本体论定性 ------ 定义故障实体 ===${NC}"
# 1.1 进程是否存在?
if pgrep -u "$JENKINS_USER" -f jenkins > /dev/null 2>&1; then
JENKINS_PID=$(pgrep -u "$JENKINS_USER" -f jenkins | head -1)
echo -e "${GREEN}[进程] 存在 (PID: $JENKINS_PID)${NC}"
PROCESS_EXISTS=true
else
echo -e "${RED}[进程] 不存在${NC}"
PROCESS_EXISTS=false
fi
# 1.2 端口是否响应?(超时 3 秒)
if timeout 3 bash -c "echo > /dev/tcp/localhost/$JENKINS_PORT" 2>/dev/null; then
echo -e "${GREEN}[端口] $JENKINS_PORT 可达${NC}"
PORT_OPEN=true
else
echo -e "${RED}[端口] $JENKINS_PORT 不可达${NC}"
PORT_OPEN=false
fi
# 1.3 最后一次活动时间(systemd 管理的服务)
if systemctl is-active jenkins >/dev/null 2>&1; then
echo -e "${YELLOW}[systemd] 服务状态: active${NC}"
else
echo -e "${RED}[systemd] 服务状态: inactive / failed${NC}"
fi
# 【手工检验注释】
# 若进程存在但端口不可达 → 可能是假死(hang),需查看线程栈: jstack -l $JENKINS_PID
# 若进程完全消失 → 进入第二步查找致命信号,重点看 OOM / 磁盘。
echo ""
# ---------- 2. 奥卡姆剃刀:致命信号层,一击致命 ----------
echo -e "${GREEN}=== 第二步:奥卡姆剃刀 ------ 致命信号层 ===${NC}"
FATAL_SIGNAL_FOUND=false
# 2.1 OOM Killer
echo "[检查] 内核 OOM Killer 记录..."
OOM_LOG=$(dmesg -T 2>/dev/null | grep -i "out of memory.*java" | tail -5)
if [ -n "$OOM_LOG" ]; then
echo -e "${RED}[致命] 发现 OOM 记录!Jenkins 被系统杀死。${NC}"
echo "$OOM_LOG"
FATAL_SIGNAL_FOUND=true
else
# 也检查 /var/log/messages(某些发行版)
OOM_LOG2=$(grep -i "out of memory.*java" /var/log/messages 2>/dev/null | tail -5)
if [ -n "$OOM_LOG2" ]; then
echo -e "${RED}[致命] /var/log/messages 中发现 OOM 记录${NC}"
echo "$OOM_LOG2"
FATAL_SIGNAL_FOUND=true
else
echo "[OOM] 未发现明显 OOM 记录"
fi
fi
# 2.2 磁盘空间
echo "[检查] 磁盘空间 (JENKINS_HOME 及关键分区)..."
DISK_FULL=false
for PART in / "$JENKINS_HOME" /tmp /var; do
if [ -d "$PART" ]; then
USAGE=$(df -h "$PART" | awk 'NR==2 {print $5}' | sed 's/%//')
INODE_USAGE=$(df -i "$PART" | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -ge "$HIGH_DISK_THRESHOLD" ]; then
echo -e "${RED}[致命] $PART 磁盘使用率 ${USAGE}% (阈值 ${HIGH_DISK_THRESHOLD}%)${NC}"
FATAL_SIGNAL_FOUND=true
DISK_FULL=true
fi
if [ "$INODE_USAGE" -ge "$HIGH_DISK_THRESHOLD" ]; then
echo -e "${RED}[致命] $PART inode 使用率 ${INODE_USAGE}% (可能小文件耗尽)${NC}"
FATAL_SIGNAL_FOUND=true
fi
fi
done
if ! $DISK_FULL; then
echo "[磁盘] 空间与 inode 正常"
fi
# 2.3 文件句柄(仅当进程存在时检查)
if $PROCESS_EXISTS; then
FD_COUNT=$(ls /proc/$JENKINS_PID/fd 2>/dev/null | wc -l)
FD_LIMIT=$(grep "Max open files" /proc/$JENKINS_PID/limits 2>/dev/null | awk '{print $4}')
if [ -n "$FD_LIMIT" ] && [ "$FD_COUNT" -ge $((FD_LIMIT * 80 / 100)) ]; then
echo -e "${RED}[致命] 文件句柄使用量接近上限: $FD_COUNT / $FD_LIMIT${NC}"
FATAL_SIGNAL_FOUND=true
else
echo "[句柄] 正常 ($FD_COUNT/$FD_LIMIT)"
fi
fi
# 2.4 JVM 崩溃日志
echo "[检查] JVM 致命错误日志..."
HS_ERR=$(find "$JENKINS_HOME" /tmp /var/log -name "hs_err_pid*" -mmin -60 2>/dev/null | head -1)
if [ -n "$HS_ERR" ]; then
echo -e "${RED}[致命] 发现 JVM 崩溃日志: $HS_ERR${NC}"
head -20 "$HS_ERR"
FATAL_SIGNAL_FOUND=true
else
echo "[JVM] 未发现 hs_err_pid 文件"
fi
# 奥卡姆剃刀决策点:若已发现致命信号,可立即提示
if $FATAL_SIGNAL_FOUND; then
echo ""
echo -e "${YELLOW}★★★ 奥卡姆剃刀结论:已捕获致命信号,建议直接针对上述发现处理,无需继续深挖。★★★${NC}"
# 【手工检验注释】
# - 若是 OOM:用 `free -h`、`ps aux --sort=-%mem | head` 分析内存分布
# - 若是磁盘满:用 `du -sh /var/lib/jenkins/jobs/*/builds/* | sort -rh | head` 找出大目录
# - 若为 JVM 崩溃:检查 hs_err 文件中 "Internal Error" 和 "Stack" 段,考虑 JDK 版本与插件兼容性
# 然后按辩证决策(第三步)决定立即重启还是继续取证。
fi
echo ""
# ---------- 3. 辩证决策:恢复 vs 深挖 ----------
echo -e "${GREEN}=== 第三步:辩证决策 ------ 恢复与根因的权衡 ===${NC}"
# 尝试简单判断是否处于业务高峰(例如当前时间)
HOUR=$(date +%H)
if [ "$HOUR" -ge 9 ] && [ "$HOUR" -le 18 ]; then
PEAK_TIME=true
echo "[时间] 当前处于业务高峰时段 (9-18点)"
else
PEAK_TIME=false
echo "[时间] 当前处于低峰时段"
fi
# 【手工检验注释】
# 这里需要人工介入决策。脚本仅给出建议:
# 若为业务高峰且致命信号已明确,建议立即执行预案恢复,但务必先执行以下命令保存现场:
# journalctl -u jenkins --since "30 minutes ago" > /tmp/jenkins_crash_snapshot.log
# cp -r /var/lib/jenkins /tmp/jenkins_home_backup_$(date +%Y%m%d_%H%M) # 注意磁盘空间
# 然后重启服务: systemctl restart jenkins
# 若为低峰或致命信号不明确,请继续执行本脚本的后续深度分析部分。
echo ""
read -p "是否继续深度分析?(y/n,默认 y): " CONTINUE
CONTINUE=${CONTINUE:-y}
if [ "$CONTINUE" != "y" ]; then
echo "已跳过深度分析。请根据需要重启服务或人工介入。"
exit 0
fi
# ---------- 4. 整体论与深度分析 ----------
echo -e "${GREEN}=== 第四步:整体论深度分析 ------ 关联与涌现行为 ===${NC}"
# 4.1 Jenkins 自身日志(最近的错误)
echo "[日志] 抓取 Jenkins 服务日志最后 $LOG_LINES 行..."
journalctl -u jenkins --no-pager -n $LOG_LINES 2>/dev/null || \
tail -n $LOG_LINES "$JENKINS_HOME/../jenkins.log" 2>/dev/null || \
echo "无法获取 Jenkins 日志,请手工检查 /var/log/jenkins/"
# 4.2 系统资源全景(关联 OOM 时刻)
echo ""
echo "[系统] 内存与 Swap 现状:"
free -h
echo ""
echo "[系统] 当前 CPU 负载与进程 Top 5:"
top -bn1 | head -15
echo ""
echo "[系统] 当前磁盘 I/O 等待:"
iostat -x 1 2 2>/dev/null | tail -20 || echo "iostat 未安装"
# 4.3 依赖连通性检查(整体论视角)
echo ""
echo "[依赖] 测试关键外部服务连通性..."
# Git 仓库连通性示例
GIT_REPO=$(grep -r "git@" "$JENKINS_HOME/jobs" -l 2>/dev/null | head -1)
if [ -f "$GIT_REPO" ]; then
REMOTE_URL=$(grep -oP '(?<=<url>)[^<]+' "$GIT_REPO" | head -1)
if [ -n "$REMOTE_URL" ]; then
timeout 5 git ls-remote "$REMOTE_URL" >/dev/null 2>&1 && \
echo "[Git] $REMOTE_URL 可达" || \
echo -e "${RED}[Git] $REMOTE_URL 不可达${NC}"
fi
else
echo "[Git] 未找到仓库配置,跳过"
fi
# 检查本地端口占用情况(监控系统滥用短连接)
echo ""
echo "[网络] TIME_WAIT 连接数量:"
ss -s | grep -i timewait || echo "无 TIME_WAIT 统计"
# 【手工检验注释】
# - 若怀疑某个外部依赖(如 Git、Nexus)拖慢 Jenkins,可用 telnet/tcping 测试延迟。
# - 若 TIME_WAIT 数量极高(数千),考虑优化内核参数或调整监控频率。
# - 使用 eBPF 工具进一步分析:`sudo filetop -C 5` 可实时看到哪个进程在写盘最多。
echo ""
# ---------- 5. 数据完整性(当进程尝试启动但立即退出时必需)----------
echo -e "${GREEN}=== 第五步:数据完整性检查 ===${NC}"
if $PROCESS_EXISTS; then
echo "进程尚存,跳过数据完整性检查。若要验证,请先停止 Jenkins。"
else
# 尝试前台启动并捕获错误(只运行5秒,用于诊断)
echo "[数据] 尝试前台启动 Jenkins 5 秒以捕获配置错误..."
sudo -u "$JENKINS_USER" timeout 5 java -jar /usr/share/jenkins/jenkins.war \
--httpPort=$JENKINS_PORT --webroot=/var/cache/jenkins/war \
2>&1 | tee /tmp/jenkins_foreground.log
echo ""
echo "[数据] 常见错误检查:"
grep -i "saxparse\|xstream\|config.xml" /tmp/jenkins_foreground.log && \
echo -e "${RED}发现 XML 配置损坏,请恢复备份或检查对应文件${NC}" || \
echo "未发现 XML 解析错误"
grep -i "noclassdeffound\|linkageerror" /tmp/jenkins_foreground.log && \
echo -e "${RED}发现插件冲突或类加载错误,考虑进入安全模式或移除最近更新的插件${NC}" || \
echo "未发现插件冲突"
fi
# 【手工检验注释】
# 若前台启动报错定位到具体文件,如 /var/lib/jenkins/jobs/xxx/config.xml 损坏:
# cp /var/lib/jenkins/jobs/xxx/config.xml /var/lib/jenkins/jobs/xxx/config.xml.bak
# 然后尝试用空白配置替换或手动修复 XML。
# 若为插件冲突,可暂时移动插件: mv /var/lib/jenkins/plugins/xxx.jpi /tmp/
echo ""
# ---------- 6. 无为防御:展示当前自愈配置 ----------
echo -e "${GREEN}=== 第六步:无为防御 ------ 自愈配置现状 ===${NC}"
echo "[cron] 检查磁盘清理任务..."
crontab -l 2>/dev/null | grep -i "jenkins\|clean\|disk" || echo "未找到相关 cron 任务,建议添加磁盘自动清理"
echo "[systemd] 检查内存限制..."
if systemctl cat jenkins 2>/dev/null | grep -q "MemoryMax\|MemoryHigh"; then
systemctl cat jenkins 2>/dev/null | grep -E "MemoryMax|MemoryHigh"
else
echo "未设置 MemoryMax/MemoryHigh,建议为 Jenkins 配置内存限制以防 OOM"
fi
echo "[健康检查] 建议配置 Jenkins 健康检查端点 (如 /login)"
# 【手工检验注释】
# 无为防御的最佳实践:
# - 添加 cron: 0 3 * * * find /var/lib/jenkins/jobs/*/builds -maxdepth 0 -mtime +7 -exec rm -rf {} \;
# - 编辑 /etc/systemd/system/jenkins.service.d/override.conf 添加:
# [Service]
# MemoryHigh=4G
# MemoryMax=5G
# - 使用 systemd 的 ExecStartPre 校验关键 XML:
# ExecStartPre=/usr/bin/xmllint --noout /var/lib/jenkins/config.xml
echo ""
echo -e "${GREEN}===== 排查脚本执行完毕 =====${NC}"
echo "请根据上述输出定位根因。若为致命信号且已执行恢复,请将本次日志保存用于事后分析。"