第284篇:数据中心网络监控:Telemetry 与 gRPC

关键词:Telemetry、gRPC、GNMI、Stream Telemetry、iTelemetry、网络监控、大数据采集、秒级采集


一、引言

传统网络监控依赖于SNMP轮询或CLI抓取,采集频率低(分钟级)、开销大、数据粒度粗糙,无法满足数据中心高速网络(25G/100G/400G)的实时监控需求。

Telemetry技术通过设备主动推送的方式,以秒级甚至亚秒级的频率采集网络指标,结合gRPC和Google Protocol Buffers(GPB)实现高效的数据传输,成为数据中心网络监控的核心技术。

二、Telemetry 与传统 SNMP 对比

2.1 核心差异

维度 SNMP (v2c/v3) Streaming Telemetry
采集方式 拉取(Polling) 推送(Push)
采集频率 5-15分钟 1秒-30秒
数据格式 ASN.1 / MIB GPB (Protocol Buffers)
传输协议 UDP 161 gRPC over TCP 4073
数据压缩 无 GPB二进制压缩(比JSON小60-80%)
扩展性 受UDP包大小限制 流式传输,无大小限制
订阅模式 无(全部轮询) 精确订阅,按需采集
增量推送 无(全量数据) 支持增量(只传变化字段)
安全 v3支持加密/认证 gRPC TLS + mTLS

2.2 采集效率对比

以100台Leaf交换机、每台采集1000个接口指标为例:

指标 SNMP Telemetry
单次采集耗时 ~60秒(逐台轮询) ~3秒(并发推送)
网络带宽占用 ~500Mbps(峰值) ~50Mbps(平均)
设备CPU开销 高(解析SNMP请求) 低(预打包推送)
数据完整率 ~95%(丢包导致) ~99.9%(TCP可靠传输)
采集粒度 接口级聚合 子接口/队列/计数器级

三、Telemetry 技术体系

3.1 主流 Telemetry 框架

框架 厂商 协议 数据模型
gNMI (gRPC Network Management Interface) OpenConfig gRPC + GPB gNMI Path
gNOI (gRPC Network Operations Interface) OpenConfig gRPC + GPB gNOI RPC
iTelemetry 华为 iTelemetry + GPB YANG模型
YANG Push IETF NETMOD WG NETCONF YANG
Open Telemetry CNCF 多种导出器 OTLP

3.2 gNMI 订阅模型

gNMI定义了三种订阅模式:

模式 说明 使用场景
ONCE 一次性采集,完成后断开 状态快照、故障排查
SAMPLE 按固定周期推送 定期性能监控
ON_CHANGE 仅当值变化时推送 事件监控、告警触发

gNMI 订阅模型 - 图示

3.3 gNMI 路径 (Path) 语法

gNMI使用分层路径来定位设备数据:

gNMI Path 对应YANG节点 数据类型
/interfaces/interface[name=100GE1/0/1]/state/counters/in-octets 接口入方向字节数 uint64
/interfaces/interface[name=100GE1/0/1]/state/counters/in-pkts 接口入方向包数 uint64
/interfaces/interface[name=100GE1/0/1]/state/counters/in-errors 接口入方向错误数 uint64
/interfaces/interface[name=100GE1/0/1]/state/oper-status 接口操作状态 string
/system/state/cpu-usage CPU使用率 float64
/system/state/memory-used 内存使用量 uint64
/bgp/state/peers/peer[neighbor=10.255.0.1]/state/session-state BGP会话状态 string

四、Telemetry 实战部署

4.1 华为 CE 交换机 Telemetry 配置

# ===== CE交换机 gRPC Telemetry 配置 =====

# 1. 创建Telemetry组
telemetry
 group grpc_dc1_fabric
  # 指定订阅路径
  path /junos-interfaces/interfaces/interface/physical-interface/admin-stats
  path /openconfig-interfaces/interfaces/interface/state/counters
  path /ietf-system/system/cpu
  path /ietf-system/system/memory
  path /ietf-bgp/bgp/neighbors/neighbor/state

  # 采集方式: SAMPLE, 间隔10秒
  subscription sample-interval 10000

  # 编码方式: GPB
  encoding gpob

  # 发送方IP
  sender-interface LoopBack 1

  # 接收方地址 (Collector)
  receiver 10.255.255.200 vrf default destination-port 4073

  # 认证
  authentication-type simple
  authentication-password cipher Admin@123

# 2. 验证Telemetry状态
display telemetry group grpc_dc1_fabric
# Group Name: grpc_dc1_fabric
# Status: Up
# Sender: 10.255.1.1
# Receiver: 10.255.255.200:4073
# Encoding: GPOB
# Subscriptions: 5

4.2 gNMI 订阅客户端(Go 示例)

package main

import (
    "context"
    "log"
    "time"

    gnmi "github.com/openconfig/gnmi/proto/gnmi"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

// SubscribeTelemetry gNMI订阅客户端
func SubscribeTelemetry(ctx context.Context, addr string, paths []string) {
    // 建立gRPC连接
    creds := credentials.NewTLS(nil) // 生产环境使用有效证书
    conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    client := gnmi.NewGNMIClient(conn)

    // 构建订阅请求
    subs := make([]*gnmi_Subscription, 0, len(paths))
    for _, path := range paths {
        subs = append(subs, &gnmi_Subscription{
            Path: parsePath(path),
            Mode: gnmi_SubscriptionMode_SAMPLE,
            SampleInterval: 10000000000, // 10秒
        })
    }

    // 发起订阅
    subReq := &gnmi_SubscribeRequest{
        Subscribe: &gnmi_SubscriptionList{
            Subscription: subs,
            Mode: gnmi_SubscriptionList_STREAM,
            UpdateOnly: true, // 只接收变化
        },
    }

    stream, err := client.Subscribe(ctx)
    if err != nil {
        log.Fatalf("Failed to subscribe: %v", err)
    }

    // 发送订阅请求
    if err := stream.Send(subReq); err != nil {
        log.Fatalf("Failed to send subscribe request: %v", err)
    }

    // 接收流式数据
    for {
        resp, err := stream.Recv()
        if err != nil {
            log.Printf("Stream error: %v", err)
            break
        }

        if update := resp.GetUpdate(); update != nil {
            for _, u := range update.Update {
                log.Printf("Update: %s = %v",
                    pathString(u.Path),
                    valString(u.Val))
            }
        }
    }
}

func main() {
    ctx := context.Background()
    paths := []string{
        "/interfaces/interface/100GE1/0/1/state/counters/in-octets",
        "/interfaces/interface/100GE1/0/1/state/counters/out-octets",
        "/system/state/cpu-usage",
    }

    SubscribeTelemetry(ctx, "10.255.1.1:4073", paths)
}

4.3 华为 iTelemetry 配置

# ===== 华为 iTelemetry 配置 =====

# 1. 创建传感器组(定义采集路径)
telemetry
 sensor-group name SENSOR_CPU
  parameter type yang
  data-format gpob
  path /ietf-system/system/cpu/cpu/state/cpu-usage
  path /ietf-system/system/memory/state/memory-usage
  sampling-interval 10000
  wait-repeat 3

 sensor-group name SENSOR_INTERFACE
  parameter type yang
  data-format gpob
  path /openconfig-interfaces:interfaces/interface/state/counters/in-octets
  path /openconfig-interfaces:interfaces/interface/state/counters/out-octets
  path /openconfig-interfaces:interfaces/interface/state/counters/in-pkts
  path /openconfig-interfaces:interfaces/interface/state/counters/out-pkts
  sampling-interval 10000
  wait-repeat 3

# 2. 创建目的地组
 sensor-group-destination DEST_COLLECTOR
  streaming-telemetry encoding gpob
  streaming-telemetry receiver 10.255.255.200 destination-port 50052
  streaming-telemetry sender-interface LoopBack 1
  streaming-telemetry mode sample
  sampling-interval 10000
  retry-interval 60000

# 3. 绑定传感器组到目的地
 sensor-group name SENSOR_CPU
  sensor-group-destination DEST_COLLECTOR
 sensor-group name SENSOR_INTERFACE
  sensor-group-destination DEST_COLLECTOR

4.4 部署架构

部署架构 - 图示

五、实战案例:Telemetry 监控排障

5.1 故障案例:Telemetry 数据流突然中断

现象:监控大屏显示某Leaf交换机的Telemetry数据从下午14:30开始中断,之前数据正常。

排查过程:

# 1. 检查设备端Telemetry状态
display telemetry sensor-group SENSOR_INTERFACE
# Status: Down
# Last Error: Stream disconnected

# 2. 检查设备到Collector的网络连通性
ping 10.255.255.200
# 可达

# 3. 检查gRPC端口连通性
telnet 10.255.255.200 4073
# Connection refused

# 4. 检查Collector服务状态
systemctl status gnmi-collector
# 服务已停止

# 5. 查看Collector日志
journalctl -u gnmi-collector -n 50
# "Out of memory: collector killed by OOM"
# 内存不足导致Collector进程被系统kill

# 6. 根因分析
# 该Collector同时管理50台设备,每台设备200+条订阅路径
# 内存占用持续增长(内存泄漏)
# 最终触发OOM Killer

# 7. 临时恢复
systemctl restart gnmi-collector
# Telemetry数据恢复

# 8. 永久修复
# 8a. 拆分Collector: 每台Collector管理不超过10台设备
# 8b. 增加Collector的内存限制
# 8c. 增加健康检查和自动重启机制

根因:gNMI Collector存在内存泄漏问题,长时间运行后内存耗尽被OOM Killer终止,导致Telemetry数据流中断。

解决: 1. 拆分Collector实例,限制单实例管理的设备数量 2. 增加Collector的健康检查和自动重启 3. 升级Collector到修复了内存泄漏的版本 4. 部署Collector的高可用(双活)架构

5.2 故障案例:Telemetry 数据延迟异常

现象:Telemetry采集时间戳与实际时间偏差超过30秒,告警判断出现误判。

排查过程:

# 1. 检查设备时钟
display clock
# 设备时间: 2024-03-15 14:30:00

# 2. 检查Collector时间
date
# Collector时间: 2024-03-15 14:30:35

# 3. 检查NTP同步状态
display ntp status
# NTP State: Synchronized
# Reference Clock: 10.255.255.10
# Offset: +0.5ms

# 4. 检查Collector NTP
chronyc tracking
# Reference ID: 10.255.255.10
# Last offset: +35.000ms

# 5. 根因分析
# Collector服务器NTP同步异常(offset 35ms)
# 设备端NTP同步正常
# 导致时间戳对比出现偏差

# 6. 修复Collector NTP
chronyc sourcestats -v
# 发现NTP服务器响应延迟高
# 更换为更近的NTP服务器
chronyc makestep
# 强制同步时钟

根因:Collector服务器与NTP服务器的网络延迟高,时钟偏差积累至35秒,导致Telemetry数据时间戳与实际时间偏差过大。

解决: 1. 为Collector配置延迟更低的NTP服务器 2. 在Telemetry数据处理管道中加入时钟偏差检测和校准 3. 使用设备的UTC时间戳,而非Collector的接收时间

六、总结

采集能力 传统SNMP Streaming Telemetry
采集频率 分钟级 秒级
数据粒度 接口聚合 子接口/队列/计数器
网络开销 高(全量轮询) 低(增量推送)
可扩展性 受限 高(并发流)
实时性 低 高
部署建议 说明
采集频率 性能指标10秒,事件指标1秒
订阅粒度 精确订阅所需路径,避免全量订阅
Collector高可用 双活部署,避免单点故障
时钟同步 确保设备、Collector、存储NTP一致
数据安全 使用gRPC TLS加密传输

思考

  1. gNMI的ON_CHANGE模式只在值变化时推送,但如果某个接口的流量在阈值附近持续波动(频繁触发变化),如何避免告警风暴?
  2. Telemetry的GPB编码比JSON/Protobuf Text小很多,但GPB是二进制格式不便于人工排查,在调试时如何查看GPB原始内容?
  3. 当网络中出现数百台设备同时推送Telemetry数据时,Collector的存储写入会成为瓶颈,有哪些架构优化方案?

下篇预告:第285篇:Telemetry 数据采集与模型驱动