第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 | 仅当值变化时推送 | 事件监控、告警触发 |

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加密传输 |
思考
- gNMI的ON_CHANGE模式只在值变化时推送,但如果某个接口的流量在阈值附近持续波动(频繁触发变化),如何避免告警风暴?
- Telemetry的GPB编码比JSON/Protobuf Text小很多,但GPB是二进制格式不便于人工排查,在调试时如何查看GPB原始内容?
- 当网络中出现数百台设备同时推送Telemetry数据时,Collector的存储写入会成为瓶颈,有哪些架构优化方案?
下篇预告:第285篇:Telemetry 数据采集与模型驱动