DDoS攻击的形态在过去两年发生了剧烈变化。传统的基于固定阈值或简单流量峰值的告警策略,在面对脉冲型攻击、慢速连接耗尽以及应用层CC攻击时,往往形同虚设。很多时候,告警还没发出来,业务已经瘫痪了;或者告警响了,运维人员冲上去一看,只是正常的营销流量突增。这种“狼来了”的困境,根源在于监控系统缺乏对流量“异常形态”的精准画像能力。Prometheus作为云原生监控的事实标准,配合其强大的PromQL查询语言,能够让我们不再被动地等待阈值被触发,而是主动通过分析流量的导数、基线偏离以及连接状态指标,在攻击发生的萌芽阶段就捕捉到那些看似微小却致命的异常信号。

核心思路:从“看绝对值”转向“看变化率与分布”

绝大多数DDoS攻击的本质不是简单的流量大,而是流量行为违背了正常业务模型。正常的业务流量是“连续且平滑”的,而攻击流量往往是“突变且不连续的”。因此,监控的重点不应只是每秒请求数(RPS)或带宽占用,而应是这些指标的瞬时变化率、源IP的熵值以及连接状态的分布。Prometheus的Counter类型数据天生适合计算变化率,这为我们提供了绝佳的武器。通过rate()和irate()函数,我们可以将流量的“速度”和“加速度”可视化出来。一个健康的业务,其流量增速通常受限于用户行为,而DDoS攻击,特别是流量型攻击,往往在几秒内就能让流量翻倍,这种“加速度”的突变是阈值告警无法捕捉的,却是PromQL最容易计算的。

第一道防线:基于流量突变率的动态基线检测

放弃设置“带宽超过10Gbps就告警”这种静态规则。在Prometheus中,我们可以利用表达式计算当前流量相对于过去一段时间的偏离度。假设我们正在监控Nginx Ingress的请求速率,可以使用如下PromQL来构建异常检测规则:

(
  rate(nginx_http_requests_total[1m])
  /
  rate(nginx_http_requests_total[1m] offset 5m)
) > 2

这个逻辑非常直观:如果过去1分钟的平均请求速率,是5分钟前同一时间段请求速率的2倍以上,说明业务正在经历非预期的流量突变。这种比较方式天然排除了业务的高峰低谷周期,因为它是基于历史同期的对比。更进一步,我们还可以结合stdvar_over_time()函数来计算历史窗口内的方差,从而构建动态的基线带。当流量曲线突破基于历史均值加减三倍标准差计算出的动态边界时,才触发告警。这种方法对周期性明显的业务极其有效,能极大降低误报率。

第二道防线:TCP连接状态与半开连接监控

对于SYN Flood这类利用TCP协议握手缺陷的攻击,流量可能并不大,但会在服务端产生大量的SYN_RECV状态连接,耗尽连接表。单纯的流量速率监控对此完全无感。我们需要深入操作系统层面,通过Node Exporter将TCP连接状态暴露给Prometheus。Node Exporter默认会采集node_netstat_Tcp_CurrEstab(已建立连接)等指标,但对于半开连接,我们需要关注更细粒度的指标,或者通过自定义脚本暴露特定的TCP状态计数。

关键的监控点在于SYN_RECV状态连接数与已建立连接数(ESTABLISHED)的比例。正常情况下,这个比例极低。一旦遭遇SYN Flood,SYN_RECV数量会瞬间飙升,而ESTABLISHED数量几乎不变。我们可以编写如下PromQL规则:

(
  node_netstat_Tcp_CurrEstab
  /
  (node_netstat_Tcp_CurrEstab + on(instance) tcp_syn_recv_count)
) < 0.5

这个表达式计算了已建立连接在总关键连接中的占比。如果这个比例突然跌破50%(意味着大量连接卡在SYN_RECV状态),几乎可以断定正在遭受SYN Flood攻击。这种基于连接状态分布的监控,比单纯看流量包速率要精准得多,因为它直接反映了系统资源的实际消耗情况。

第三道防线:应用层源IP熵值与行为基线

针对应用层的CC攻击或慢速攻击,攻击者往往会模拟真实用户请求,但受限于资源,其源IP分布或请求序列行为与真实海量用户存在本质区别。真实用户的源IP分布通常具有较高的随机性,而攻击源IP可能集中在某个网段,或者虽然分散但请求模式高度一致。在Prometheus生态中,我们虽然不直接处理日志,但可以通过将Loki或自研指标暴露器与Prometheus结合,将源IP的“熵值”转化为指标。

具体做法是,在流量入口处统计每分钟独立IP数量与总请求数的比值,或者计算源IP的基尼系数。如果独立IP数增长缓慢,但总请求数呈指数级上升,说明少数IP在发起海量请求,这是典型的CC攻击特征。我们可以利用Prometheus的count()和topk()聚合操作来实现近似检测。例如,通过Recording Rules持续计算每分钟请求量最高的前10个IP的请求占比:

topk_10_ratio = 
  sum(topk(10, rate(requests_by_ip[1m]))) 
  / 
  sum(rate(requests_by_ip[1m]))

当这个比值突然升高并超过历史基线时,说明流量正在被少数“超级用户”主导,这不符合大规模分布式用户的正常访问特征,极有可能是攻击行为。这种分析深入到了流量内部结构,让伪装成正常请求的攻击流量无处遁形。

第四道防线:上下游服务联动与资源饱和度

DDoS攻击的最终目的是耗尽资源,因此监控系统资源的饱和度变化是最后也是最坚实的一道防线。但这里的关键不是监控CPU使用率是否达到100%,而是监控“CPU在等待什么”。对于代理型服务,当遭遇攻击时,往往不是CPU算力耗尽,而是端口耗尽或内存缓冲区耗尽。Prometheus需要重点采集以下指标:

Socket队列溢出:node_netstat_TcpExt_ListenOverflows和node_netstat_TcpExt_ListenDrops。这两个指标如果出现非零且持续增长,说明应用层处理速度跟不上TCP握手速度,流量已经超出了系统的承受极限,这是非常危险的信号。

网络软中断负载:通过node_softnet_dropped_total和node_netstat_TcpExt_PruneCalled等指标,可以判断网卡接收队列是否被打满,导致内核被迫丢弃数据包。

应用层队列深度:比如Nginx的nginx_connections_waiting指标。如果这个值持续大于零,说明工作进程全部繁忙,新连接只能排队等待,这是服务即将崩溃的前兆。

将这些资源饱和度指标与流量突变率指标进行逻辑与运算,可以构建出极高精度的告警规则。例如:只有当“流量速率突增3倍”且“Socket队列溢出数大于0”同时成立时,才触发DDoS攻击告警。这种组合条件既捕捉了攻击的流量特征,又验证了攻击的实际破坏效果,彻底解决了误报问题。

实战中的PromQL调优与Recording Rules设计

直接在Alertmanager规则中编写极其复杂的PromQL会导致可读性差且查询性能低下。正确的做法是利用Recording Rules预先计算好中间指标。我们可以设计一套层次化的指标预处理体系:

第一层:原始速率计算。将所有的Counter转化为速率,例如instance:nginx_rps:rate1m。

第二层:基线偏离度计算。计算当前速率与历史均值的比值,例如instance:nginx_rps:anomaly_score。

第三层:综合攻击评分。将流量偏离度、连接状态异常度、源IP集中度等多项指标加权求和,得出一个最终的“攻击可能性评分”。

通过这种分层设计,最终的告警规则可以非常简单,例如:instance:ddos_risk_score > 0.8。同时,这种中间指标对于事后复盘和攻击溯源也具有极高的价值,我们可以清晰地看到攻击评分是在哪一个时间点、因为哪一项具体指标的突变而飙升的。

构建自适应基线的挑战与应对

虽然基于PromQL的偏移对比很强大,但它依然依赖于时间窗口的选择。如果业务形态发生根本性变化,比如上线了一个大型营销活动,流量基线会永久性抬升,这时基于历史同期对比的规则就会持续误报。为了解决这个问题,我们需要在规则中加入“退化”机制。可以监控告警的持续时间,如果一个“异常”持续了超过30分钟且系统资源并未耗尽,那么系统应该自动将这个新的流量水平纳入正常基线。在Prometheus的体系中,这可以通过调整Recording Rules中offset的时间范围,或者结合外部自动化脚本动态修改告警规则的阈值来实现。这种自适应能力是区分初级监控系统和成熟监控体系的关键标志。

Prometheus监控DDoS攻击的终极奥义不在于设置了多少条告警规则,而在于是否通过PromQL构建了一个能够描述系统“健康状态”的多维向量空间。当流量异常时,这个向量会在空间中发生剧烈漂移。我们的监控系统要捕捉的不是单一的指标超过某个数字,而是整个系统状态的向量偏离了正常轨迹。通过将流量速率、连接状态、源IP分布和资源饱和度这四个维度的指标深度融合,我们就能在攻击者还在试探性扫描的阶段,就敏锐地捕捉到系统正在滑向被攻击深渊的细微征兆,从而为自动化防御系统争取到最宝贵的分钟级响应时间。