本文按"发现 → 监控 → 分析 → 优化"四个环节,给出可落地的实现思路与代码片段。适用的前提是:你已经有基本的网络设备管理权限和一台可以跑采集任务的机器。
核心要点
- 发现:ICMP + SNMP + WMI + CLI 四条路并走,产出资产清单与拓扑。
- 监控:六类指标(资源 / 容量 / 质量 / 健康 / 拓扑 / 业务),别只采 CPU 和带宽。
- 分析:降噪四步 + 动态基线 + 拓扑反向回溯定位根因。
- 优化:容量看"到达阈值的天数";自动化必须有四道护栏。
- 顺序不能换:自动化排在可观测性之后。
一、发现:从网段扫描到资产清单
先扫存活,再用 SNMP 拿身份。系统描述和厂商 OID 是两个最有用的字段,能直接区分交换机、路由器、防火墙还是打印机。
# 1) 网段存活扫描(输出活跃 IP)
$ nmap -sn 10.20.30.0/24 -oG - | awk '/Up$/{print $2}' > alive.txt
# 2) 对存活地址取系统身份(sysDescr / sysObjectID / sysName)
$ snmpget -v2c -c public@vlan10 10.20.30.1 \
1.3.6.1.2.1.1.1.0 \
1.3.6.1.2.1.1.2.0 \
1.3.6.1.2.1.1.5.0
SNMPv2-MIB::sysDescr.0 = STRING: Cisco IOS Software, C2960X
SNMPv2-MIB::sysObjectID.0 = OID: .1.3.6.1.4.1.9.1.2494
SNMPv2-MIB::sysName.0 = STRING: SW-ACC-3F-01
# 3) 取接口清单(ifName / ifAlias / ifSpeed / ifOperStatus)
$ snmpwalk -v2c -c public@vlan10 10.20.30.1 1.3.6.1.2.1.2.2.1.2 # ifName
用 Python 把发现结果落成资产清单,关键是保留"发现时间"和"来源协议",方便后面做差异比对:
<?php
// discover_merge.php ------ 合并多来源发现结果,产出资产清单
$sources = ['icmp', 'snmp', 'wmi', 'cli'];
$assets = []; // key = 管理地址
foreach ($sources as $src) {
foreach (load_result($src) as $row) {
$ip = $row['mgmt_ip'];
$assets[$ip]['mgmt_ip'] = $ip;
$assets[$ip]['sources'][] = $src; // 记录来源,便于判断可信度
$assets[$ip]['sys_descr'] = $row['sys_descr'] ?? ($assets[$ip]['sys_descr'] ?? null);
$assets[$ip]['vendor'] = guess_vendor($row['sys_object_id'] ?? null);
$assets[$ip]['last_seen'] = date('c');
}
}
// 周期性复核:与上一次清单做差集,识别新增 / 消失 / 变更
$diff = compare_with_last_snapshot($assets);
foreach (['added', 'removed', 'changed'] as $k) {
if ($diff[$k]) { error_log("asset_{$k}: " . json_encode($diff[$k])); }
}
工程提示:发现必须做 周期性复核。只在项目上线时扫一遍,拓扑会在半年内变成一张历史照片。拓扑漂移是"监控看着正常、故障时定位不到"的常见根因。
二、监控:指标采集与 95 计费值
接口流量的常用 OID 与要点:
| 指标 | OID | 注意事项 |
|---|---|---|
| 接口入/出字节数 | 1.3.6.1.2.1.2.2.1.10 / .16 | 32 位计数器,高带宽链路会回绕,优先用 ifHCInOctets(64 位) |
| 接口错误包 | 1.3.6.1.2.1.2.2.1.14 / .20 | 累加值,看的是增长速率而不是绝对值 |
| 接口丢弃包 | 1.3.6.1.2.1.2.2.1.13 / .19 | 突增通常意味着拥塞或硬件问题 |
| 接口速率 | ifSpeed / ifHighSpeed | 算利用率的分母,务必取对 |
| 设备运行时长 | 1.3.6.1.2.1.1.3.0 | 变小说明设备重启过,是个强信号 |
-- 按接口统计分位数与 95 计费值(示意,函数按时序库方言调整)
SELECT device, if_name,
round(avg(bps)/1e6, 1) AS avg_mbps,
round(percentile_cont(0.95) WITHIN GROUP (ORDER BY bps)/1e6, 1) AS p95_mbps,
round(max(bps)/1e6, 1) AS peak_mbps,
round(avg(util_pct), 1) AS avg_util
FROM if_traffic
WHERE ts >= now() - interval '30 days'
GROUP BY device, if_name
HAVING avg(util_pct) > 40 -- 只看有压力的接口
ORDER BY p95_mbps DESC;
工程提示:容量判断看的是 增长斜率与到达阈值的天数,不是当前值。"利用率 65%" 没有行动含义,"按当前斜率 40 天后到 85%"才有。
三、分析:动态基线 + 拓扑反向回溯
先用 EWMA 建立动态基线,把异常定义成"偏离自身同类型时段的基线":
<?php
// baseline.php ------ 按时段建立 EWMA 基线并判定异常
final class Baseline
{
private float $ewma = 0.0;
private float $var = 0.0;
public function __construct(private float $alpha = 0.2) {}
public function push(float $x): void
{
if ($this->ewma === 0.0) { $this->ewma = $x; return; }
$diff = $x - $this->ewma;
$this->ewma += $this->alpha * $diff;
$this->var = (1 - $this->alpha) * ($this->var + $this->alpha * $diff * $diff);
}
public function sd(): float { return sqrt($this->var); }
/** 偏离自身基线超过 k 倍标准差才算异常 */
public function isAnomaly(float $x, float $k = 3.0): bool
{
return $this->sd() > 0 && abs($x - $this->ewma) > $k * $this->sd();
}
}
// 关键:基线要按 (设备, 指标, 星期几, 小时) 分桶,否则高峰期必然误报。
根因定位用拓扑反向回溯:从最下游的告警出发,沿上游方向 BFS,找第一个"异常出现时间更早"的节点。
# rca.py ------ 拓扑反向回溯寻找根因
from collections import deque
def find_root(alerts, topology, anomaly_start):
"""alerts: [(node, ts)] topology: {node: [upstream...]}"""
downstream = select_closest_to_user(alerts) # 最贴近用户的告警
seen, q = set(), deque(downstream)
best = None
while q:
node = q.popleft()
if node in seen:
continue
seen.add(node)
t = anomaly_start.get(node)
if t is not None and (best is None or t < anomaly_start[best]):
best = node # 更早异常者更接近根因
q.extend(topology.get(node, [])) # 继续往上游走
return best
# 判据说明:
# 1) 因早于果 ------ 先说异常时间的先后,再谈告警数量
# 2) 告警最多的设备往往是受害最重的,不是出问题的
四、优化:自动化四道护栏
# guard.py ------ 自动化执行前的四道护栏(示意)
def run_action(action, target, ctx):
if not in_blast_radius(target):
return escalate(action, target, "超出爆炸半径白名单")
if is_rate_limited(action, target):
return defer(action, target, "触发限流,稍后重试")
key = f"{action}:{target}:{ctx.fault_id}"
if not acquire_idempotent_lock(key):
return already_done(action, target) # 幂等:重复触发只生效一次
if not shadow_pass(action, target):
log_intent(action, target); return "影子模式:仅记录"
return execute_with_timeout(action, target, timeout=30)
# 四道护栏:幂等 / 限流熔断 / 爆炸半径白名单 / 影子模式
# 没有它们,自动化的风险不是"可能出问题",而是"必然放大故障"。
五、降噪效果怎么度量
降噪做完之后,需要有一个可度量的口径,否则无法判断改进了多少。工程上建议同时看三个数字:
告警压缩比,即原始事件数与最终通知数的比值。这个数字反映降噪流水线的整体效果,通常在 20:1 到 100:1 之间。压缩比突然下降,往往意味着拓扑关联失效------比如拓扑漂移了、或者新接入的设备还没进图。
单次通知对应的处理对象数。理想的形态是一条通知对应一个可处理的对象。如果一条通知里还塞着五个不同位置的异常,说明分组维度没打对;如果一次故障产生了三条通知,说明关联范围设得太窄。
重复告警率,即同一根因在短时间内再次产生通知的比例。这个数字直接反映抑制规则的有效性。抑制规则的判据是"上游已确认异常",所以它的前提是拓扑里有明确的方向------又一次回到发现环节的数据质量。
三个数字之外还有一个不太好量化但很重要的观察:值班同学在收到通知后,是否需要先去翻原始告警列表。如果需要,说明通知本身没有携带足够的上下文。好的通知应该自带"哪个对象、什么现象、影响了谁、从什么时候开始"这四件事。
六、落地顺序
- 发现与资产清单 + 周期复核
- 六类指标口径与基线
- 降噪四步 + 动态基线 + 根因回溯
- 自动化处置(从影子模式开始)
这四步的依赖关系是单向的:没有资产清单和拓扑,降噪的关联与抑制就没有依据;没有基线,动态判定不出来;没有可靠的根因对象,自动化就没有安全的执行目标。所以并行推进通常会导致中途返工,按顺序推进反而更快。
参考来源
- IETF RFC 3411-3418------SNMP 框架与 MIB 定义
- IEEE 802.1AB------LLDP 链路层发现协议
- Google SRE Book------Monitoring Distributed Systems
- 厂商实现文档(仅作技术对照,非推荐):OpManager 帮助文档
一句话总结
先把发现做扎实拿到可信拓扑,再铺六类指标并建立基线,然后用降噪 + 动态基线 + 拓扑回溯把根因找出来,最后才引入带护栏的自动化------顺序对了,每一环的收益都能被下一环接住。
如果这篇对你有帮助,欢迎点赞收藏,评论区可以聊聊你们在拓扑发现和告警降噪上的具体做法。