一、应用场景:为什么我们需要监控脚本?
在服务器运维中,资源监控脚本是守护系统健康的"哨兵"。它们持续跟踪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 | 慢 | 高 | 高 |
六、三大黄金实践原则
- 环境隔离原则:在脚本开头显式设置PATH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
- 阈值动态化:从配置文件读取参数
# monitor.conf
CPU_WARNING=90
MEM_WARNING=85
- 日志分级管理
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技术结合,实现更低开销的内核级监控数据采集。