服务公告
Monitoring:监控自动化告警
发布时间:2026-04-26 16:02
监控自动化告警:让告警自己卷自己
一、前言
搞过的人都知道,监控告警最烦的是半夜爬起来处理重复告警,手工配置监控又繁琐得要死。本章讲讲怎么用脚本实现监控自动化告警,让告警收敛、阈值自调、告警渠道一键切换,省下来的时间摸鱼不香吗?
二、操作步骤
步骤1:搞清楚你要监控什么
# 查看当前系统负载情况,先摸清家底
uptime
# 预期输出:
# 16:32:01 up 45 days, 3:22, 2 users, load average: 2.15, 1.87, 1.65
# CentOS/RHEL 查看CPU核心数
nproc
# Ubuntu 查看CPU核心数
nproc
# 查看内存使用情况
free -m
# 预期输出:
# total used free shared buff/cache available
# Mem: 15868 8234 4123 312 3511 6934
# Swap: 2047 0 2047先别急着写脚本,把你要监控的指标梳理清楚:CPU使用率、内存使用率、磁盘空间、网络连接数、进程存活状态,这些是基础中的基础。
步骤2:搭建告警脚本框架
# 创建告警脚本目录
mkdir -p /opt/monitoring/scripts
cd /opt/monitoring/scripts
# 创建主监控脚本,基础框架长这样
cat > monitor.sh << 'EOF'
#!/bin/bash
# 配置区
ALERT_EMAIL="admin@example.com"
WEBHOOK_URL="https://hooks.slack.com/services/YOUR_WEBHOOK"
LOG_DIR="/var/log/monitoring"
THRESHOLD_CPU=80
THRESHOLD_MEM=85
THRESHOLD_DISK=90
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a ${LOG_DIR}/monitor.log
}
# 检查进程存活
check_process() {
local proc_name=$1
if pgrep -f "$proc_name" > /dev/null; then
log "OK: $proc_name is running"
return 0
else
log "CRITICAL: $proc_name is NOT running"
return 1
fi
}
# 发送告警通知
send_alert() {
local severity=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 写入日志
echo "[$timestamp] [$severity] $message" >> ${LOG_DIR}/alerts.log
# 发送邮件
echo "$message" | mail -s "[$severity] Monitoring Alert" $ALERT_EMAIL
# 发送Slack/Webhook
curl -s -X POST -H 'Content-type: application/json'
--data "{\"text\":\"[$severity] $message\"}"
$WEBHOOK_URL > /dev/null 2>&1
}
EOF
chmod +x monitor.sh
mkdir -p $LOG_DIR步骤3:实现CPU监控告警逻辑
# 在monitor.sh末尾追加CPU监控函数
cat >> /opt/monitoring/scripts/monitor.sh << 'EOF'
# CPU使用率监控
check_cpu() {
local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
local cpu_int=${cpu_usage%.*}
if [ $cpu_int -gt $THRESHOLD_CPU ]; then
log "WARNING: CPU usage is ${cpu_int}%, threshold is ${THRESHOLD_CPU}%"
send_alert "WARNING" "CPU usage high: ${cpu_int}%"
# 触发自动化扩容(如果有K8s的话)
if command -v kubectl > /dev/null 2>&1; then
log "INFO: Considering scaling up pods..."
fi
else
log "OK: CPU usage is normal at ${cpu_int}%"
fi
}
# CentOS/RHEL 用 top,Ubuntu 也用 top,但输出格式略有差异,统一处理
get_cpu_usage() {
# 取第一颗CPU核心的使用情况(简化版)
local cpu_idle=$(top -bn1 | grep "Cpu(s)" | awk '{print $8}' | sed 's/%id//')
if [ -z "$cpu_idle" ]; then
# 兼容某些系统格式
cpu_idle=$(top -bn1 | grep "%Cpu(s)" | awk '{print $4}' | cut -d. -f1)
fi
echo $((100 - ${cpu_idle:-0}))
}
EOF
# 测试CPU监控函数
bash -c 'source /opt/monitoring/scripts/monitor.sh && check_cpu'
# 预期输出:
# [2024-01-15 16:35:22] OK: CPU usage is normal at 23%步骤4:实现内存监控告警逻辑
# 继续追加内存监控函数
cat >> /opt/monitoring/scripts/monitor.sh << 'EOF'
# 内存使用率监控
check_memory() {
local mem_total=$(free -m | awk '/Mem/{print $2}')
local mem_available=$(free -m | awk '/Mem/{print $7}')
local mem_used=$((mem_total - mem_available))
local mem_percent=$((mem_used * 100 / mem_total))
log "INFO: Memory usage: ${mem_used}MB / ${mem_total}MB (${mem_percent}%)"
if [ $mem_percent -gt $THRESHOLD_MEM ]; then
log "WARNING: Memory usage is ${mem_percent}%, threshold is ${THRESHOLD_MEM}%"
send_alert "WARNING" "Memory usage high: ${mem_percent}%"
# 尝试清理缓存(谨慎使用)
if [ $mem_percent -gt 95 ]; then
log "CRITICAL: Memory critical, attempting cache drop..."
sync && echo 3 > /proc/sys/vm/drop_caches 2>/dev/null
fi
else
log "OK: Memory usage is normal at ${mem_percent}%"
fi
}
EOF
# 测试内存监控
bash -c 'source /opt/monitoring/scripts/monitor.sh && check_memory'
# 预期输出:
# [2024-01-15 16:38:45] INFO: Memory usage: 8234MB / 15868MB (51%)
# [2024-01-15 16:38:45] OK: Memory usage is normal at 51%步骤5:实现磁盘空间监控告警逻辑
# 追加磁盘监控函数
cat >> /opt/monitoring/scripts/monitor.sh << 'EOF'
# 磁盘空间监控(支持多分区)
check_disk() {
# CentOS/RHEL: df -h 使用human格式
# Ubuntu: df -h 也一样
local df_output=$(df -h 2>/dev/null | grep -v "tmpfs\|devtmpfs\|loop" | tail -n +2)
while read line; do
local filesystem=$(echo "$line" | awk '{print $1}')
local usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
local mountpoint=$(echo "$line" | awk '{print $6}')
if [ ! -z "$usage" ] && [ $usage -gt $THRESHOLD_DISK ]; then
log "WARNING: Disk ${mountpoint} usage is ${usage}%, threshold is ${THRESHOLD_DISK}%"
send_alert "WARNING" "Disk ${mountpoint} usage high: ${usage}%"
fi
done <<< "$df_output"
}
# inode监控(经常被忽略,但很坑)
check_inode() {
local inode_usage=$(df -i 2>/dev/null | grep -v "tmpfs\|devtmpfs" | tail -n +2 | awk '{print $5}' | tr -d '%' | sort -n | tail -1)
if [ ! -z "$inode_usage" ] && [ $inode_usage -gt 80 ]; then
log "WARNING: Inode usage is ${inode_usage}%"
send_alert "WARNING" "Inode usage high: ${inode_usage}%"
fi
}
EOF
# 测试磁盘监控
bash -c 'source /opt/monitoring/scripts/monitor.sh && check_disk && check_inode'
# 预期输出:
# [2024-01-15 16:42:18] OK: No disk partitions exceed threshold (90%)步骤6:配置定时任务和告警收敛
# 创建告警收敛脚本,避免同类型告警狂发
cat > /opt/monitoring/scripts/alert_dedup.sh << 'EOF'
#!/bin/bash
ALERT_LOG="/var/log/monitoring/alerts.log"
DEDUP_DIR="/var/log/monitoring/dedup"
LOCK_TIMEOUT=3600 # 同类告警1小时内不重复发
mkdir -p $DEDUP_DIR
check_dedup() {
local alert_type=$1
local dedup_file="${DEDUP_DIR}/${alert_type}.lock"
if [ -f "$dedup_file" ]; then
local last_time=$(cat $dedup_file)
local current_time=$(date +%s)
local diff=$((current_time - last_time))
if [ $diff -lt $LOCK_TIMEOUT ]; then
return 1 # 在锁定期内,不发告警
fi
fi
# 更新锁文件
date +%s > $dedup_file
return 0 # 可以发告警
}
EOF
chmod +x /opt/monitoring/scripts/alert_dedup.sh
# 配置定时任务
# CentOS/RHEL 使用 crontab
# Ubuntu 也用 crontab
cat > /etc/cron.d/monitoring << 'EOF'
# 每5分钟执行一次监控检查
*/5 * * * * root /opt/monitoring/scripts/monitor.sh >> /var/log/monitoring/cron.log 2>&1
# 每小时清理过期日志(保留30天)
0 * * * * root find /var/log/monitoring -name "*.log" -mtime +30 -delete
EOF
chmod 644 /etc/cron.d/monitoring
# 验证定时任务是否生效
crontab -l
# 预期输出:
# # EDITOR=vi crontab -e
# */5 * * * * root /opt/monitoring/scripts/monitor.sh >> /var/log/monitoring/cron.log 2>&1
# 0 * * * * root find /var/log/monitoring -name "*.log" -mtime +30 -delete步骤7:添加告警升级机制
# 追加告警升级逻辑(严重问题自动升级到上级)
cat >> /opt/monitoring/scripts/monitor.sh << 'EOF'
# 告警升级检查
check_escalation() {
local severity=$1
local message=$2
local minutes_threshold=30
# 检查这个告警类型最近30分钟内出现次数
local recent_count=$(grep "$message" ${LOG_DIR}/alerts.log 2>/dev/null |
grep "$(date '+%Y-%m-%d %H:' | sed 's/:$//')$(date '+%M' -d '30 minutes ago')" 2>/dev/null | wc -l)
if [ $recent_count -gt 3 ]; then
log "ESCALATION: $message keeps recurring ($recent_count times in 30min)"
send_alert "CRITICAL" "[ESCALATED] $message"
# 触发电话告警(需要对接短信/电话网关)
if command -v send_sms.py > /dev/null 2>&1; then
/opt/monitoring/scripts/send_sms.py "ESCALATION: $message"
fi
fi
}
# 主执行入口
main() {
log "=== Monitoring cycle started ==="
check_process "nginx" || check_process "apache2"
check_cpu
check_memory
check_disk
check_inode
log "=== Monitoring cycle completed ==="
}
# 直接运行或被cron调用
if [ "$0" = "${BASH_SOURCE[0]}" ]; then
main
fi
EOF
# 测试主函数
bash /opt/monitoring/scripts/monitor.sh
# 预期输出:
# [2024-01-15 16:50:01] === Monitoring cycle started ===
# [2024-01-15 16:50:01] OK: nginx is running
# [2024-01-15 16:50:02] OK: CPU usage is normal at 18%
# [2024-01-15 16:50:02] OK: Memory usage is normal at 51%
# [2024-01-15 16:50:02] OK: No disk partitions exceed threshold (90%)
# [2024-01-15 16:50:02] === Monitoring cycle completed ===步骤8:集成到企业微信/钉钉/飞书
# 创建通知渠道配置文件
cat > /opt/monitoring/scripts/notify_channels.conf << 'EOF'
# 通知渠道配置模板,不要在这里写真实密钥!
# 企业微信
WECOM_CORP_ID="your_corp_id_placeholder"
WECOM_CORP_SECRET="your_corp_secret_placeholder"
WECOM_AGENT_ID="1000001"
# 钉钉
DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"
# 飞书
FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_WEBHOOK"
# 告警静默时段(不想半夜被叫醒用这个)
SILENCE_START="22:00"
SILENCE_END="08:00"
EOF
# 创建多渠道发送函数
cat >> /opt/monitoring/scripts/monitor.sh << 'EOF'
# 判断是否在静默时段
is_silence() {
local current_hour=$(date +%H)
local current_min=$(date +%M)
local current_time=$((10#$current_hour * 60 + 10#$current_min))
local silence_start=$((22 * 60))
local silence_end=$((8 * 60))
if [ $silence_start -gt $silence_end ]; then
# 跨天静默(如22:00-08:00)
if [ $current_time -ge $silence_start ] || [ $current_time -lt $silence_end ]; then
return 0
fi
fi
return 1
}
# 发送企业微信通知
send_wecom() {
local message=$1
local token=$(curl -s "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${WECOM_CORP_ID}&corpsecret=${WECOM_CORP_SECRET}" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
curl -s -X POST "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=$token"
-H 'Content-Type: application/json'
-d "{\"touser\":\"@all\",\"msgtype\":\"text\",\"agentid\":\"${WECOM_AGENT_ID}\",\"text\":{\"content\":\"$message\"}}"
}
# 发送钉钉通知
send_dingtalk() {
local message=$1
curl -s -X POST "${DINGTALK_WEBHOOK}"
-H 'Content-Type: application/json'
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$message\"}}"
}
EOF
# 测试发送(先注释掉真实发送,测试日志)
echo "[$(date)] Test alert" >> /var/log/monitoring/alerts.log
tail -5 /var/log/monitoring/alerts.log
# 预期输出:
# [2024-01-15 16:55:32] Test alert三、常见问题FAQ
Q1: 监控脚本跑起来CPU占用反而很高怎么办?
这说明你写的脚本本身就有问题。最常见的原因是每次调用 top、free、df 都要fork新进程,循环调用就炸了。正确做法是用 /proc 体系的数据,这些是内核直接映射的,纯内存操作没开销。比如读 /proc/stat 算CPU比 top 快10倍不止:
# 低开销CPU采样(替代top)
cat /proc/stat | grep '^cpu ' | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage"%"}'内存同理,/proc/meminfo 比 free 干净利索。
Q2: 告警收敛还是挡不住重复告警,怎么破?
告警收敛不能只靠时间窗口,还要做内容归并。同一个服务同一个指标,10分钟内只算一次。实现思路是把告警信息hash后存在Redis或文件里,判断这条告警之前是否发过。比如CPU告警,这次80%和上次80%算同一条,不能分开算。真正有效的是告警指纹匹配,不是简单的时间窗口。
Q3: 生产环境告警太多根本处理不过来,怎么做优先级?
告警分级是基本功,但不是简单分成 critical/warning 就行。我建议用四象限:服务不可用(最高)、服务降级(高)、资源瓶颈(中)、容量预警(低)。每个级别对应不同通知渠道和升级策略。关键是把噪音告警源头干掉——比如依赖方抖动导致你这边连跳10条告警,这不是收敛能解决的问题,得从根源处理或者做依赖隔离。
Q4: 半夜收到告警但脚本跑的是root权限,安全吗?
能用非root跑就别用root。新建一个 monitoring 用户,权限最小化,只给必要的读权限和写日志目录的权限。drop_caches 这种危险操作本身就是反面教材,生产环境别碰。权限控制是底线,不能为了图省事就用root跑所有东西。
Q5: 怎么验证监控脚本是否正常工作?
两个思路:一是主动触发测试,手工把阈值调低让告警触发;二是mock测试环境,用脏数据注入然后验证告警是否发送。最重要的验证是看告警是否真实有效——收到告警后检查系统状态,确认告警内容准确性,否则就是狼来了。
四、总结
核心要点:
- 监控脚本要用/proc体系替代命令调用,降低自身开销
- 告警收敛靠内容归并+时间窗口,不是简单的时间阈值
- 多渠道通知要支持静默时段,避免半夜被噪音吵醒
- 告警分级要落实到不同升级策略,Critical级别必须有人响应
- 脚本权限遵循最小化原则,监控用户只给必要权限
- 定时任务要用systemd.timer替代cron(CentOS 8+/Ubuntu 18.04+),可靠性更高
延伸阅读:
- Prometheus + Alertmanager 组合比脚本方案更适合大规模集群监控
- Grafana Loki 配合可以做日志监控告警,补齐指标监控的盲区
- PagerDuty/Opsgenie 这类商业平台做告警分派和升级链路管理更专业
- 监控要跟容量规划结合,提前预判而不是告警了才处理
监控自动化告警不是一次性工程,是持续迭代的过程。刚开始能跑起来就行,慢慢加上告警收敛、升级策略、故障自愈这些高级功能。别想着一口吃成胖子,先让告警能正常收到,再逐步优化告警质量。
相关推荐
上一篇: Python:Python安全编程
下一篇: Next.js 最佳实践-星耀云