基于观测云 DataKit 实现 H3C 路由器有源 Ping 链路质量监控

背景:为什么传统监控不够?

在企业网络运维中,仅依赖设备存活(如 ICMP 或 SNMP)无法全面反映链路质量。尤其在多出口、策略路由或 VRF 场景下,需要从指定源地址发起探测 ,以真实反映业务路径质量。但是设备存活监控(ICMP/SNMP)只能回答"通不通",无法回答"好不好"。尤其在以下场景,传统方案存在明显盲区:

场景 传统监控局限 业务影响
多出口链路 默认路由探测,无法覆盖备用链路 主链路故障时,备用链路质量未知
策略路由(PBR) 探测源地址与实际业务流不一致 监控正常,但用户访问卡顿
VRF 隔离网络 跨 VRF 路由不可见 私网互通问题无法提前发现
云专线/SD-WAN 公网探测路径与专线路径分离 专线质量劣化无感知

为了弥补传统监控盲区,下面借助观测云,实现从指定源地址发起探测,真实还原业务流量的实际转发路径。

方案架构:DataKit + SSH 有源探测

本方案基于观测云 DataKit 的 PythonD 插件,通过 SSH 登录 H3C 路由器执行有源 Ping,实现以下目标:

  • 多链路质量监控(指定源 IP)
  • 延迟 / 丢包 / 抖动指标采集
  • 统一指标上报观测云

整体架构

关键技术选型

组件 选型 理由
采集器 DataKit PythonD 观测云原生插件,支持自定义 Point 上报
SSH 库 Paramiko Python 生态成熟,支持密钥认证、长连接复用
目标设备 H3C 路由器 支持 -a 参数指定源地址,Comware 系统通用
传输协议 HTTP/HTTPS 观测云标准接入,支持压缩与批量上报

核心能力:有源 Ping 原理

H3C 有源 Ping 命令

H3C 支持通过指定源地址进行 Ping。

示例:

python 复制代码
ping -a 172.30.253.1 172.30.253.6
Ping 172.30.253.6 (172.30.253.6) from 172.30.253.1: 56 data bytes, press CTRL_C to break
56 bytes from 172.30.253.6: icmp_seq=0 ttl=255 time=3.246 ms
56 bytes from 172.30.253.6: icmp_seq=1 ttl=255 time=48.882 ms
56 bytes from 172.30.253.6: icmp_seq=2 ttl=255 time=0.522 ms
56 bytes from 172.30.253.6: icmp_seq=3 ttl=255 time=0.547 ms
56 bytes from 172.30.253.6: icmp_seq=4 ttl=255 time=0.561 ms

--- Ping statistics for 172.30.253.6 ---
5 packet(s) transmitted, 5 packet(s) received, 0.0% packet loss

指标设计:完整链路质量画像

核心指标集

指标名 类型 说明
status int 连通性(1=成功,0=失败)
packet_loss float 丢包率 (%)
latency_avg float 平均延迟(ms)
latency_min float 最小延迟(ms)
latency_max float 最大延迟(ms)
latency_stddev float 抖动

接入步骤

安装 DataKit

登录能连接路由器的 Linux 主机,安装文档注册观测云,安装 DataKit。

开启 Pythond 采集器

bash 复制代码
cd /usr/local/datakit/conf.d
cp samples/pythond.conf.sample pythond.conf

编辑 pythond.conf

bash 复制代码
cd /usr/local/datakit/python.d
mkdir route_demo

在 route_demo 下新建 route_demo.py 文件,内容如下:

python 复制代码
from datakit_framework import DataKitFramework
import paramiko
import re
import time
import json

class H3CPingMonitor(DataKitFramework):
    name = 'H3CPingMonitor'
    interval = 60  # 每60秒执行一次
    
    def __init__(self, **kwargs):
        super().__init__(ip='127.0.0.1', port=9529)
        # H3C路由器配置
        self.router_ip = '172.30.250.201'
        self.router_port = 22
        self.username = 'test1'      # 修改为你的H3C用户名
        self.password = 'xxxx'   # 修改为你的H3C密码
        # 目标ping地址列表
        self.target_ips = ['172.30.253.6']
        # 源地址(有源ping的源接口IP,根据实际情况修改)
        self.source_ip = '172.30.253.1'  # 例如: '10.0.0.1',如果为None则使用默认源
        
    def ssh_connect(self):
        """建立SSH连接到H3C路由器"""
        try:
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            client.connect(
                hostname=self.router_ip,
                port=self.router_port,
                username=self.username,
                password=self.password,
                timeout=30,
                allow_agent=False,
                look_for_keys=False
            )
            return client
        except Exception as e:
            print(f"SSH连接失败: {str(e)}")
            return None
    
    def parse_ping_output(self, output):
        """
        解析H3C ping输出,提取延迟指标
        返回: dict 包含 success, packet_loss, latency_min, latency_avg, latency_max, latency_stddev
        """
        result = {
            'success': False,
            'packet_loss': 100,
            'latency_min': None,
            'latency_avg': None,
            'latency_max': None,
            'latency_stddev': None
        }
        
        # 检查是否成功(0%丢包)
        if re.search(r'0.0%\s+packet\s+loss|0%\s+packet\s+loss', output):
            result['success'] = True
            result['packet_loss'] = 0
        else:
            # 提取丢包率
            loss_match = re.search(r'(\d+.?\d*)%\s+packet\s+loss', output)
            if loss_match:
                result['packet_loss'] = float(loss_match.group(1))
                result['success'] = result['packet_loss'] < 100
        
        # 提取延迟统计: round-trip min/avg/max/std-dev = 0.522/10.752/48.882/19.094 ms
        stats_pattern = r'round-trip\s+min/avg/max/std-dev\s*=\s*([\d.]+)/([\d.]+)/([\d.]+)/([\d.]+)\s*ms'
        stats_match = re.search(stats_pattern, output)
        
        if stats_match:
            result['latency_min'] = float(stats_match.group(1))
            result['latency_avg'] = float(stats_match.group(2))
            result['latency_max'] = float(stats_match.group(3))
            result['latency_stddev'] = float(stats_match.group(4))
        else:
            # 备用方案:从每行time=提取计算
            times = re.findall(r'time=([\d.]+)\s*ms', output)
            if times:
                times = [float(t) for t in times]
                result['latency_min'] = min(times)
                result['latency_avg'] = sum(times) / len(times)
                result['latency_max'] = max(times)
        
        return result
    
    def execute_ping(self, client, target_ip):
        """
        在H3C路由器上执行有源ping命令
        H3C命令格式: ping -a <source-ip> <destination-ip>
        """
        try:
            # 打开交互式shell
            shell = client.invoke_shell()
            shell.settimeout(30)
            
            # 等待初始提示符
            time.sleep(1)
            shell.recv(65535).decode('utf-8', errors='ignore')
            
            # 构建有源ping命令
            if self.source_ip:
                ping_cmd = f"ping -a {self.source_ip} {target_ip}\n"
            else:
                ping_cmd = f"ping {target_ip}\n"
            
            print(f"执行命令: {ping_cmd.strip()}")
            shell.send(ping_cmd)
            
            # 等待命令执行完成
            time.sleep(2)
            
            # 接收输出
            output = ""
            start_time = time.time()
            while True:
                if shell.recv_ready():
                    chunk = shell.recv(65535).decode('utf-8', errors='ignore')
                    output += chunk
                    # 检测到统计信息或超时退出
                    if "Ping statistics" in output and "round-trip" in output:
                        time.sleep(0.3)
                        if shell.recv_ready():
                            output += shell.recv(65535).decode('utf-8', errors='ignore')
                        break
                elif time.time() - start_time > 10:
                    break
                else:
                    time.sleep(0.2)
            
            shell.close()
            
            print(f"Ping {target_ip} 输出:\n{output}")
            
            # 解析ping结果
            parsed = self.parse_ping_output(output)
            
            return {
                'target': target_ip,
                'status': 1 if parsed['success'] else 0,
                'packet_loss': parsed['packet_loss'],
                'latency_min': parsed['latency_min'],
                'latency_avg': parsed['latency_avg'],
                'latency_max': parsed['latency_max'],
                'latency_stddev': parsed['latency_stddev'],
                'output': output[:500]
            }
            
        except Exception as e:
            print(f"执行ping命令失败 {target_ip}: {str(e)}")
            return {
                'target': target_ip,
                'status': 0,
                'packet_loss': 100,
                'latency_min': None,
                'latency_avg': None,
                'latency_max': None,
                'latency_stddev': None,
                'output': str(e)
            }
    
    def run(self):
        print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 开始H3C路由器Ping监控...")
        
        # 建立SSH连接
        client = self.ssh_connect()
        if not client:
            # 连接失败,上报所有目标为失败状态
            data = []
            for ip in self.target_ips:
                fields = {
                    "status": 0,
                    "packet_loss": 100,
                    "error": "ssh_connection_failed"
                }
                data.append({
                    "measurement": "h3c_ping_monitor",
                    "tags": {
                        "router_ip": self.router_ip,
                        "target_ip": ip,
                        "source_ip": self.source_ip or "default"
                    },
                    "fields": fields
                })
            
            in_data = {
                'M': data,
                'input': "h3c_ping_monitor"
            }
            return self.report(in_data)
        
        try:
            # 执行所有ping检测
            ping_results = []
            for target_ip in self.target_ips:
                result = self.execute_ping(client, target_ip)
                ping_results.append(result)
                time.sleep(1)
            
            # 构建上报数据
            data = []
            for result in ping_results:
                fields = {
                    "status": result['status'],
                    "packet_loss": result['packet_loss']
                }
                
                # 添加延迟指标(仅当ping成功且有数据时)
                if result['status'] == 1:
                    if result['latency_avg'] is not None:
                        fields["latency_avg"] = result['latency_avg']
                    if result['latency_min'] is not None:
                        fields["latency_min"] = result['latency_min']
                    if result['latency_max'] is not None:
                        fields["latency_max"] = result['latency_max']
                    if result['latency_stddev'] is not None:
                        fields["latency_stddev"] = result['latency_stddev']
                
                data.append({
                    "measurement": "h3c_ping_monitor",
                    "tags": {
                        "router_ip": self.router_ip,
                        "target_ip": result['target'],
                        "source_ip": self.source_ip or "default"
                    },
                    "fields": fields
                })
            
            in_data = {
                'M': data,
                'input': "h3c_ping_monitor"
            }
            
            print(f"上报数据: {json.dumps(in_data, indent=2)}")
            
            # 上报到DataKit
            return self.report(in_data)
            
        finally:
            client.close()
            print("SSH连接已关闭")


# 如果直接运行此脚本进行测试
if __name__ == '__main__':
    monitor = H3CPingMonitor()
    monitor.run()

重启 DataKit

复制代码
datakit service -R

监控视图

使用采集到的指标,自定义仪表板,效果如下。

总结

本方案通过 DataKit PythonD + SSH,实现了对 H3C 路由器的有源链路质量监控,具备以下优势:

  • 支持真实业务路径检测(指定源 IP)
  • 指标体系完整(延迟 / 丢包 / 抖动)
  • 易扩展(多目标、多源、并发)
  • 可直接接入观测云告警体系
相关推荐
盛世宏博智慧档案12 小时前
户外配电柜为什么要装POE供电以太网温湿度传感器?
服务器·网络·人工智能·监控·温湿度·配电柜
行百里er1 天前
Spring Insight 里收到的 Span 是怎么存下来的
spring boot·spring·监控
行百里er3 天前
Spring Insight 里上报 Span 为什么用 JDK HttpClient 而不是 RestTemplate
spring boot·spring cloud·监控
SkyWalking中文站4 天前
Horizon UI 1.0 正式发布:SkyWalking 新一代控制台接棒 Booster
运维·监控·自动化运维
行百里er4 天前
BlockingQueue:异步批量上报
spring boot·spring cloud·监控
IT界的老黄牛4 天前
Prometheus TSDB 拆不出来、也存不了一年:4 条外部存储出路 + 容量测算
prometheus·时序数据库·监控·thanos·victoriametrics·remote_write
IT界的老黄牛5 天前
Jenkins 监控一条龙:接入、看板 9964、11 条告警规则直接抄走
jenkins·grafana·prometheus·监控·ci-cd·告警规则
SkyWalking中文站5 天前
SkyWalking 11 与 BanyanDB 0.11:在存储引擎内部实现 Trace 尾部采样
运维·监控·自动化运维
行百里er5 天前
BeanPostProcessor:包装 Feign Client
spring boot·后端·监控
行百里er7 天前
HandlerInterceptor 和 WebFilter:两套 HTTP 请求拦截
后端·架构·监控