第347篇:Telemetry 数据采集导致控制面拥塞案例

关键词

Telemetry、gRPC、gNMI、采样、控制面拥塞、CPU过载、数据采集、推送模型、采样频率


一、案例背景

1.1 故障现象

某运营商 Telemetry 采集故障:

时间:Telemetry 系统上线后第二周 影响:多台核心路由器 CPU 飙升到 90%+

现象: | 核心路由器 CPU 变化: CPU% ▲ 100 80 60 40 20 0 └──────────────────────────► 时间 Telemetry 上线 现象: ├─ CPU 从 30% 飙升到 85-95% ├─ BGP 保活超时(Hold Timer 到期) ├─ 路由收敛变慢(>10 秒) ├─ SNMP 响应超时 └─ SSH 管理会话频繁断开 | ████ ██████████ ██████████████ ████████████████ ████████████████████ | | --- | --- |

1.2 网络环境

Telemetry 采集架构:

      ┌──────────────────────┐
      │  Telemetry Collector  │
      │  (Kafka + InfluxDB)  │
      └──────────┬───────────┘
                 │ gRPC
                 │ Subscribe
      ┌──────────┴───────────┐
      │  采集器网络           │
      └──────────┬───────────┘
                 │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐
Core-1 50台 Core-2 50台 Core-3 50台

采集配置: ┌──────────────────────────────────────────┐ │ 采集方式:gRPC Dial-out(推送模式) │ │ 采样路径:/ifd/statistics/ │ │ / routing/bgp/peer/ │ │ / routing/route-table/ │ │ / system/process/memory/ │ │ 采样间隔:10 秒 │ │ 设备数量:150 台 │ │ 每设备采样点:500+ │ └──────────────────────────────────────────┘


二、原因分析

2.1 Telemetry 资源消耗分析

排查过程:

  第一步:确认 CPU 消耗来源
  ┌──────────────────────────────────────────┐
  │  # 查看进程 CPU 占用                     │
  │  display process cpu                     │
  │                                           │
  │  ┌──────────────────────────────────────┐ │
  │  │  Process              CPU%          │ │
  │  ├──────────────────────────────────────┤ │
  │  │  GRPC_MASTER          35%     ← 最高│ │
  │  │  GRPC_WORKER_0        20%           │ │
  │  │  GRPC_WORKER_1        18%           │ │
  │  │  BGP                   8%           │ │
  │  │  SNMPD                 5%           │ │
  │  │  ...                                 │ │
  │  └──────────────────────────────────────┘ │
  │                                           │
  │  → GRPC 进程 CPU 消耗 >50%,占比最高    │
  └──────────────────────────────────────────┘

  第二步:分析 Telemetry 数据量
  ┌──────────────────────────────────────────┐
  │  # 查看 Telemetry 数据统计               │
  │  display telemetry statistics             │
  │                                           │
  │  ┌──────────────────────────────────────┐ │
  │  │  Total subscription: 500            │ │
  │  │  Total data points/s: 50,000       │ │ ← 过高!
  │  │  Avg packet size: 2.5 KB            │ │
  │  │  Sample interval: 10s               │ │
  │  │  Collector: 192.168.1.100:10101     │ │
  │  └──────────────────────────────────────┘ │
  │                                           │
  │  # 单个设备每秒采集 50,000 个数据点      │
  │  # 150 台 × 50,000 = 7,500,000 数据点/秒 │
  │  # 每个数据点都要序列化、编码、封装       │
  │  # CPU 负担巨大!                         │
  └──────────────────────────────────────────┘

2.2 根因确认

根因分析:

问题汇总: | 1. 采样频率过高 ┌──────────────────────────────────────┐ └──────────────────────────────────────┘ 2. 采样路径过多 ┌──────────────────────────────────────┐ └──────────────────────────────────────┘ 3. 推送模式选择不当 ┌──────────────────────────────────────┐ └──────────────────────────────────────┘ 4. 采集器和设备之间网络带宽占用 ┌──────────────────────────────────────┐ | 10 秒间隔对 CPU 统计太频繁 10 秒间隔对路由表(几十万条)太频繁 建议:分级采样 └─ 接口统计:10 秒(合理) └─ 路由表:60 秒 └─ BGP 对等体:30 秒 每个路径 500+ 采样点 路由表路径采集全部路由条目 大型路由器路由表 50 万+ 条 每 10 秒全量采集 → 灾难 建议:按需采集,仅采集变更 Dial-out 模式下设备主动推送 Collector 处理能力不足时 设备端仍有序列化和发送开销 建议:大容量采集用 Dial-in 模式 每设备 ~125 KB/s 150 台 = 18.75 MB/s 管理网带宽:1GE 占用率:15% 可接受 但 CPU 是主要瓶颈 | | | --- | --- | --- |


三、解决方案

3.1 紧急优化

紧急降低 CPU 负载:

  步骤一:降低采样频率
  ┌──────────────────────────────────────────┐
  │  # 调整 Telemetry 采样间隔               │
  │  system-view                             │
  │  telemetry                                │
  │   sensor-group interface                 │
  │    sensor-path ifd/statistics/           │
  │    sample-interval 30000                 │  # 改为 30 秒
  │   sensor-group routing                    │
  │    sensor-path routing/route-table/      │
  │    sample-interval 120000                │  # 改为 120 秒
  │   sensor-group bgp                        │
  │    sensor-path routing/bgp/peer/         │
  │    sample-interval 60000                 │  # 改为 60 秒
  └──────────────────────────────────────────┘

  步骤二:减少采样路径
  ┌──────────────────────────────────────────┐
  │  # 移除不重要的采样路径                  │
  │  telemetry                                │
  │   sensor-group interface                  │
  │    # 只留关键接口统计                    │
  │    sensor-path ifd/statistics/ifDescr    │
  │    sensor-path ifd/statistics/in-octets  │
  │    sensor-path ifd/statistics/out-octets │
  │    # 移除:                               │
  │    #  sensor-path ifd/statistics/*       │  # 通配符删除
  └──────────────────────────────────────────┘

  步骤三:暂停非关键采集
  ┌──────────────────────────────────────────┐
  │  # 临时暂停部分采集                      │
  │  undo telemetry subscription all        │
  │  # 只保留关键订阅                        │
  │  telemetry subscription critical-only   │
  └──────────────────────────────────────────┘

3.2 架构优化

长期优化方案:

  方案一:分级采样策略
  ┌──────────────────────────────────────────┐
  │  数据分类        频率    采集方式        │
  ├──────────────────────────────────────────┤
  │  接口流量        30秒    全量采集        │
  │  CPU/内存        60秒    全量采集        │
  │  BGP 状态         30秒    全量采集        │
  │  路由表变化      触发式   仅变化推送     │
  │  温度/电源       120秒   全量采集        │
  │  OSPF 邻接        60秒    全量采集        │
  └──────────────────────────────────────────┘

  方案二:Dial-in 模式(推荐)
  ┌──────────────────────────────────────────┐
  │  Dial-out 与 Dial-in 对比:               │
  │                                           │
  │  Dial-out(当前):                        │
  │  └─ 设备主动推送到 Collector             │
  │  └─ 推空控制——Collector 无法控制频率    │
  │  └─ 设备 CPU 负担大                      │
  │                                           │
  │  Dial-in(推荐):                         │
  │  └─ Collector 主动订阅                   │
  │  └─ Collector 可控制采样频率             │
  │  └─ 设备只需响应请求                     │
  │  └─ 减轻设备 CPU 负担                    │
  │                                           │
  │  迁移方案:                                │
  │  └─ Collector 端改为 gNMI Dial-in        │
  │  └─ 设备端配置 gRPC 服务                 │
  │  └─ 逐步迁移,验证采全                   │
  └──────────────────────────────────────────┘

  方案三:增加采集节点
  ┌──────────────────────────────────────────┐
  │  当前:1 台 Collector → 150 台设备      │
  │  修改:3 台 Collector                     │
  │  └─ Collector-1:Core-1 ~ 50             │
  │  └─ Collector-2:Core-51 ~ 100           │
  │  └─ Collector-3:Core-101 ~ 150          │
  │                                           │
  │  好处:                                    │
  │  └─ 降低 Collector 端处理压力            │
  │  └─ 设备端负载不变                       │
  │  └─ 可实现 HA 冗余                      │
  └──────────────────────────────────────────┘

四、Telemetry 容量规划脚本

#!/usr/bin/env python3
"""
Telemetry 采集容量规划与风险评估工具
"""

from dataclasses import dataclass
from typing import List, Dict
import math


@dataclass
class TelemetryConfig:
    """Telemetry 配置"""
    device_count: int
    sensors: Dict[str, int]  # path -> sample points
    interval_sec: int
    avg_packet_size_kb: float
    mode: str  # "dial-out" or "dial-in"


@dataclass
class DeviceSpec:
    """设备规格"""
    cpu_cores: int
    cpu_freq_ghz: float
    cpu_per_point: float  # us per data point
    mgmt_bandwidth_gbps: float


class TelemetryCapacityPlanner:
    """Telemetry 容量规划器"""

    def __init__(self):
        self.telemetry_config = None
        self.device_spec = None

    def set_config(self, config: TelemetryConfig):
        self.telemetry_config = config

    def set_device_spec(self, spec: DeviceSpec):
        self.device_spec = spec

    def compute_data_points_per_second(self):
        """计算每秒数据点数"""
        config = self.telemetry_config
        total_points = sum(config.sensors.values())
        return total_points / config.interval_sec

    def compute_cpu_utilization(self):
        """估算 CPU 利用率"""
        config = self.telemetry_config
        spec = self.device_spec
        if not config or not spec:
            return 0

        points_per_sec = self.compute_data_points_per_second()
        cpu_time_per_sec = points_per_sec * spec.cpu_per_point * 1e-6

        if config.mode == "dial-out":
            # Dial-out 额外 30% 开销(推送处理)
            cpu_time_per_sec *= 1.3

        total_cpu_capacity = spec.cpu_cores * spec.cpu_freq_ghz * 1e9
        utilization = (cpu_time_per_sec / total_cpu_capacity) * 100

        return round(utilization, 2)

    def compute_bandwidth_usage(self):
        """估算带宽占用"""
        config = self.telemetry_config
        if not config:
            return 0

        points_per_sec = self.compute_data_points_per_second()
        packets_per_sec = points_per_sec / 100  # 假设每包 100 点
        bandwidth_kbps = (
            packets_per_sec * config.avg_packet_size_kb * 8
        )

        return round(bandwidth_kbps / 1000, 2)  # Mbps

    def assess_risk(self):
        """评估采集风险"""
        cpu = self.compute_cpu_utilization()
        bw = self.compute_bandwidth_usage()
        config = self.telemetry_config

        risks = []

        if cpu > 80:
            risks.append({
                "severity": "CRITICAL",
                "item": "CPU 利用率",
                "value": f"{cpu}%",
                "suggestion": (
                    "降低采样频率或减少采样路径"
                )
            })
        elif cpu > 50:
            risks.append({
                "severity": "WARNING",
                "item": "CPU 利用率",
                "value": f"{cpu}%",
                "suggestion": "监控 CPU 趋势"
            })

        if bw > config.device_count * 0.1:
            risks.append({
                "severity": "WARNING",
                "item": "带宽占用",
                "value": f"{bw} Mbps",
                "suggestion": "考虑增加管理带宽或使用 Dial-in"
            })

        return risks

    def generate_planning_report(self):
        """生成规划报告"""
        config = self.telemetry_config
        if not config:
            return ""

        points_ps = self.compute_data_points_per_second()
        cpu = self.compute_cpu_utilization()
        bw = self.compute_bandwidth_usage()
        risks = self.assess_risk()

        report = f"""
Telemetry 采集容量规划报告
{'=' * 60}

一、采集配置
  设备数量: {config.device_count}
  采集模式: {config.mode}
  采样间隔: {config.interval_sec}s
  每包大小: {config.avg_packet_size_kb} KB

  采样路径:
"""
        for path, points in config.sensors.items():
            report += f"    {path}: {points} 个采样点\n"

        report += f"""
二、资源估算
  每秒数据点数: {points_ps:.0f}
  预估 CPU 利用率: {cpu}%
  预估带宽占用: {bw} Mbps

三、风险评估
"""
        if risks:
            for r in risks:
                symbol = "🔴" if r["severity"] == "CRITICAL" else "🟡"
                report += f"  {symbol} [{r['severity']}] {r['item']}: {r['value']}\n"
                report += f"     建议: {r['suggestion']}\n"
        else:
            report += "  ✅ 当前配置在安全范围内\n"

        report += """
四、优化建议
"""
        if cpu > 80:
            report += """  1. 关键:降低采样频率或将高频采样间隔加倍
  2. 关键:减少通配符采样路径,使用精确路径
  3. 建议:将 Dial-out 改为 Dial-in
  4. 建议:分级采样(路由表低频,接口高频)
  5. 建议:增加采集节点(分片采集)
"""

        return report


def main():
    """主函数"""
    planner = TelemetryCapacityPlanner()

    # 模拟优化前的采集配置
    config_before = TelemetryConfig(
        device_count=150,
        sensors={
            "ifd/statistics/*": 500,
            "routing/route-table/*": 300,
            "routing/bgp/peer/*": 100,
            "system/process/memory/*": 20,
        },
        interval_sec=10,
        avg_packet_size_kb=2.5,
        mode="dial-out"
    )

    spec = DeviceSpec(
        cpu_cores=4,
        cpu_freq_ghz=2.0,
        cpu_per_point=5,  # 5us per data point
        mgmt_bandwidth_gbps=1.0
    )

    planner.set_config(config_before)
    planner.set_device_spec(spec)
    print("=== 优化前 ===")
    print(planner.generate_planning_report())

    # 模拟优化后的配置
    config_after = TelemetryConfig(
        device_count=150,
        sensors={
            "ifd/statistics/ifDescr": 50,
            "ifd/statistics/in-octets": 50,
            "ifd/statistics/out-octets": 50,
            "routing/route-table/changes": 5,
            "routing/bgp/peer/state": 10,
            "system/process/memory/used": 2,
        },
        interval_sec=30,
        avg_packet_size_kb=1.5,
        mode="dial-in"
    )

    planner.set_config(config_after)
    print("\n=== 优化后 ===")
    print(planner.generate_planning_report())


if __name__ == "__main__":
    main()

五、Telemetry 部署最佳实践

Telemetry 采集最佳实践:

规划阶段: ┌──────────────────────────────────────────┐ │ ✅ 评估设备 CPU 余量(至少保留 30%) │ │ ✅ 计算管理网带宽(推荐 10GE 管理网) │ │ ✅ 确定采集频率(分级策略) │ │ ✅ 规划 Collector 数量和容量 │ │ ✅ 小规模试点(先 5-10 台设备验证) │ └──────────────────────────────────────────┘

配置阶段: | ✅ 避免通配符采样路径 ✅ 精确指定采样点 ✅ 分级采样频率 ✅ 大容量场景用 Dial-in 模式 ✅ 配置采集超时和保护措施 | ├─ 接口统计:30 秒 ├─ BGP 对等体:30 秒 └─ 路由表:120 秒 | | --- | --- |

运维阶段: ┌──────────────────────────────────────────┐ │ ✅ 监控 GRPC 进程 CPU 占用 │ │ ✅ 监控管理链路带宽 │ │ ✅ 监控数据丢点和延迟 │ │ ✅ 定期审查采样路径必要性 │ │ ✅ 设备软件升级后重新评估 │ └──────────────────────────────────────────┘


六、总结

Telemetry 采集故障关键要点:

  1. 控制面保护
     └─ Telemetry 运行在控制面 CPU 上
     └─ 过度采集会影响路由协议和 SSH 管理
     └─ 必须保留 30%+ CPU 余量

  2. 容量规划
     └─ 先计算每秒数据点数量
     └─ 估算 CPU 和带宽消耗
     └─ 小规模验证后再全面推广

  3. 优化方向
     └─ 降低采样频率(分级策略)
     └─ 缩减采样路径(避免通配符)
     └─ 考虑 Dial-in 模式
     └─ 分布式 Collector 架构

下篇预告:第348篇《数据中心IP地址重规划与迁移实战案例》——以数据中心IP地址重规划项目为例,讲解地址迁移的方法论和实施要点。


下篇预告:第348篇《数据中心IP地址重规划与迁移实战案例》——以数据中心IP地址重规划项目为例,讲解地址迁移的方法论和实施要点。