服务公告

服务公告 > 综合新闻 > Monitoring:监控自动化告警

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 这类商业平台做告警分派和升级链路管理更专业
  • 监控要跟容量规划结合,提前预判而不是告警了才处理

监控自动化告警不是一次性工程,是持续迭代的过程。刚开始能跑起来就行,慢慢加上告警收敛、升级策略、故障自愈这些高级功能。别想着一口吃成胖子,先让告警能正常收到,再逐步优化告警质量。