Taoの小窝
首页项目博客照片墙音乐技能栈说说杂谈友链关于
封面

企业级 Linux 服务器自动化巡检系统:从 Shell 命令到生产级运维脚本

写作时间:2025-11-08 11:00:00
# 运维
# Linux
# Shell
# 自动化
# 监控

项目背景

在运维工作中,服务器的健康状态直接决定了线上服务的稳定性。传统的人工巡检方式不仅耗时,而且容易遗漏关键指标。本项目设计并实现了一套基于 Shell 脚本的企业级 Linux 服务器自动化巡检系统,用于对 CentOS 7 生产环境进行每日健康检查,覆盖 CPU、内存、磁盘、系统负载、关键服务、僵尸进程和网络连通性等核心指标。

项目目标

  • 实现服务器核心指标的自动化采集与告警
  • 建立标准化的巡检报告输出机制
  • 通过 crontab 实现每日定时自动巡检
  • 替代人工巡检,提升运维效率与故障发现速度

技术栈

技术 用途
Shell / Bash 脚本开发语言
top / free / df 系统性能指标采集
systemctl 服务状态管理
journalctl 服务日志查询
bc 浮点数计算与比较
crontab 定时任务调度
ping 网络连通性检测

项目结构

server-inspection/
├── inspect_simple.sh    # 最简版脚本:纯命令拼接,适合入门理解
├── inspect_full.sh      # 完整版脚本:模块化设计,生产环境可用
├── crontab_guide.sh     # 定时任务配置教程
├── resume_guide.sh      # 简历撰写指南
├── interview_qa.sh      # 面试问答话术
└── 项目详细讲解.md       # 项目文档

核心实现

1. 最简版脚本 inspect_simple.sh

最简版脚本完全由命令拼接组成,没有 if 判断、循环和函数,适合 Shell 新手快速理解 Linux 运维命令的组合使用。

#!/bin/bash
# 最简版服务器巡检脚本 (CentOS 7)

TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "========== 服务器巡检报告 [$TIME] =========="

# CPU使用率:取 top 中 idle 的相反值
echo "CPU使用率: $(top -bn1 | grep "Cpu(s)" | awk '{print 100-$8"%"}')"

# 内存使用率
echo "内存使用率: $(free -m | grep Mem | awk '{printf "%.1f%%", $3/$2*100}')"

# 磁盘使用率(根分区)
echo "磁盘使用率: $(df -h / | tail -1 | awk '{print $5}')"

# 服务状态
echo "MySQL状态: $(systemctl is-active mysqld 2>/dev/null || echo '未安装')"
echo "Nginx状态: $(systemctl is-active nginx 2>/dev/null || echo '未安装')"

echo "============================================"

逐行讲解:

  • #!/bin/bash:Shebang 行,指定使用 Bash 解释器执行。
  • $(date ...):命令替换,将当前时间存入变量 TIME。
  • top -bn1:top 的批处理模式,刷新一次后退出。
  • grep "Cpu(s)" | awk '{print 100-$8"%"}':提取 CPU 空闲率,用 100 减去得到使用率。
  • free -m | grep Mem:提取内存总量行。
  • df -h / | tail -1 | awk '{print $5}':查看根分区磁盘使用率。
  • systemctl is-active mysqld 2>/dev/null || echo '未安装':检查服务状态,2>/dev/null 丢弃错误输出,服务不存在时显示"未安装"。

2. 完整版脚本 inspect_full.sh

完整版脚本按照配置层、初始化层、工具函数层、检测函数层、主流程层、清理层进行模块化设计,是企业级生产环境可用的版本。

配置区

CPU_THRESHOLD=80          # CPU 告警阈值
MEM_THRESHOLD=80          # 内存告警阈值
DISK_THRESHOLD=80         # 磁盘告警阈值
LOAD_THRESHOLD=5          # 系统负载告警阈值

REPORT_DIR="/var/log/server_inspection"
ALERT_LOG="$REPORT_DIR/alert.log"
REPORT_FILE="$REPORT_DIR/report_$(date +%Y%m%d).log"
REPORT_KEEP_DAYS=30

将阈值、目录、保留天数等配置变量化,方便后续修改,无需改动核心逻辑。

工具函数

write_alert() {
    echo "[ALERT] [$NOW] $1" | tee -a "$ALERT_LOG"
    ALERT_COUNT=$((ALERT_COUNT + 1))
}

write_log() {
    echo "$1" >> "$REPORT_FILE"
}

check_threshold() {
    local current=$1
    local threshold=$2
    local name=$3

    if [ "$(echo "$current > $threshold" | bc)" -eq 1 ]; then
        write_alert "$name 超过阈值!当前值: ${current}%, 阈值: ${threshold}%"
        return 1
    else
        write_log "[OK] $name 正常,当前值: ${current}%"
        return 0
    fi
}
  • write_alert 使用 tee -a 同时输出到控制台和告警日志文件,并累加告警计数器。
  • check_threshold 使用 bc 进行浮点数比较,解决 Shell 本身不支持小数比较的问题。
  • local 关键字限制变量作用域在函数内部,避免污染全局命名空间。

CPU 检测

check_cpu() {
    write_log ""
    write_log "========== CPU 检测 =========="
    local cpu_idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | cut -d'%' -f1)
    local cpu_used=$(echo "100 - $cpu_idle" | bc)
    write_log "CPU 使用率: ${cpu_used}%"
    write_log "CPU 空闲率: ${cpu_idle}%"
    check_threshold "$cpu_used" "$CPU_THRESHOLD" "CPU使用率"
}

系统负载检测

check_load() {
    write_log ""
    write_log "========== 系统负载检测 =========="
    local cpu_cores=$(grep -c processor /proc/cpuinfo)
    local load_1min=$(awk '{print $1}' /proc/loadavg)
    local load_5min=$(awk '{print $2}' /proc/loadavg)
    local load_15min=$(awk '{print $3}' /proc/loadavg)

    write_log "CPU核心数: ${cpu_cores}"
    write_log "系统负载: 1分钟=${load_1min}, 5分钟=${load_5min}, 15分钟=${load_15min}"

    if [ "$(echo "$load_1min > $LOAD_THRESHOLD" | bc)" -eq 1 ]; then
        write_alert "系统负载过高!1分钟负载: ${load_1min}, 阈值: ${LOAD_THRESHOLD}"
    else
        write_log "[OK] 系统负载正常"
    fi
}

/proc/loadavg 输出三个数值,分别代表 1 分钟、5 分钟、15 分钟的平均负载。通过对比 CPU 核心数,可以判断当前负载是否处于合理区间。

内存检测

check_memory() {
    write_log ""
    write_log "========== 内存检测 =========="
    local mem_total=$(free -m | grep Mem | awk '{print $2}')
    local mem_used=$(free -m | grep Mem | awk '{print $3}')
    local mem_available=$(free -m | grep Mem | awk '{print $7}')
    local mem_usage=$(echo "scale=1; $mem_used / $mem_total * 100" | bc)

    write_log "内存总量: ${mem_total}MB"
    write_log "内存已用: ${mem_used}MB"
    write_log "内存可用: ${mem_available}MB"
    write_log "内存使用率: ${mem_usage}%"

    check_threshold "$mem_usage" "$MEM_THRESHOLD" "内存使用率"
}

磁盘检测

check_disk() {
    write_log ""
    write_log "========== 磁盘检测 =========="
    df -h | grep -vE 'tmpfs|devtmpfs|Filesystem|cdrom' | while read -r line; do
        [ -z "$line" ] && continue

        local disk_usage=$(echo "$line" | awk '{print $5}' | cut -d'%' -f1)
        local disk_mount=$(echo "$line" | awk '{print $6}')
        local disk_size=$(echo "$line" | awk '{print $2}')
        local disk_avail=$(echo "$line" | awk '{print $4}')

        write_log "挂载点: ${disk_mount}, 总大小: ${disk_size}, 可用: ${disk_avail}, 使用率: ${disk_usage}%"

        if [ "$disk_usage" -gt "$DISK_THRESHOLD" ]; then
            write_alert "磁盘 ${disk_mount} 使用率过高!当前值: ${disk_usage}%, 阈值: ${DISK_THRESHOLD}%"
        fi
    done
}

通过 grep -vE 排除临时文件系统,只检查真实磁盘分区。使用 while read -r line 逐行处理 df -h 的输出。

服务状态检测

check_service() {
    local service_label=$1
    local service_name=$2

    write_log ""
    write_log "========== ${service_label} 服务检测 =========="

    if ! systemctl list-unit-files | grep -q "^${service_name}"; then
        write_log "${service_label} 服务未安装"
        write_alert "${service_label} 服务未安装!"
        return
    fi

    local status=$(systemctl is-active "$service_name" 2>/dev/null)

    if [ "$status" = "active" ]; then
        write_log "[OK] ${service_label} 运行正常,状态: ${status}"
        local enabled_status=$(systemctl is-enabled "$service_name" 2>/dev/null)
        write_log "开机自启: ${enabled_status}"
        if [ "$enabled_status" != "enabled" ]; then
            write_alert "${service_label} 未设置开机自启!"
        fi
    else
        write_alert "${service_label} 服务异常!状态: ${status}"
        write_log "最近服务日志:"
        journalctl -u "$service_name" --no-pager -n 5 >> "$REPORT_FILE" 2>/dev/null
    fi
}

该函数为通用服务检测函数,接受服务显示名称和 systemctl 服务名两个参数。除了检查运行状态,还会检查是否开机自启,服务异常时自动拉取最近 5 条日志。

僵尸进程检测

check_zombie() {
    write_log ""
    write_log "========== 僵尸进程检测 =========="
    local zombie_count=$(ps aux | awk '{if($8=="Z") print}' | wc -l)
    write_log "僵尸进程数: ${zombie_count}"

    if [ "$zombie_count" -gt 0 ]; then
        write_alert "存在 ${zombie_count} 个僵尸进程!"
        ps aux | awk '{if($8=="Z") print}' >> "$REPORT_FILE"
    else
        write_log "[OK] 无僵尸进程"
    fi
}

网络连通性检测

check_network() {
    write_log ""
    write_log "========== 网络连通性检测 =========="
    if ping -c 1 -W 2 114.114.114.114 > /dev/null 2>&1; then
        write_log "[OK] 外网连通正常"
    else
        write_alert "外网不通!"
    fi
}

> /dev/null 2>&1 将标准输出和标准错误都丢弃,只保留命令的退出状态。

主流程

main() {
    cat > "$REPORT_FILE" << EOF
============================================================
          服务器巡检报告
          生成时间: ${NOW}
          服务器主机名: $(hostname)
          服务器IP: $(hostname -I | awk '{print $1}')
============================================================
EOF

    check_cpu
    check_load
    check_memory
    check_disk
    check_service "MySQL" "mysqld"
    check_service "Nginx" "nginx"
    check_zombie
    check_network

    cat >> "$REPORT_FILE" << EOF

============================================================
  巡检完成
  告警数量: ${ALERT_COUNT}
  报告路径: ${REPORT_FILE}
  告警日志: ${ALERT_LOG}
============================================================
EOF
}

日志自动清理

cleanup_old_reports() {
    find "$REPORT_DIR" -name "report_*.log" -mtime +$REPORT_KEEP_DAYS -delete 2>/dev/null
    find "$REPORT_DIR" -name "alert.log" -size +10M -exec mv {} "${ALERT_LOG}.old" \; 2>/dev/null
}

自动删除超过 30 天的历史报告,并对超过 10MB 的告警日志进行轮转,避免磁盘空间被占满。

定时任务配置

通过 crontab -e 添加定时任务,实现每天凌晨 2 点自动巡检:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

# 每天凌晨2点执行巡检
0 2 * * * /opt/server-inspection/inspect_full.sh >> /var/log/server_inspection/cron.log 2>&1

在 crontab 中显式设置 SHELL 和 PATH 非常重要,因为 cron 执行时的环境变量很少,很多命令可能找不到。>> /var/log/xxx.log 2>&1 将正常输出和错误输出都追加到日志文件中,便于后续排查。

项目成果

  • 7 项核心指标全自动巡检:CPU、内存、磁盘、系统负载、MySQL、Nginx、僵尸进程、网络连通性。
  • 巡检耗时从人工 30 分钟降至脚本 5 秒以内。
  • 故障发现方式从被动等待用户反馈变为主动告警通知。
  • 巡检报告可追溯,支持历史查询和故障复盘。
  • 日志自动轮转清理,避免磁盘空间被历史日志占满。

面试高频问题

Q1:脚本是怎么设计的?

采用分层设计思想:

  1. 配置层:阈值、路径、保留天数变量化。
  2. 检测层:7 个独立函数分别负责不同指标。
  3. 告警层:通用阈值比较函数 + 告警写入函数。
  4. 调度层:crontab 定时执行 + 自动清理旧报告。

Q2:脚本在 crontab 里不执行怎么排查?

  1. systemctl status crond 检查 cron 服务是否运行。
  2. crontab -l 检查配置是否正确。
  3. tail -f /var/log/cron 检查 cron 是否调度。
  4. 检查脚本权限 chmod +x、Shebang 行、绝对路径。
  5. 通过 >> /path.log 2>&1 查看具体报错。

Q3:磁盘使用率超过 90% 怎么处理?

只做告警,不做自动清理。因为自动清理磁盘有风险,可能误删重要数据。正确的做法是:

  1. 通过 df -h 定位高使用率分区。
  2. 通过 du -sh * 定位大文件或大目录。
  3. 人工确认后,再决定是归档、压缩还是删除。

写在最后

这个项目虽然代码量不大,但完整覆盖了 Shell 脚本开发、Linux 系统管理、服务监控、定时任务、日志管理等运维核心技能。通过最简版到完整版的渐进式设计,既能帮助初学者理解基础命令,也能作为生产环境的运维工具直接使用。

avatar

Tao

在数据与代码间探索的普通人 / 数据科学与大数据技术专业大三学生,坐标陕西西安。热爱技术实践与数据分析,正在找测试、数据分析方向的实习。

RECOMMENDED

Python + Pytest + Requests 接口自动化测试框架:从零搭建可维护的分层式测试工程

2025-12-20 12:00:00

数据分析入门:从数据清洗到可视化

2025-09-15 10:00:00

从零开始的数据分析学习项目:清洗、探索、可视化与机器学习入门

2025-08-15 10:00:00

Table of Contents