前言
在 Linux 运维中,有常见的两类风险:
很多人第一反应是上 ELK、Prometheus,但对中小规模场景来说,这往往成本过高。
其实,只用系统自带工具(tail + awk + curl),就可以实现一套稳定、实时、低成本的日志告警方案。
但要注意:👉 绝大多数“网上脚本”都存在严重问题,例如:
一、设计原则
这套方案遵循以下原则:
1 单进程状态管理(避免 shell 坑)
所有统计逻辑在 awk 内完成,避免:
2 真正的滑动时间窗口
采用:
👉 实现“近似实时滑动窗口”
3 不依赖日志格式细节
只依赖:
避免 fragile 正则
4 低依赖
仅依赖:
5 安全优先
二、终极生产脚本
保存为:
/usr/local/bin/log-alert.sh
#!/bin/bash# ================= 配置项 =================NGINX_LOG="/var/log/nginx/access.log"SSH_MODE="file"# file 或 journaldSSH_LOG="/var/log/secure"WEBHOOK="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的key"NGINX_THRESHOLD=10SSH_THRESHOLD=5SILENT=300STATE_DIR="/dev/shm/log_alert"mkdir -p "$STATE_DIR"chmod 700 "$STATE_DIR"WHITELIST=("127.0.0.1""10.0.0.0/8""192.168.0.0/16")# ================= 工具检查 =================for cmd in tail awk curl; docommand -v $cmd >/dev/null || { echo"缺少依赖: $cmd"; exit 1; }done# ================= webhook 校验 =================resp=$(curl -s --max-time 5 "$WEBHOOK")echo"$resp" | grep -q '"errcode":0' || {echo"Webhook 无效或不可用"exit 1}# ================= 告警函数 =================send_alert() {local title="$1"local content="$2" curl -s -o /dev/null -X POST "$WEBHOOK" \ -H "Content-Type: application/json" \ -d "{ \"msgtype\": \"text\", \"text\": {\"content\": \"${title}\n${content}\"} }" &}# ================= 白名单判断 =================is_white() {local ip="$1" [[ "$ip" == "127.0.0.1" ]] && return 0for w in"${WHITELIST[@]}"; do [[ "$ip" == "$w" ]] && return 0donereturn 1}# ================= NGINX 监控 =================tail -F "$NGINX_LOG" | awk -v t="$NGINX_THRESHOLD" -v s="$SILENT"'{ ip=$1 code=$9 now=systime() if (code !~ /^5[0-9][0-9]$/) next key=ip events[key][++cnt[key]]=now # 清理旧数据 for (i in events[key]) { if (now - events[key][i] > 60) delete events[key][i] } n=0 for (i in events[key]) n++ if (n >= t && now - last[key] > s) { last[key]=now print "NGINX|"ip"|"n fflush() }}' | while IFS='|'read -r type ip count; do is_white "$ip" && continue send_alert "Nginx 5xx告警""IP: $ip\n1分钟: $count 次"done &# ================= SSH 监控 =================if [[ "$SSH_MODE" == "journald" ]]; then SRC="journalctl -f _COMM=sshd"else SRC="tail -F $SSH_LOG"fibash -c "$SRC" | grep "Failed password" | awk -v t="$SSH_THRESHOLD" -v s="$SILENT"'{ for(i=1;i<=NF;i++){ if($i=="from"){ ip=$(i+1); break } } if(ip=="") next now=systime() key=ip events[key][++cnt[key]]=now for(i in events[key]){ if(now - events[key][i] > 60) delete events[key][i] } n=0 for(i in events[key]) n++ if(n >= t && now - last[key] > s){ last[key]=now print "SSH|"ip"|"n fflush() }}' | while IFS='|'read -r type ip count; do is_white "$ip" && continue send_alert "SSH暴力破解告警""IP: $ip\n失败次数: $count"done &wait -nexit 1
三、systemd
[Unit]Description=Log Alert ServiceAfter=network.target[Service]ExecStart=/bin/bash /usr/local/bin/log-alert.shRestart=alwaysRestartSec=3StandardOutput=journalStandardError=journalNoNewPrivileges=yesProtectSystem=full[Install]WantedBy=multi-user.target
四、必须知道的几个关键点
1 这是“近似滑动窗口”
不是精确时间序列系统,但对运维告警完全够用
2 /dev/shm 是内存
👉 重启后状态会丢失(正常)
3 默认只支持简单白名单
CIDR 未做完整实现(避免复杂度)
4 日志格式必须标准
否则需要改 $1 $9
如果你觉得本文对你有帮助,欢迎点赞、推荐、转发,关注我,后续会分享更多Linux入门干货!
文 / 零距技术仓记录每一次真实的折腾 (#^.^#)🚀 想看到更多实用折腾技巧?👉 先关注💬 评论区说说你的经历或想看的内容👍 点赞表示支持🔁 顺手分享给也在折腾的人,让大家都少踩坑 😎