简介:这份PDF文档面向HPC与下一代数据中心网络的运维工程师及架构设计人员,聚焦如何借助Network Telemetry技术打破“网络黑盒”,解决大规模复杂网络中流量精细可视、可控以及端到端秒级故障定位的难题。资源包共1个PDF文件,大小约603KB,内容围绕INT与gRPC两大核心技术展开,系统讲解交换机主动推送Buffer Usage、CPU、内存等状态信息的机制,以及通过INT在报文路径中嵌入MetaData实现转发路径与时延可视化的完整方案。文档还结合TCP Incast微突发流、交换机缓存容量与带宽升级不匹配等真实场景,剖析传统SNMP监控的局限,并给出丢包端口快速定位、缓存实时监控、端到端时延测量等运维能力的实现思路。目前已有320人学习,适合希望深入理解网络遥测原理、构建可视化运维体系的技术人员参考,可帮助读者掌握从技术选型到方案落地的关键要点。
1. 网络遥测不是新概念,但 INT+gRPC 这套组合拳值得你花时间拆一遍
如果你正在维护一个 25G/100G 的 HPC 或 AI 训练集群,大概率遇到过这种场景:业务侧反馈训练任务间歇性卡顿,你登上核心交换机display interface看了一圈,端口没有 CRC 错包,CPU 和内存也正常,SNMP 监控大盘上更是一片绿。但业务就是慢,丢包就是发生了,你根本不知道是哪台交换机的哪个端口、在哪个微秒级的时间窗口把包丢了。这不是你技术不行,是传统监控手段的颗粒度根本够不着这个场景。
这份《网络遥测(Network Telemetry)技术精细化网络运维实践》讲的就是怎么把网络从“黑匣子”变成“玻璃房”。核心思路两条线:一条是 gRPC,让交换机主动把 Buffer 使用率、CPU、内存、丢包事件推给监控服务器,解决“设备状态实时可见”的问题;另一条是 INT(In-band Network Telemetry),让报文自己携带路径上每一跳的入端口、出端口、时间戳、设备 ID,解决“转发路径和逐跳时延可见”的问题。适合谁看?数据中心网络运维工程师、HPC 集群网络负责人、以及正在做网络可观测性选型的架构师。如果你还在用 SNMP 轮询那套东西排查微突发丢包,这份材料会告诉你差距在哪。
2. 为什么 SNMP 和 NetFlow 在这个场景下不够用:从 TCP Incast 说起
2.1 缓存增长跑不过带宽增长,微突发丢包是结构性问题
先看一组硬数据。1Gbps 接口时代,主流交换芯片缓存大约 4MB;10Gbps 时代涨到 16MB;到了 25Gbps,缓存只有 32MB。接口速率翻了 25 倍,缓存只翻了 8 倍。按全端口公平使用缓存来算,可用缓存时间反而下降了 65%。这意味着什么?25G 网络下的 TCP Incast 现象比 10G 严重得多。
TCP Incast 的典型场景是这样的:一台 Master 节点向一组 Slave 节点发起计算任务请求,所有 Slave 几乎同时返回结果。对 Master 的上联端口来说,瞬间就是一个多打一的微突发流。如果这个突发量超过了出接口缓存能吸收的上限,尾部丢弃就发生了。应用层检测到丢包,触发 TCP 重传,端到端时延进一步恶化。更麻烦的是,这种丢包是微秒级的,SNMP 默认 5 分钟轮询一次,根本抓不到。
提示:判断你的网络是否存在 Incast 风险,可以看业务侧是否出现“周期性卡顿 + 重传率升高 + 交换机端口无持续拥塞”的组合特征。
2.2 SNMP 的轮询模型和 NetFlow 的采样模型各有硬伤
SNMP 的问题在于它是“拉”模型。监控服务器周期性去问设备:你现在的状态是什么?这个周期最短也就 15 秒到 1 分钟,对于微突发场景来说,等你问到的时候,拥塞早就发生并结束了。而且 SNMP 能拿到的 OID 有限,Buffer 的实时占用、端口的瞬时队列深度这些细粒度数据,标准 MIB 里根本没有。
NetFlow 和 sFlow 确实能推送流量采样信息,但它们推的是原始 IP 报文格式的采样数据,不是规范化的数据模型。你要自己解析、自己聚合,数据量还特别大。一个中等规模的数据中心,NetFlow 采集器每天处理几十亿条流记录是常态,扩展性很难跟上。更关键的是,NetFlow 只告诉你“谁和谁在通信”,不告诉你“报文在每一跳经历了什么”。转发路径、逐跳时延、Buffer 占用,这些它都不管。
2.3 gRPC 和 INT 分别补上了哪块拼图
gRPC 补的是“设备自身状态实时推送”这块。交换机作为 gRPC 客户端,主动向监控服务器(gRPC 服务端)建立 HTTP/2 通道,周期性推送 Buffer Usage、CPU、Memory 等数据。当 Buffer 不足导致丢包时,交换机实时上报丢包事件。注意,这里是交换机主动推,不是监控服务器去拉。时效性从分钟级提升到秒级甚至亚秒级。
INT 补的是“报文转发路径和逐跳时延”这块。它的工作方式是在报文里插入遥测元数据。首节点匹配到采样策略后,镜像报文并在四层头部后插入 INT 头,把入端口 ID、出端口 ID、入端口时间戳、出端口时间戳、设备 ID 封装成 MetaData 插进去。中间每一跳匹配到 INT 头后,继续追加本跳的 MD。最后一跳插完 MD 后,在外面封装一个 ERSPAN IP 头,外层目的 IP 是监控服务器地址,把整个 INT 报文转发过去。这样监控服务器就拿到了一条完整的路径画像:报文从哪进、从哪出、每一跳花了多少时间、经过哪些设备。
两者结合,一个管设备状态,一个管路径状态,合起来就是整网流量可视化。
3. 动手拆解 gRPC 推送配置:从交换机侧到监控服务器侧
3.1 交换机侧 gRPC 客户端配置的关键参数
不同厂商的配置命令有差异,但核心参数就那几个。下面以常见的数据中心交换机 CLI 风格为例,展示 gRPC 客户端的关键配置项。注意,这里不是让你照抄,是让你理解每个参数控制什么。
# 开启 gRPC 功能,指定监控服务器的 IP 和端口 grpc enable grpc server ip 192.168.10.100 port 50051 # 配置推送周期,单位毫秒,这里设为 1000ms 即 1 秒推一次 grpc push interval 1000 # 配置需要推送的遥测数据类型 grpc sensor-path buffer-usage enable grpc sensor-path cpu-usage enable grpc sensor-path memory-usage enable grpc sensor-path packet-drop-event enable # 配置丢包事件的触发阈值,Buffer 使用率超过 80% 开始上报 grpc threshold buffer-usage 80 # 指定 gRPC 通道使用 TLS 加密(如果监控服务器侧启用了 TLS) grpc tls enable逻辑说明:grpc enable是总开关。grpc server ip指定监控服务器地址和端口,交换机作为客户端主动向这个地址发起 HTTP/2 连接。grpc push interval控制周期性数据的推送频率,设得太短会增加交换机和监控服务器的负担,设得太长会丢失微突发细节,一般 500ms 到 1s 是常见起点。grpc sensor-path系列命令决定推什么数据,Buffer、CPU、Memory 是基础三件套,丢包事件是重点。grpc threshold是丢包事件的触发门限,Buffer 使用率超过设定值才上报,避免正常波动产生大量无效告警。
参数怎么改:如果你的监控服务器处理能力有限,先把push interval放到 2000ms,只开buffer-usage和packet-drop-event两个 sensor-path,跑稳了再逐步加。TLS 如果监控服务器侧没配,交换机侧开了也连不上,两边要一致。
3.2 监控服务器侧 gRPC 服务端的最小实现
监控服务器侧需要起一个 gRPC 服务端来接收交换机推送的数据。下面用 Python 写一个最小可运行的接收端,基于grpcio和protobuf。实际生产中你会用更完整的框架,但理解这个最小实现有助于排查连接问题。
import grpc from concurrent import futures import telemetry_pb2 import telemetry_pb2_grpc class TelemetryServicer(telemetry_pb2_grpc.TelemetryServiceServicer): def PushTelemetry(self, request, context): # request 里包含设备 ID、数据类型、时间戳、具体数值 device_id = request.device_id data_type = request.data_type timestamp = request.timestamp value = request.value # 丢包事件单独处理,触发告警 if data_type == "packet_drop_event": print(f"[DROP] device={device_id} time={timestamp} detail={value}") # 这里可以接入告警系统,比如写入 Kafka 或调用 webhook else: print(f"[DATA] device={device_id} type={data_type} value={value}") return telemetry_pb2.PushResponse(code=0, message="ok") def serve(): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) telemetry_pb2_grpc.add_TelemetryServiceServicer_to_server( TelemetryServicer(), server ) # 监听 50051 端口,与交换机侧配置一致 server.add_insecure_port('[::]:50051') server.start() print("gRPC telemetry server started on port 50051") server.wait_for_termination() if __name__ == '__main__': serve()逻辑说明:TelemetryServicer类实现了PushTelemetry方法,交换机每次推送数据都会调用这个方法。request对象里携带了设备 ID、数据类型、时间戳和具体数值。丢包事件通过data_type区分,单独走告警逻辑。grpc.server创建服务端实例,add_insecure_port监听端口,要和交换机侧grpc server ip配的端口一致。
参数说明:max_workers=10控制并发处理线程数,交换机数量多的时候要适当调大。add_insecure_port是不加密通道,如果交换机侧开了 TLS,这里要换成add_secure_port并加载证书。生产环境建议至少把接收到的数据写入消息队列,不要直接在 gRPC 回调里做重逻辑,否则会阻塞后续推送。
3.3 验证 gRPC 通道是否建连成功
配置完之后,第一件事是确认交换机有没有成功连上监控服务器。在交换机侧执行:
# 查看 gRPC 客户端状态 display grpc client status # 预期输出示例 # Server IP: 192.168.10.100 # Server Port: 50051 # Connection State: CONNECTED # Last Push Time: 2025-01-15 10:23:45 # Push Count: 1523如果Connection State显示DISCONNECTED或CONNECTING,按这个顺序排查:先ping监控服务器地址确认三层可达;再telnet 192.168.10.100 50051确认端口开放;然后检查监控服务器侧 gRPC 服务端是否真的在监听(netstat -tlnp | grep 50051);最后看两边 TLS 配置是否一致。常见翻车点是防火墙只开了 ICMP 没开 TCP 50051,或者监控服务器侧服务端绑定了127.0.0.1而不是0.0.0.0。
4. INT 逐跳元数据插入:从首节点到监控服务器的完整链路
4.1 INT 头的封装格式和 MetaData 字段含义
INT 的核心是在报文里插入逐跳的遥测数据。每个参与转发的交换机都会在 INT 头后面追加一层 MetaData。MetaData 里包含哪些字段,直接决定了你能看到什么粒度的信息。常见字段如下表:
| 字段名 | 长度 | 含义 | 运维用途 |
|---|---|---|---|
| Device ID | 4 字节 | 设备唯一标识 | 定位是哪台交换机 |
| Ingress Port ID | 2 字节 | 入端口编号 | 定位从哪个口进来 |
| Egress Port ID | 2 字节 | 出端口编号 | 定位从哪个口出去 |
| Ingress Timestamp | 8 字节 | 入端口时间戳 | 计算入向处理时延 |
| Egress Timestamp | 8 字节 | 出端口时间戳 | 计算出向转发时延 |
| Queue Depth | 4 字节 | 出队列深度 | 判断是否发生拥塞 |
这些字段不是每个厂商都全支持,具体支持哪些要看芯片能力。比如有些芯片不支持纳秒级时间戳,只能用微秒级。但 Device ID、入端口、出端口这三个是基础,一般都有。
4.2 首节点、中间节点、尾节点的处理差异
INT 的处理流程分三种角色,配置和逻辑都不一样。
首节点(Ingress Switch):报文进入网络的第一台交换机。它需要匹配采样策略,决定哪些报文要插 INT 头。匹配到之后,镜像报文,在四层头部后插入 INT 头,然后封装本跳的 MetaData。配置示例:
# 定义 INT 采样策略,匹配源 IP 为 10.0.0.0/24 的流量 int sample-policy policy1 match source-ip 10.0.0.0/24 action mirror-and-insert-int # 在入接口应用采样策略 interface Ethernet1/1 int sample-policy policy1 # 指定 INT 元数据要采集的字段 int metadata device-id enable int metadata ingress-port enable int metadata egress-port enable int metadata ingress-timestamp enable int metadata egress-timestamp enable中间节点(Transit Switch):报文经过的中间交换机。它不需要重新匹配采样策略,只需要识别 INT 头,然后在 INT 头后面追加本跳的 MetaData。配置相对简单:
# 全局开启 INT 透明传输和元数据追加 int transit enable int metadata device-id enable int metadata ingress-port enable int metadata egress-port enable int metadata ingress-timestamp enable int metadata egress-timestamp enable尾节点(Egress Switch):报文离开网络的最后一台交换机。它追加完本跳 MetaData 后,需要在报文外面封装一个 ERSPAN IP 头,外层目的 IP 是监控服务器地址,然后把整个 INT 报文转发给监控服务器。配置示例:
# 配置 ERSPAN 目的地址为监控服务器 int erspan destination 192.168.10.200 int erspan source 192.168.10.1 # 在出接口应用 INT 尾节点处理 interface Ethernet1/49 int egress-role enable逻辑说明:首节点的sample-policy决定了采样范围,不要全量采样,否则监控服务器会被海量 INT 报文打爆。一般按业务网段或特定五元组采样。中间节点只需要int transit enable,不需要重复配采样策略。尾节点的erspan destination指向监控服务器,erspan source是尾节点自己的一个可达 IP。
参数怎么改:采样率是核心参数。如果监控服务器处理能力有限,先把采样率降到 1:1000 甚至 1:10000,只采关键业务流。int metadata字段按需开启,时间戳字段对芯片有额外开销,如果只关心路径不关心时延,可以先不开时间戳。
4.3 监控服务器侧解析 INT 报文并还原路径
监控服务器收到 ERSPAN 封装的 INT 报文后,需要解封装、提取 MetaData、还原路径。下面用 Python 的 scapy 库写一个解析示例:
from scapy.all import sniff, Ether, IP, GRE, Raw import struct def parse_int_packet(pkt): # 剥掉外层 ERSPAN 封装(通常是 GRE 或 UDP) if pkt.haslayer(GRE): inner = pkt[GRE].payload else: return # 定位 INT 头,这里假设 INT 头紧跟在 TCP/UDP 之后 # 实际偏移量取决于具体封装格式,需要根据厂商文档调整 raw_bytes = bytes(inner) # 简化处理:假设 INT 头从固定偏移开始 int_offset = 42 # 以太头14 + IP头20 + TCP头8,仅作示例 int_data = raw_bytes[int_offset:] # 解析 MetaData 列表,每层 MD 固定长度 md_length = 28 # DeviceID 4 + InPort 2 + OutPort 2 + InTS 8 + OutTS 8 + QueueDepth 4 hop_count = len(int_data) // md_length path = [] for i in range(hop_count): md = int_data[i*md_length:(i+1)*md_length] device_id = struct.unpack('!I', md[0:4])[0] in_port = struct.unpack('!H', md[4:6])[0] out_port = struct.unpack('!H', md[6:8])[0] in_ts = struct.unpack('!Q', md[8:16])[0] out_ts = struct.unpack('!Q', md[16:24])[0] queue_depth = struct.unpack('!I', md[24:28])[0] path.append({ 'device_id': device_id, 'in_port': in_port, 'out_port': out_port, 'in_ts': in_ts, 'out_ts': out_ts, 'queue_depth': queue_depth, 'hop_latency_ns': out_ts - in_ts }) # 打印路径 for i, hop in enumerate(path): print(f"Hop {i}: device={hop['device_id']} " f"in={hop['in_port']} out={hop['out_port']} " f"latency={hop['hop_latency_ns']}ns " f"queue={hop['queue_depth']}") # 监听 ERSPAN 流量,假设走 GRE 协议 sniff(filter="proto gre", prn=parse_int_packet, store=0)逻辑说明:sniff抓取 GRE 封装的 ERSPAN 报文,parse_int_packet剥掉外层封装后定位 INT 头。md_length是每层 MetaData 的固定长度,按字段定义累加得到。循环解析每一跳的 Device ID、入端口、出端口、时间戳、队列深度,计算逐跳时延。最后按顺序打印路径。
参数说明:int_offset是 INT 头相对于内层报文起始位置的偏移量,这个值取决于具体封装格式,不同厂商实现不一样,需要根据实际抓包调整。md_length也要和交换机侧实际开启的字段对齐,如果没开 Queue Depth,长度要减 4。sniff的 filter 表达式按实际 ERSPAN 封装协议调整,有的用 GRE,有的用 UDP 4790。
5. 避坑与排查:INT+gRPC 落地时最容易翻车的五个点
5.1 交换机 gRPC 连接频繁断开重连
现象:display grpc client status显示 Connection State 在 CONNECTED 和 DISCONNECTED 之间反复跳变,监控服务器侧日志看到大量连接建立和断开记录。
原因:最常见的是 gRPC 服务端max_workers设得太小,交换机数量一多,并发推送请求把线程池打满,新连接被拒绝。其次是网络中间有防火墙或负载均衡设备,对 HTTP/2 长连接有空闲超时,超时后静默断连。
解决:把监控服务器侧max_workers调到交换机数量的 2 到 3 倍。在交换机侧开启 gRPC keepalive,定期发送心跳包维持连接。如果中间有防火墙,把空闲超时调到 300 秒以上。
5.2 INT 报文到了监控服务器但解析出来全是乱码
现象:tcpdump 能看到 ERSPAN 报文,但解析脚本提取的 Device ID、端口号全是异常值,路径完全对不上。
原因:INT 头的偏移量算错了。不同厂商在四层头部后插入 INT 头的位置有差异,有的在 TCP 头之后,有的在 UDP 头之后,还有的会跳过选项字段。另外,MetaData 的字段顺序和长度也可能和文档不一致,需要以实际抓包为准。
解决:先用 Wireshark 打开一个 INT 报文,手动逐字节对照厂商文档确认偏移量和字段布局。把int_offset和md_length改成实际值。建议先在测试环境用单条流验证,确认解析正确后再上生产。
5.3 Buffer 使用率推送正常但丢包事件一直不上报
现象:gRPC 通道正常,Buffer Usage 数据每秒都在推,但packet-drop-event从来没触发过,业务侧却确实有丢包。
原因:丢包事件的触发阈值设得太高。比如设了 80%,但实际丢包发生在 Buffer 使用率 60% 的时候,因为芯片的缓存分配策略不是全局共享,某些端口组可能提前耗尽。另外,有些芯片的丢包事件上报需要额外开启硬件计数器,默认是关闭的。
解决:把grpc threshold buffer-usage先降到 50% 观察,确认能触发后再逐步调回合理值。检查芯片是否支持丢包事件上报,如果不支持,只能靠周期性推送的 Buffer Usage 数据做趋势判断,无法做到实时告警。
5.4 INT 采样率过高导致监控服务器 CPU 打满
现象:监控服务器 CPU 持续 90% 以上,gRPC 数据接收延迟增大,INT 解析脚本处理不过来,Kafka 积压严重。
原因:采样率设得太激进。比如 1:1 全量采样,每条流都插 INT 头,监控服务器收到的报文量等于全网转发量,任何单机都扛不住。另外,INT 元数据字段开太多也会增加报文长度和处理开销。
解决:把采样率降到 1:1000 或更低,只采关键业务流。INT metadata 只开 Device ID、入端口、出端口三个基础字段,时间戳和队列深度按需开启。监控服务器侧用多线程或异步处理,解析和存储分离,解析完直接扔消息队列,不要在原地做重逻辑。
5.5 尾节点 ERSPAN 封装后报文被上游设备丢弃
现象:首节点和中间节点都正常插入了 INT 头,但监控服务器就是收不到 ERSPAN 报文。在尾节点上抓包能看到封装后的报文,但发出去就没了。
原因:ERSPAN 封装后的外层 IP 是监控服务器地址,但尾节点到监控服务器之间的路径上,某些设备可能不认识 ERSPAN 协议,或者 ACL 把 GRE/UDP 4790 封装的报文拦了。另外,如果外层 IP 的源地址不可达,回程路由也会有问题。
解决:确认尾节点到监控服务器三层可达,ping通。检查路径上所有设备的 ACL,放行 ERSPAN 封装协议(GRE 或 UDP 4790)。int erspan source配一个尾节点上实际存在的、有路由的 IP 地址,不要随便配一个不存在的地址。
6. 把 INT 和 gRPC 数据拼起来用:一个端到端故障定位的实操技巧
前面几章分别讲了 gRPC 和 INT 的配置与解析,但真正体现这套方案价值的地方,是把两类数据关联起来用。我一般会这么做:在监控服务器侧,把 gRPC 推送的 Buffer Usage 和丢包事件按设备 ID 和时间戳建索引,同时把 INT 解析出来的逐跳路径也按设备 ID 和时间戳建索引。当业务侧报障时,先根据时间窗口找到对应的丢包事件,拿到设备 ID 和端口号,再去 INT 路径库里查同一时间段经过该设备的流,还原出完整的转发路径和逐跳时延。
这个关联查询用 SQL 就能做,不需要上复杂的图数据库。下面是一个简化示例:
-- 查询指定时间窗口内发生丢包的设备及其端口 WITH drop_events AS ( SELECT device_id, port_id, drop_time, buffer_usage FROM grpc_drop_events WHERE drop_time BETWEEN '2025-01-15 10:23:00' AND '2025-01-15 10:23:10' ), -- 查询同一时间窗口内经过这些设备的 INT 路径 int_paths AS ( SELECT flow_id, device_id, in_port, out_port, hop_latency_ns, queue_depth, int_timestamp FROM int_hop_records WHERE int_timestamp BETWEEN '2025-01-15 10:23:00' AND '2025-01-15 10:23:10' ) SELECT d.device_id, d.port_id AS drop_port, d.buffer_usage, p.flow_id, p.in_port, p.out_port, p.hop_latency_ns, p.queue_depth FROM drop_events d JOIN int_paths p ON d.device_id = p.device_id AND d.port_id = p.out_port ORDER BY p.hop_latency_ns DESC;逻辑说明:drop_eventsCTE 从 gRPC 丢包事件表里捞出指定时间窗口的丢包记录,拿到设备 ID 和端口号。int_pathsCTE 从 INT 逐跳记录表里捞出同一时间窗口的路径数据。然后按设备 ID 和出端口做 JOIN,把丢包事件和经过该端口的流关联起来。最后按逐跳时延降序排列,时延最大的那跳就是最可疑的拥塞点。
参数说明:时间窗口不要开太大,10 秒左右比较合适,太大数据量会爆炸。d.port_id = p.out_port这个 JOIN 条件是关键,因为丢包发生在出端口方向。如果 INT 记录里没有出端口字段,这个查询就跑不了,所以前面配置 INT metadata 时出端口字段必须开。
这个查询跑出来的结果,直接告诉你:哪台交换机的哪个端口在什么时间发生了丢包,当时 Buffer 使用率是多少,哪些流经过了那个端口,每一跳的时延和队列深度是多少。拿着这个结果去定位问题,比盲目登设备看 counter 高效得多。
从那以后我每次上 INT 新版本或者调整采样策略,都会先跑一遍这个关联查询,确认丢包事件和 INT 路径能对上,再放开到全量业务。这个习惯帮我省了很多事后排查的时间。希望帮到你。
本文还有配套的精品资源,点击获取