Pi-hole报警系统:实时异常检测与通知
网络广告过滤工具Pi-hole在家庭和小型办公网络中广泛应用,但其默认配置缺乏主动监控能力。当DNS查询异常激增、广告拦截率骤降或服务意外终止时,用户往往无法及时察觉。本文将系统讲解如何构建基于原生组件的Pi-hole报警系统,通过日志分析、进程监控和阈值检测实现全方位异常预警。
报警系统架构设计
Pi-hole报警系统采用三层架构设计,通过模块化组件实现异常检测与通知:
核心组件说明:
- 数据采集层:通过解析
pihole.log和pihole-FTL.log获取DNS查询数据,利用系统命令采集CPU/内存/磁盘使用率,通过utils.sh中的getFTLPID()函数监控服务进程 - 异常检测层:实现三类检测机制(阈值检测、趋势分析、服务状态检查),通过
piholeDebug.sh中的诊断逻辑扩展异常判断能力 - 通知分发层:支持本地日志标记、邮件推送和Webhook集成,可通过修改
pihole.cron配置定时任务触发检测
关键异常指标定义
基于Pi-hole运行特性,需重点监控以下六类异常指标,建议设置三级告警阈值:
| 指标类别 | 监控参数 | 警告阈值 | 严重阈值 | 数据来源 |
|---|---|---|---|---|
| DNS服务状态 | FTL进程存活 | 进程不存在 >5秒 | 进程不存在 >30秒 | getFTLPID() |
| 查询流量 | DNS查询量/分钟 | >基础值200% | >基础值300% | pihole.log |
| 拦截效率 | 广告拦截率 | <70% | <50% | pihole -c |
| 系统资源 | 内存使用率 | >85% | >95% | free -m |
| 磁盘空间 | /var/log使用率 | >80% | >90% | df -h |
| 上游DNS | 查询失败率 | >5% | >10% | pihole-FTL.log |
表:Pi-hole核心监控指标及阈值建议
阈值计算方法:基础值建议取7天滑动平均值,通过pihole-FTL sqlite3查询历史数据:
# 获取最近7天平均DNS查询量
sqlite3 /etc/pihole/pihole-FTL.db "SELECT AVG(count) FROM queries WHERE timestamp > strftime('%s','now','-7 days') GROUP BY strftime('%Y-%m-%d %H:%M', timestamp, 'unixepoch');"
异常检测实现方案
1. FTL服务存活监控
利用utils.sh中的getFTLPID()函数实现进程监控,扩展添加超时重启与告警功能:
#!/bin/bash
# 保存为 /usr/local/bin/monitor_ftl.sh
source /opt/pihole/utils.sh
source /opt/pihole/COL_TABLE
FTL_PID_FILE="/run/pihole-FTL.pid"
ALERT_THRESHOLD=3 # 连续失败次数
RESTART_THRESHOLD=5 # 触发自动重启的失败次数
check_ftl_status() {
local fail_count=0
local restart_count=0
while true; do
PID=$(getFTLPID "$FTL_PID_FILE")
if [ "$PID" -eq -1 ]; then
fail_count=$((fail_count + 1))
restart_count=$((restart_count + 1))
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_RED}FTL服务异常${COL_NC} (连续失败: $fail_count次)" >> /var/log/pihole/alert.log
if [ $restart_count -ge $RESTART_THRESHOLD ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_YELLOW}尝试重启FTL服务${COL_NC}" >> /var/log/pihole/alert.log
systemctl restart pihole-FTL
restart_count=0 # 重置重启计数器
fi
if [ $fail_count -ge $ALERT_THRESHOLD ]; then
send_alert "FTL服务异常" "FTL进程已连续失败$fail_count次,可能需要手动干预"
fail_count=0 # 重置告警计数器
fi
else
fail_count=0
restart_count=0
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_GREEN}FTL服务正常${COL_NC} (PID: $PID)" >> /var/log/pihole/monitor.log
fi
sleep 10 # 10秒检查一次
done
}
send_alert() {
# 实现告警通知逻辑,后续章节详细说明
echo "ALERT: $1 - $2" >> /var/log/pihole/critical_alert.log
}
check_ftl_status
将上述脚本添加到系统服务并设置开机启动:
# 创建systemd服务文件
sudo tee /etc/systemd/system/pihole-alert.service <<EOF
[Unit]
Description=Pi-hole FTL Service Monitor
After=pihole-FTL.service
[Service]
ExecStart=/usr/local/bin/monitor_ftl.sh
Restart=always
User=root
[Install]
WantedBy=multi-user.target
EOF
# 启用并启动服务
sudo systemctl enable pihole-alert.service
sudo systemctl start pihole-alert.service
2. DNS查询异常检测
通过分析pihole.log实现DNS查询量异常检测,使用滑动窗口算法识别异常流量模式:
#!/bin/bash
# 保存为 /usr/local/bin/query_alert.sh
source /opt/pihole/COL_TABLE
LOG_FILE="/var/log/pihole/pihole.log"
ALERT_LOG="/var/log/pihole/query_alert.log"
WINDOW_SIZE=5 # 5分钟滑动窗口
BASELINE_DAYS=7 # 7天基准数据
# 计算基准查询量(最近7天相同时间段的平均值)
calculate_baseline() {
local current_hour=$(date +%H)
local current_minute=$(date +%M)
local current_day=$(date +%u)
# 计算相同小时段的平均查询量
baseline=$(awk -v h="$current_hour" -v m="$current_minute" '
BEGIN { total=0; count=0 }
{
time = substr($1, 1, 5) # 提取HH:MM
if (time >= h ":" (m - 5) && time <= h ":" (m + 5)) {
total += 1
count += 1
}
}
END { print total/count }
' $(find /var/log/pihole -name "pihole.log.*.gz" | sort -r | head -n $((BASELINE_DAYS*24)))) # 取最近7天的日志
echo $baseline
}
# 实时监控查询量
monitor_queries() {
local baseline=$(calculate_baseline)
local threshold_high=$(echo "$baseline * 3" | bc) # 300%阈值
local threshold_medium=$(echo "$baseline * 2" | bc) # 200%阈值
local window_count=0
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 基准查询量: $baseline QPM, 告警阈值: 中=$threshold_medium, 高=$threshold_high" >> $ALERT_LOG
# 使用tail -f实时监控日志
tail -F $LOG_FILE | while read -r line; do
window_count=$((window_count + 1))
# 每5分钟计算一次平均查询量
if [ $((window_count % (60 * 5))) -eq 0 ]; then
current_qpm=$window_count
window_count=0
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 当前查询量: $current_qpm QPM" >> $ALERT_LOG
# 判断是否超过阈值
if (( $(echo "$current_qpm > $threshold_high" | bc -l) )); then
send_alert "DNS查询量异常" "当前查询量$current_qpm QPM,超过基准值300% (基准值: $baseline QPM)"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_RED}严重告警: 查询量超过300%基准值${COL_NC}" >> $ALERT_LOG
elif (( $(echo "$current_qpm > $threshold_medium" | bc -l) )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_YELLOW}警告: 查询量超过200%基准值${COL_NC}" >> $ALERT_LOG
fi
# 每小时重新计算基准值,适应流量变化
if [ $(( $(date +%M) )) -eq 0 ]; then
baseline=$(calculate_baseline)
threshold_high=$(echo "$baseline * 3" | bc)
threshold_medium=$(echo "$baseline * 2" | bc)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] 更新基准值: $baseline QPM" >> $ALERT_LOG
fi
fi
done
}
send_alert() {
# 告警通知逻辑
echo "ALERT: $1 - $2" >> /var/log/pihole/critical_alert.log
}
monitor_queries
3. 广告拦截率骤降检测
通过定时检查Pi-hole拦截效率,当拦截率低于设定阈值时触发告警:
#!/bin/bash
# 保存为 /usr/local/bin/block_rate_alert.sh
source /opt/pihole/COL_TABLE
ALERT_THRESHOLD_LOW=50 # 严重阈值:拦截率<50%
ALERT_THRESHOLD_MEDIUM=70 # 警告阈值:拦截率<70%
CHECK_INTERVAL=300 # 检查间隔(秒)
ALERT_LOG="/var/log/pihole/block_rate_alert.log"
check_block_rate() {
while true; do
# 使用pihole -c获取统计信息
stats=$(pihole -c -e)
# 提取拦截率和总查询量
block_rate=$(echo "$stats" | grep "Blocked" | awk '{print $4}' | sed 's/%//')
total_queries=$(echo "$stats" | grep "Queries" | awk '{print $2}')
# 判断拦截率是否低于阈值
if (( $(echo "$block_rate < $ALERT_THRESHOLD_LOW" | bc -l) )); then
send_alert "拦截率严重下降" "当前拦截率: ${block_rate}%,总查询: ${total_queries}。已低于${ALERT_THRESHOLD_LOW}%阈值"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_RED}严重告警: 拦截率${block_rate}%${COL_NC} (总查询: $total_queries)" >> $ALERT_LOG
elif (( $(echo "$block_rate < $ALERT_THRESHOLD_MEDIUM" | bc -l) )); then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_YELLOW}警告: 拦截率${block_rate}%${COL_NC} (总查询: $total_queries)" >> $ALERT_LOG
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_GREEN}正常: 拦截率${block_rate}%${COL_NC} (总查询: $total_queries)" >> $ALERT_LOG
fi
sleep $CHECK_INTERVAL
done
}
send_alert() {
# 告警通知逻辑
echo "ALERT: $1 - $2" >> /var/log/pihole/critical_alert.log
}
check_block_rate
将上述检测脚本添加到crontab:
# 每5分钟执行一次拦截率检查
echo "*/5 * * * * /usr/local/bin/block_rate_alert.sh" | sudo tee -a /etc/cron.d/pihole_alert
多渠道通知系统实现
1. 邮件通知集成
利用mailutils实现邮件通知功能,配置SMTP服务发送告警邮件:
#!/bin/bash
# 保存为 /usr/local/bin/send_email_alert.sh
# 配置邮件参数
SMTP_SERVER="smtp.example.com"
SMTP_PORT="587"
SMTP_USER="your_email@example.com"
SMTP_PASSWORD="your_email_password"
RECIPIENT="recipient@example.com"
send_email() {
local subject="$1"
local body="$2"
# 使用mail命令发送邮件
echo -e "Subject: Pi-hole告警: $subject\n\n$body" | \
mail -S smtp="smtp://$SMTP_SERVER:$SMTP_PORT" \
-S smtp-use-starttls \
-S smtp-auth=login \
-S smtp-auth-user="$SMTP_USER" \
-S smtp-auth-password="$SMTP_PASSWORD" \
-S from="$SMTP_USER" \
"$RECIPIENT"
}
# 在之前的告警函数中调用
send_alert() {
local subject="$1"
local body="$2"
# 写入本地日志
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ALERT: $subject - $body" >> /var/log/pihole/critical_alert.log
# 发送邮件通知
/usr/local/bin/send_email_alert.sh "$subject" "$body"
}
2. 系统日志集成
将告警信息集成到系统日志,便于集中管理和分析:
# 修改告警发送函数
send_alert() {
local subject="$1"
local body="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# 写入本地告警日志
echo "[$timestamp] ALERT: $subject - $body" >> /var/log/pihole/critical_alert.log
# 发送系统日志
logger -p alert -t pihole-alert "[$subject] $body"
# 发送邮件通知
/usr/local/bin/send_email_alert.sh "$subject" "$body"
}
告警触发与响应流程
异常检测到告警分发的完整流程如下:
典型异常响应流程:
- 接收告警通知,记录异常发生时间和关键指标
- 登录Pi-hole管理界面查看实时统计
- 检查
pihole-FTL.log获取详细错误信息 - 根据异常类型执行对应修复操作:
- FTL服务异常:执行
pihole restartdns重启服务 - 查询量激增:检查网络中是否存在恶意设备或DNS放大攻击
- 拦截率下降:检查广告列表更新状态,执行
pihole -g更新 Gravity
- FTL服务异常:执行
- 恢复后验证异常是否解除
系统优化与最佳实践
1. 告警阈值动态调整
基于网络使用模式自动调整告警阈值,避免工作时段误报:
#!/bin/bash
# 动态阈值调整示例
get_dynamic_threshold() {
local current_hour=$(date +%H)
local current_day=$(date +%u)
# 工作日高峰时段(8:00-22:00)提高阈值
if [ $current_day -le 5 ] && [ $current_hour -ge 8 ] && [ $current_hour -le 22 ]; then
echo "150" # 150%基准值
# 夜间时段(22:00-8:00)降低阈值
else
echo "120" # 120%基准值
fi
}
2. 监控数据可视化
通过piholeDebug.sh收集的诊断数据,结合Python脚本生成趋势图表:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import pandas as pd
import re
from datetime import datetime
# 解析告警日志
data = []
with open('/var/log/pihole/query_alert.log', 'r') as f:
for line in f:
if '当前查询量:' in line:
# 提取时间戳和查询量
timestamp = re.search(r'\[(.*?)\]', line).group(1)
qpm = re.search(r'当前查询量: (\d+)', line).group(1)
data.append({'timestamp': timestamp, 'qpm': int(qpm)})
# 转换为DataFrame并绘图
df = pd.DataFrame(data)
df['timestamp'] = pd.to_datetime(df['timestamp'])
df.set_index('timestamp', inplace=True)
plt.figure(figsize=(12, 6))
df['qpm'].plot(title='DNS查询量趋势')
plt.ylabel('查询量/分钟')
plt.savefig('/var/www/html/query_trend.png') # 保存图表供Web访问
3. 自动化修复集成
对常见异常实现自动修复,减少人工干预:
# 在FTL服务监控中添加自动修复逻辑
if [ $restart_count -ge $RESTART_THRESHOLD ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_YELLOW}尝试重启FTL服务${COL_NC}" >> /var/log/pihole/alert.log
# 尝试标准重启
systemctl restart pihole-FTL
sleep 10
PID=$(getFTLPID "$FTL_PID_FILE")
if [ "$PID" -eq -1 ]; then
# 标准重启失败,尝试强制重启
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${COL_RED}标准重启失败,尝试强制重启${COL_NC}" >> /var/log/pihole/alert.log
killall -9 pihole-FTL
systemctl start pihole-FTL
sleep 10
PID=$(getFTLPID "$FTL_PID_FILE")
if [ "$PID" -eq -1 ]; then
# 强制重启也失败,发送紧急告警
send_alert "FTL服务无法启动" "标准重启和强制重启均失败,需要手动干预"
fi
fi
restart_count=0
fi
总结与扩展方向
本文详细介绍了基于Pi-hole原生组件构建报警系统的完整方案,通过日志分析、进程监控和阈值检测实现了三类核心异常的实时告警。系统具有以下特点:
- 原生兼容性:完全基于Pi-hole现有组件开发,无需额外安装第三方监控工具
- 多维度监控:覆盖服务存活、查询流量、拦截效率等关键指标
- 灵活通知机制:支持本地日志、系统日志和邮件通知多渠道分发
- 自动化修复:集成常见异常的自动恢复逻辑
未来扩展方向:
- 集成Prometheus和Grafana实现更专业的可视化监控
- 添加网络流量异常检测,识别潜在的DNS攻击
- 开发移动App通知渠道,实现即时告警推送
- 构建异常模式识别引擎,通过机器学习预测潜在故障
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



