30分钟滚动基线、动态阈值、三层检测管道——这套系统把"封禁"变成了理解流量的过程。

为什么有人宁愿造轮子也不用Fail2Ban

打开网易新闻 查看精彩图片

每台跑公开Web应用的VPS都在承受计划外的流量:爬虫、暴力破解登录、配置错误的机器人每秒狂轰同一个接口。常规教程的终点是"装个Fail2Ban完事"。

但有个问题被跳过了:你真的理解这些流量吗?

硬编码的"10分钟内5次失败就封禁"在凌晨2点和下午2点是同一套标准。业务低谷期的正常波动,可能被误判为攻击;高峰期的真实威胁,又可能滑过阈值。

HNG DevOps项目的一位开发者选择造轮子:用Python写了一个守护进程,实时追踪Nginx日志,把当前请求率和30分钟滚动基线对比——全局 spike 推 Slack 告警, abusive IP 走 iptables DROP,还带分级自动解封。

代码开源在 github.com/Trojanhorse7/hng-anomaly-detector。

单台VPS上的架构:容器怎么操控宿主机防火墙

整套系统跑在一台Linux VPS上,Docker Compose 编排。核心设计是让容器打破网络隔离:

```yaml network_mode: host cap_add: - NET_ADMIN ```

```NET_ADMIN```(网络管理权限)让容器内的 iptables 调用直接作用于宿主机防火墙,而不是困在隔离的容器网络里。这是关键取舍——为了检测和响应的一体化,牺牲了部分容器沙箱特性。

三个容器分工:Nginx 打日志,Detector 做计算和决策,Slack Webhook 管通知。没有外部依赖,没有 Kafka,没有 ELK。

三层检测管道:从原始日志到异常判定

检测逻辑拆成三个层次:滑动窗口 → 滚动基线 → 异常评估。

第一层用 ```collections.deque```(双端队列)做时间容器。全局一个队列存所有请求,每个来源IP各有一个独立队列。队列长度不设固定值,而是用时间戳驱逐:超过60秒的条目从左侧弹出。任意时刻的 RPS = 条目数 / 60。

这里没有"每分钟一个桶"的近似。每个请求被单独追踪、精确老化。并行队列单独跟踪 4xx/5xx 错误,为后面的"错误激增"路径提供数据。

第二层是基线计算。后台线程每60秒执行一次,构建过去1800秒(30分钟)的每秒请求数密集向量,算出均值和标准差。

有个关键 twist:如果当前 UTC 小时已有足够样本,基线只采这个小时的数据,而非完整30分钟窗口。因为流量模式会变——凌晨和下午不是同一回事,基线应该反映"现在"而不是"安静时段和繁忙时段的混合平均"。

底层值(floor values)防止 z-score 计算时的除零边缘情况。每次重算都写入结构化日志,带时间戳、数据源标记(hourly vs full window)、算出的 mean/std。

异常判定的两套阈值:正常模式 vs 错误激增模式

第三层是实时判定。每个新请求进来,当前 RPS 和基线对比。满足任一条件即触发异常:

正常模式:z > 3.0 或 rate > 5×mean

但有个动态收紧机制:如果某 IP 的错误 RPS(4xx/5xx 响应)超过基线错误均值的3倍,阈值自动收紧——z-score 降到 2.0,rate 倍数降到 3×。

这意味着暴力破解登录的 IP 会被更激进地审查。大量失败请求本身就是信号,系统用这个信号调整对自己的敏感度。

响应动作:分级封禁和自动解封

异常触发后,系统区分两类响应。

全局 spike——比如某篇内容突然上首页——推 Slack 告警让人工判断。单个 abusive IP 则直接 iptables DROP,但封禁时长分级:首次15分钟,二次1小时,三次24小时。自动解封,不给人为误伤永久买单。

解封逻辑用独立的定时线程,扫描封禁记录表,到期执行 iptables 删除。封禁期间的新请求会被计数但不重复触发规则,避免日志爆炸。

这套设计放弃了什么,又坚持了什么

明确放弃的功能:机器学习分类、深度包检测、分布式协同、Web 管理界面。

坚持的原则:可解释性(每个决策都能追溯到具体数值)、本地自治(单节点闭环,不依赖外部服务)、渐进响应(误伤成本可控)。

代码里有个细节:基线计算用的 numpy 而不是纯 Python 循环。1800个点的向量运算,用 numpy 是微秒级,纯 Python 是毫秒级。60秒周期里省下的CPU,留给更频繁的日志解析。

另一个取舍是内存。每个IP一个 deque,在DDoS场景下可能膨胀。代码里设了IP级LRU淘汰,最多跟踪10,000个活跃IP,超出的新IP直接走简化路径(只计数不建基线)。这是明确的降级策略,保证核心功能不崩。

从个人项目到生产可用,还差几步

当前实现是单进程多线程,GIL 限制下日志解析和基线计算会争抢。作者提到下一步是拆成多进程,用共享内存传递 deque 快照

更大的缺口是可视化。现在只有结构化日志,没有实时 dashboard。要生产化,需要补一套轻量的时序存储和查询——VictoriaMetrics 或者甚至 SQLite + 定时聚合。

但核心架构已经回答了最初的问题:理解流量,再决定封禁。30分钟滚动基线是对"正常"的动态定义,错误激增收紧是对"可疑"的上下文感知,分级自动解封是对"误伤"的成本控制。

这三层设计,把防火墙从规则列表变成了有短期记忆的决策系统。不是AI,但是一种工程化的智能。

如果你也在维护一台被骚扰的VPS,不妨看看这套代码。它可能比你想象的更适合你的场景——或者至少,让你重新想想 Fail2Ban 的"5次失败"到底意味着什么。