ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux系统资源监控Shell脚本实战指南

Linux系统资源监控Shell脚本实战指南

1. 为什么需要系统资源监控脚本?

在Linux服务器运维中,系统资源监控是保证服务稳定性的第一道防线。我管理过上百台生产服务器,最深刻的教训就是:当收到用户投诉时,问题往往已经发生了半小时以上。传统的监控工具如Zabbix、Prometheus虽然功能强大,但在以下场景中显得"笨重":

  • 临时测试环境需要快速部署监控
  • 老旧设备无法承担额外监控服务开销
  • 需要定制化的告警逻辑(比如只关注特定时段的CPU峰值)

一个轻量级的Shell脚本就能完美解决这些问题。上周我帮客户部署的监控脚本,在内存泄漏导致OOM前成功触发告警,避免了线上事故。这种脚本的核心价值在于:

  1. 实时性:通过crontab可以实现分钟级监控
  2. 低开销:纯Shell实现,不依赖额外服务
  3. 可定制:告警阈值、通知方式自由定义

2. 基础监控命令深度解析

2.1 CPU使用率获取的三种姿势

最常用的top -bn1命令其实有坑:首次运行会有不准确的初始值。经过多次测试,我发现这些方法更可靠:

# 方法1:mpstat(需安装sysstat) cpu_usage=$(mpstat 1 1 | awk '/Average/{print 100-$NF}') # 方法2:/proc/stat计算(推荐) read cpu a b c idle rest < /proc/stat total=$((a+b+c+idle)) cpu_usage=$((100*(total-idle)/total)) # 方法3:vmstat快速采样 cpu_usage=$(vmstat 1 2 | tail -1 | awk '{print 15}')

提示:生产环境中建议使用方法2,它不依赖额外工具且计算准确。注意/proc/stat的数值是自启动累计值,需要两次采样计算差值。

2.2 内存监控的隐藏陷阱

free -m的输出看似简单,但缓存(buffer/cache)的处理是关键。很多脚本错误地把可用内存算作free列,实际上应该:

total_mem=$(free -m | awk '/Mem:/{print $2}') used_mem=$(free -m | awk '/Mem:/{print $3}') buff_cache=$(free -m | awk '/Mem:/{print $6}') real_used=$((used_mem - buff_cache)) mem_usage=$((real_used * 100 / total_mem))

这个算法更真实反映应用内存压力,避免缓存干扰判断。上周有个案例:某服务器free显示只剩200MB,但实际应用内存充足,就是缓存占了3GB。

2.3 磁盘容量检查的高级技巧

df -h的常规用法有几个缺陷:

  1. 可能包含tmpfs等虚拟文件系统
  2. 挂载点路径含空格时会解析错误

改进方案:

# 只监控物理磁盘(排除tmpfs, overlay等) df -h | awk '/^\/dev\//{print $5 " " $6}' | while read usage mount; do usage=${usage%\%} if [ $usage -gt 90 ]; then echo "警报:$mount 使用率 $usage%" fi done

对于LVM卷组,还需要vgs和lvs命令补充检查。曾遇到一个坑:物理卷已满但df显示充足,导致数据写入失败。

3. 工业级告警脚本实现

3.1 参数化设计模板

好的监控脚本应该像这样可配置:

#!/bin/bash # 配置区(用户自定义) THRESHOLD_CPU=90 THRESHOLD_MEM=85 THRESHOLD_DISK=90 ALERT_EMAIL="admin@example.com" LOG_FILE="/var/log/system_monitor.log"

3.2 多级告警机制实现

简单的阈值触发太粗糙,我推荐分级告警:

check_cpu() { local level=$1 if [ $cpu_usage -ge $level ]; then local msg="CPU使用率 ${cpu_usage}% 超过阈值 ${level}%" echo "$(date) - $msg" >> $LOG_FILE send_alert "CPU警报" "$msg" fi } # 分级检查 check_cpu 80 # 警告级 check_cpu 90 # 严重级 check_cpu 95 # 紧急级

3.3 告警收敛与防抖动

直接邮件轰炸是不可接受的,必须实现:

  1. 时间窗口收敛:相同告警1小时内不重复发送
  2. 状态恢复通知:问题解决后发送恢复提示
  3. 抖动过滤:短暂峰值不触发告警

示例实现:

last_alert_time=0 alert_cooldown=3600 # 1小时冷却 send_alert() { local now=$(date +%s) if [ $((now - last_alert_time)) -gt $alert_cooldown ]; then mail -s "$1" $ALERT_EMAIL <<< "$2" last_alert_time=$now fi }

4. 生产环境部署指南

4.1 crontab配置的隐藏坑

新手常犯的错误配置:

* * * * * /path/to/script.sh # 错误!会重复执行

正确做法是加锁防止并发:

* * * * * flock -xn /tmp/system_monitor.lock -c "/path/to/script.sh"

4.2 日志轮转策略

不加控制的日志会撑爆磁盘,用logrotate配置:

# /etc/logrotate.d/system_monitor /var/log/system_monitor.log { daily rotate 7 compress missingok notifempty }

4.3 性能优化技巧

当监控大量服务器时,这些优化很关键:

  1. 用awk替代grep|cut管道
  2. 减少子进程创建(如用${var%\%}替代echo $var | tr -d '%')
  3. 合并磁盘检查(单次df读取处理所有分区)

实测优化后脚本执行时间从800ms降到120ms。

5. 异常案例处理手册

5.1 虚假CPU高峰排查

遇到CPU持续告警时,按此流程排查:

  1. top -H -p <PID>定位具体线程
  2. perf top -p <PID>分析热点函数
  3. cat /proc/<PID>/stack查看调用栈

曾发现一个"假警报":定时任务导致updatedb全盘扫描,CPU短暂冲高属正常现象。

5.2 内存泄漏诊断

当内存持续增长时:

# 1. 按内存排序进程 ps -eo pid,comm,%mem --sort=-%mem | head # 2. 检查slab内存 cat /proc/meminfo | grep Slab # 3. 追踪进程内存分配 valgrind --tool=memcheck --leak-check=full ./process

5.3 磁盘空间"幽灵"占用

当df显示已用空间与du统计不符时:

  1. 检查被删除但未释放的文件:
    lsof | grep deleted
  2. 排查Docker等容器日志:
    find /var/lib/docker/containers -name "*.log" -size +1G

6. 脚本安全加固方案

6.1 权限最小化原则

# 错误做法 chmod 777 script.sh # 正确配置 chown root:root script.sh chmod 750 script.sh

6.2 输入验证与消毒

所有外部输入必须验证:

validate_threshold() { if ! [[ "$1" =~ ^[0-9]+$ ]] || [ $1 -gt 100 ]; then echo "错误阈值:$1" >&2 exit 1 fi }

6.3 敏感信息保护

告警邮件中的服务器信息需要脱敏:

# 替换IP最后一位 server_ip=$(hostname -I | awk '{print $1}') masked_ip=$(echo $server_ip | sed 's/\.[0-9]*$/.xxx/')

7. 扩展监控维度

7.1 温度监控

服务器硬件温度也不容忽视:

# 需要lm-sensors temp=$(sensors | awk '/Package id/{print $4}' | tr -d '+°C') if [ $temp -gt 90 ]; then echo "CPU温度过高:${temp}°C" fi

7.2 网络连接监控

检测异常连接数:

# ESTABLISHED连接数 conn_count=$(ss -s | awk '/TCP:/{print $4}') if [ $conn_count -gt 1000 ]; then echo "连接数异常:$conn_count" fi

7.3 进程存活检查

关键进程监控模板:

check_process() { if ! pgrep -x "$1" >/dev/null; then echo "进程 $1 未运行!" systemctl restart $1 fi }

经过多年实战检验,这套监控方案在几十家企业环境中稳定运行。最关键的体会是:监控脚本不是写完就完,需要根据业务特点持续调整阈值和检查项。比如电商系统需要特别关注大促时段的CPU波动,而文件存储服务器则要重点监控inode使用情况。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进