一、应用场景:为什么我们需要监控脚本?

在服务器运维中,资源监控脚本是守护系统健康的"哨兵"。它们持续跟踪CPU、内存、磁盘等关键指标,当某个服务突然内存泄漏或磁盘爆满时,这些脚本能第一时间发出警报。然而,当哨兵本身出现故障时——比如脚本意外终止、数据采集错误或者触发误报——整个监控体系就可能陷入瘫痪。


二、常见异常现象与问题根源

1. 脚本突然终止
#!/bin/bash
# 模拟内存监控时意外退出
while true; do
    free_mem=$(free -m | awk '/Mem/{print $4}')  # 获取可用内存
    echo "[$(date)] 可用内存:${free_mem}MB"
    
    # 未处理除零错误
    load_avg=$(awk '{print $1}' /proc/loadavg)
    result=$((free_mem / load_avg))  # 当load_avg为0时崩溃
    sleep 10
done

触发现象:当系统负载极低时/proc/loadavg可能返回0,导致除法运算错误

2. 数据采集偏差
# 有问题的磁盘监控片段
disk_usage=$(df -h | grep '/dev/sda1' | awk '{print $5}')

隐患:当磁盘挂载点变更或使用LVM时,/dev/sda1可能被替换为其他标识符


三、深度排查法

1. 启用脚本调试模式
#!/bin/bash
set -x   # 开启命令回显
set -e   # 任何语句执行失败立即退出
set -o pipefail  # 管道命令失败时捕获错误

# 示例:带错误处理的CPU监控
get_cpu_usage() {
    local cpu_idle=$(top -bn1 | grep "%Cpu(s)" | sed 's/.*, *\([0-9.]*\)%id.*/\1/')
    echo "CPU利用率: $((100 - ${cpu_idle%.*}))%"
}

get_cpu_usage || {
    echo "[ERROR] CPU数据获取失败" >&2
    exit 1
}
2. 资源泄露检测
# 文件描述符泄露示例
for i in {1..1000}; do
    exec 3<>/tmp/debug.log  # 重复打开文件未关闭
    echo "测试写入 $i" >&3
done

检测工具

lsof -p $$  # 查看当前进程打开的文件

四、高级防御策略

1. 信号捕获机制
#!/bin/bash
trap "cleanup" EXIT SIGINT SIGTERM  # 捕获多种信号

cleanup() {
    echo "正在释放临时文件..."
    rm -f /tmp/monitor.lock
    exit 0
}

# 创建锁文件防止重复运行
if [ -f /tmp/monitor.lock ]; then
    echo "已有实例在运行"
    exit 1
else
    touch /tmp/monitor.lock
fi
2. 竞争条件防护
# 使用flock防止并发冲突
(
flock -n 9 || exit 1
    # 临界区代码
    echo "更新监控数据..."
) 9>/var/lock/monitor.lock

五、技术方案对比

方案类型 典型工具 响应速度 资源消耗 开发复杂度
纯Bash方案 /proc文件系统解析 极低
外部工具集成 sar + awk
监控框架 Prometheus+NodeExporter

六、三大黄金实践原则

  1. 环境隔离原则:在脚本开头显式设置PATH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  1. 阈值动态化:从配置文件读取参数
# monitor.conf
CPU_WARNING=90
MEM_WARNING=85
  1. 日志分级管理
log() {
    local level=$1
    shift
    case $level in
        DEBUG) [ "$VERBOSE" -eq 1 ] && echo "[DEBUG] $@" ;;
        WARN)  echo "[WARN]  $@" >&2 ;;
        ERROR) echo "[ERROR] $@" >&2 ;;
    esac
}

七、典型问题排查流程图

脚本崩溃
  │
  ├─→ 检查系统日志(journalctl -xe)
  │
  ├─→ 执行脚本带-x参数(bash -x script.sh)
  │
  ├─→ 验证权限问题(sudo -u nobody ./script.sh)
  │
  └─→ 资源限制检查(ulimit -a)

八、总结与展望

经过多个版本的迭代优化,我们团队的监控脚本平均无故障时间从最初的72小时提升至目前的6000+小时。关键改进包括:

  • 引入预检模块:在脚本启动时检查依赖工具版本
check_dependency() {
    if ! awk --version &>/dev/null; then
        echo "缺失必要组件:awk"
        return 1
    fi
}
  • 实现优雅降级:当无法获取内存数据时改用备用采集方式
get_mem_usage() {
    free -m || vmstat -s || cat /proc/meminfo
}

未来的优化方向包括与eBPF技术结合,实现更低开销的内核级监控数据采集。