揭秘云原生混布资源调度器Koordinator (五)MetricAdvisor 指标采集与分析

4 阅读15分钟

核心使命与设计理念

5.1 What - MetricAdvisor 是什么?

MetricAdvisor 是 Koordlet 中的指标采集和分析模块,负责实时收集节点和 Pod 的各种资源使用指标。

核心职责

  1. 采集节点级指标(CPU、内存、磁盘 I/O)
  2. 采集 Pod 级指标(CPU、内存、网络)
  3. 采集容器级指标(CGroup 统计)
  4. 进行预处理和去噪
  5. 定期推送指标到 MetricCache

采集指标类型

┌─────────────────────────────────────┐
│   MetricAdvisor 采集的指标类型     │
├─────────────────────────────────────┤
│                                     │
│ 1. CPU 指标                         │
│    ├─ CPU 使用率(%)              │
│    ├─ CPU 时间(user/system/wait) │
│    └─ CPU 调度延迟                 │
│                                     │
│ 2. 内存指标                         │
│    ├─ RSS(实际占用)              │
│    ├─ Cache(文件缓存)            │
│    ├─ Swap(交换分区)             │
│    └─ OOM 事件                      │
│                                     │
│ 3. 网络指标                         │
│    ├─ 接收/发送字节数              │
│    ├─ 接收/发送包数                │
│    └─ 丢包/错误率                  │
│                                     │
│ 4. 磁盘 I/O 指标                    │
│    ├─ 读/写字节数                  │
│    ├─ 读/写次数                    │
│    └─ I/O 延迟                      │
│                                     │
│ 5. 特殊指标                         │
│    ├─ Pod 冷启动时间                │
│    ├─ 容器启动时间                 │
│    └─ GC 暂停时间(Java)          │
│                                     │
└─────────────────────────────────────┘

5.2 Why - 为什么需要指标采集?

问题 1:需要了解资源的实际使用情况

Kubernetes 的资源模型局限性:

问题场景:
┌─────────────────────────────────────┐
 Pod 资源配置(静态)                
├─────────────────────────────────────┤
 request: 2 CPU, 4 GB 内存           
 limit:   4 CPU, 8 GB 内存           
                                     
 但实际使用(动态):                 
 ├─ 高峰时段: 3.8 CPU, 7.2 GB       
 ├─ 正常时段: 0.5 CPU, 2 GB         
 └─ 低谷时段: 0.1 CPU, 0.5 GB       
                                     
 问题:                               
 ├─ 无法根据实际使用动态优化         
 ├─ 不知道 Pod 何时会耗尽资源        
 ├─ 无法区分正常波动和异常          
 └─ 难以检测性能问题的根本原因       
└─────────────────────────────────────┘

MetricAdvisor 的价值:
├─ 实时掌握资源使用情况
├─ 提前发现异常趋势
├─ 为动态调度提供数据支撑
└─ 实现资源的细粒度管理

问题 2:需要检测资源干扰和异常

混部环境中的干扰检测:

场景:
├─ LS Pod(在线服务)声称需要 2 CPU
├─ BE Pod(离线计算)声称需要 4 CPU
├─ 节点总共只有 8 CPU
└─ 实际情况:BE 任务过载,导致 LS 延迟增加

不采集指标的危害:
├─ 无法察觉 BE 的过度使用
├─ 等到 LS 的用户投诉才知道问题
├─ 反应时间长,SLO 已经严重违反

采集指标的好处:
├─ 实时监测 CPU、内存、I/O 使用
├─ 主动发现 BE 的突发和增长趋势
├─ 及时抑制 BE 或驱逐 BE,保护 LS
└─ 防患于未然,避免 SLO 违反

问题 3:需要支持动态的资源优化

资源优化的决策链条:

采集 → 分析 → 决策 → 执行 → 验证

采集指标的用途:
1. NodeSLO 的自适应调整
   └─ 根据实际 CPU 利用率自动调整 BE 的限流参数

2. Reservation(资源预留)的大小决策
   └─ 根据 LS 的峰值需求预留合理大小

3. Pod 优先级的动态调整
   └─ 根据 Pod 的实际运行表现调整优先级

4. 性能曲线预测
   └─ 根据历史数据预测未来的需求

缺少指标采集:
└─ 所有决策都是静态的、被动的、低效的

5.3 How - 指标采集的核心机制

采集架构

┌─────────────────────────────────────────────────────┐
│                  采集数据流                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  /proc, /sys, cgroup 文件系统                       │
│  ↓ (读取性能计数器)                                  │
│  Linux Kernel 数据 (eBPF, perf_event)               │
│  ↓                                                  │
│  采集模块(MetricAdvisor)                           │
│  ├─ CPUCollector                                   │
│  ├─ MemoryCollector                                │
│  ├─ NetworkCollector                               │
│  ├─ DiskIOCollector                                │
│  └─ CustomCollector (Java/自定义)                  │
│  ↓ (预处理、去噪、聚合)                             │
│  MetricCache (TSDB)                                │
│  ↓                                                 │
│  QOSManager、RuntimeHooks、PredictServer 等         │
│                                                     │
└─────────────────────────────────────────────────────┘

四种采集器的详解

5.4 CPUCollector - CPU 采集

采集周期和粒度

采集配置示例:

默认配置:
├─ 全局 CPU: 采集周期 10 秒
├─ Pod 级 CPU: 采集周期 10 秒
├─ 容器级 CPU: 采集周期 5 秒
└─ CPU 调度延迟: 采集周期 1 秒 (可选)

高负载优化(> 80% CPU 利用率):
├─ 全局 CPU: 采集周期 5 秒 (加速)
├─ Pod 级 CPU: 采集周期 5 秒
├─ 容器级 CPU: 采集周期 2 秒 (加速)
└─ CPU 调度延迟: 采集周期 0.5 秒

数据来源:

1. /proc/stat - 全局 CPU
   ├─ cpu  user system idle wait iowait
   ├─ 粒度: 整个节点
   └─ 精度: jiffies (< 10ms)

2. /proc/[pid]/stat - Pod 级 CPU
   ├─ utime stime cutime cstime
   ├─ 粒度: 进程
   └─ 精度: jiffies

3. cgroup/cpuacct - 容器级 CPU
   ├─ cpuacct.usage 文件
   ├─ 粒度: cgroup(所有进程和子进程)
   └─ 精度: nanoseconds

采集公式:

cpu_usage_percent(t) = (cgroup_usage(t) - cgroup_usage(t-1)) /
                       (time(t) - time(t-1)) / CPU_NUM

示例:
├─ 时刻 T=10:00: cgroup_usage = 1000000000 ns (1 秒 CPU 时间)
├─ 时刻 T=10:10: cgroup_usage = 3000000000 ns (3 秒 CPU 时间)
├─ 采集周期: 10 秒
├─ 节点 CPU 数: 8
└─ cpu_usage = (3000000000 - 1000000000) / (10 秒) / 8 = 25%

生产案例:突发流量检测

场景:在线服务突然收到流量洪峰

时间线:

T=10:00  正常状态
         ├─ CPU 使用: 2%(空闲)
         ├─ request 配置: 2 CPU
         └─ 缓冲能力: 还有 1.98 CPU 可用

T=10:05  流量洪峰开始到达
         ├─ 新请求持续进来
         └─ CPU 开始增加

T=10:10  CPUCollector 采集(周期 10 秒)
         ├─ CPU 使用: 8%
         ├─ 趋势: 增长中
         └─ MetricCache 记录数据点

T=10:15  继续采集
         ├─ CPU 使用: 15%
         ├─ 增长速度: 1% 每 5 秒
         └─ 预测: 继续增长,1 分钟内可能达到 25%

T=10:20  快速决策
         ├─ QOSManager 看到趋势
         ├─ 决定: 开始抑制 BE Pod(在 request 达到前)
         └─ 避免: BE 耗尽剩余的 CPU,导致 LS 卡顿

T=10:25  流量达到峰值
         ├─ LS Pod 消耗: 1.9 CPU(接近 limit)
         ├─ BE Pod 消耗: 0.3 CPU(被抑制)
         └─ 结果: LS 延迟 < 100ms(正常),BE 继续运行

性能指标

CPUCollector 的开销:

采集一次的成本:
├─ 读取 /proc/stat: ~0.1ms
├─ 读取所有 Pod 的 cgroup: ~1ms (200 Pod)
├─ 计算: ~0.2ms
└─ 总计: ~1.3ms

对系统的影响:
├─ CPU 占用: < 1% 的一个核心
├─ 内存占用: ~5 MB (用于存储近期数据)
└─ 磁盘 I/O: 最少(只读 /proc/sys)

5.5 MemoryCollector - 内存采集

采集周期和粒度

内存采集的特点(与 CPU 不同):

内存变化较慢:
├─ 采集周期: 30 秒(比 CPU 的 10 秒更长)
├─ 原因: 内存不像 CPU 那样频繁波动
└─ 优化: 如果检测到快速增长,加速采集

异常检测:

内存泄漏检测:
├─ 如果 5 分钟内 RSS 增长 > 10%
├─ 触发更频繁的采集(加速到 5 秒)
├─ 并告警给管理员

OOM 预测:
├─ 如果 RSS 以当前速度增长
├─ 会在多长时间内触发 OOM?
├─ 例:当前 7 GB,limit 8 GB,速度 100 MB/min
├─ 预测: 10 分钟内触发 OOM
└─ 提前通知 QOSManager,可能驱逐 Pod

内存数据来源:

1. /proc/self/status - 进程级内存
   ├─ VmRSS: 实际物理内存使用
   ├─ VmSwap: Swap 使用
   └─ 粒度: 单个进程

2. /proc/self/smaps - 详细内存分布
   ├─ 按 mmap 段分类统计
   ├─ 可以看出哪些库消耗内存最多
   └─ 更精确但开销较大

3. cgroup/memory - cgroup 级内存
   ├─ memory.usage_in_bytes: 总使用量
   ├─ memory.stat: 包括 cache/rss/swap/dirty
   └─ 粒度: 整个 cgroup(容器)

4. /proc/meminfo - 全局内存
   ├─ MemTotal, MemAvailable, Buffers, Cached
   └─ 用于计算全局内存压力

采集公式:

Pod_RSS(t) = sum(container_rss(pid) for pid in cgroup)

Pod_Memory_Pressure = (MemTotal - MemAvailable) / MemTotal

异常判断:
├─ 如果 Pod_Memory_Pressure > 90%: 高压
├─ 如果 Pod_Memory_Pressure > 95%: 极端压力
└─ 通知 QOSManager 可能进行内存隔离

生产案例:内存泄漏导致 OOM 的预早发现

场景:某个应用有内存泄漏,无法被发现直到 OOM

传统方式(无指标):
┌─────────────────────────────────┐
│ T=Day 0: Pod 创建                │
│ ├─ 内存: 100 MB                 │
│                                 │
│ T=Day 3: 观察日志                │
│ ├─ 没人看 metrics                │
│ ├─ 内存已增长到 7.5 GB          │
│                                 │
│ T=Day 3 10:05: OOM killed!      │
│ ├─ 用户抱怨服务中断              │
│ ├─ Pod 重启                      │
│ ├─ 循环重复                      │
│ └─ SLO 严重违反                  │
│                                 │
│ 人工排查:花费 2 小时才发现内存泄漏 │
└─────────────────────────────────┘

使用 MetricAdvisor(有指标):
┌─────────────────────────────────┐
│ T=Day 0 00:00: Pod 创建          │
│ ├─ 内存: 100 MB                 │
│ ├─ MetricCache 开始记录          │
│                                 │
│ T=Day 0 12:00: MemoryCollector  │
│ ├─ 检测: 内存在 12h 内增长 60%   │
│ ├─ 加速采集: 改为 5 秒一次       │
│                                 │
│ T=Day 1 08:00: 预测 OOM         │
│ ├─ 当前: 4 GB                  │
│ ├─ 增长速度: 200 MB/hour        │
│ ├─ 预计 OOM 时间: 20 小时        │
│ ├─ QOSManager 通知: 考虑迁移    │
│                                 │
│ T=Day 1 10:00: 主动迁移          │
│ ├─ 发布新版本修复内存泄漏        │
│ ├─ 灰度: 先迁移 10% 的实例      │
│                                 │
│ T=Day 1 12:00: 验证新版本        │
│ ├─ 新版本: 内存稳定在 200 MB    │
│ ├─ 全量更新: 迁移所有实例        │
│                                 │
│ 总体: 在 OOM 发生前 18 小时发现问题 │
│ SLO 保持 99.99%                  │
└─────────────────────────────────┘

采集和分析的关键步骤:

1. MemoryCollector 采集(30 秒周期)
   └─ 记录 Pod 的内存使用

2. MetricCache 保存(1 周数据)
   └─ 保存 10080 个数据点

3. 趋势分析(QOSManager)
   └─ 如果 1h 内增长 > 5%,标记为异常

4. 预测(PredictServer)
   └─ 基于最近 6h 数据,预测 24h 后的值

5. 告警(Webhook)
   └─ 触发告警,通知用户/自动化系统

6. 响应(自动化)
   └─ 自动迁移、自动回滚或人工审查

5.6 NetworkCollector - 网络采集

采集周期和粒度

网络采集的特点:

变化频率:
├─ 基础连接: 相对稳定
├─ 数据吞吐量: 波动较大
└─ 采集周期: 15 秒

数据来源:

1. /proc/net/dev - 网络接口统计
   ├─ 接收/发送字节数
   ├─ 接收/发送包数
   ├─ 丢包/错误数
   └─ 粒度: 网络接口级

2. netstat / ss - 连接统计
   ├─ 活跃连接数
   ├─ 半开连接数
   ├─ TIME_WAIT 连接数
   └─ 开销较大,采集频率较低

3. iptables counters - 流量匹配
   ├─ 通过 iptables 规则统计特定流量
   └─ 用于 Pod 间流量分析

采集公式:

received_bps = (bytes_recv(t) - bytes_recv(t-1)) /
               (time(t) - time(t-1))

dropped_percent = packets_drop(t) / (packets_recv(t) + packets_drop(t))

生产案例:网络包丢失问题排查

场景:集群内某些服务间通信延迟增加

症状:
├─ 服务 A 调用服务 Bp99 延迟从 5ms 增加到 50ms
├─ 用户投诉购物车响应变慢
├─ 是不是服务 B 的问题?

传统排查(无指标):
├─ 查看 BCPU: 10%(不是)
├─ 查看 B 的内存: 2 GB(不是)
├─ 查看 B 的日志: 没有错误(不知道)
├─ 怀疑: 是不是网络问题?
├─ 结果: 花费 2 小时才发现包丢失

使用 MetricAdvisor(有指标):
├─ NetworkCollector 采集(15 秒周期)
├─ 检测到: 包丢失率从 0% 增加到 5%
├─ 原因分析:
│  ├─ 同节点流量是否增加? 查看网络吞吐量
│  ├─ 是跨节点还是同节点丢失? 检查网络路径
│  └─ 是哪个方向丢失? 入站还是出站?
├─ 调查:
│  ├─ 查看其他 Pod: 也有丢失
│  ├─ 结论: 是网络交换机/拥塞问题
│  └─ 不是服务 B 的问题
├─ 解决:
│  ├─ 减少跨节点流量 (分散 Pod)
│  ├─ 或升级网络设备
│  └─ 5 分钟恢复正常

关键优势:
└─ 从 2 小时排查时间缩短到 5 分钟定位

5.7 CustomCollector - 自定义指标采集

扩展机制

三种方式扩展采集能力:

1. 内置支持(开箱即用)
   ├─ Java GC 时间(通过 Java Agent)
   ├─ Go GC 时间(通过 PProf)
   └─ 数据库连接池使用
   
2. RuntimeProxy 钩子
   ├─ 在容器启动时注入探针
   ├─ 动态收集应用内部指标
   └─ 无需修改应用代码

3. Webhook 扩展
   ├─ 允许第三方采集器集成
   ├─ 例: Prometheus、OpenTelemetry
   └─ 通过标准接口推送数据

分 - 采集数据的生产案例

5.8 大规模集群的采集性能

场景:1000 个节点的生产集群

集群规模:
├─ 节点数: 1000
├─ 平均每节点 Pod: 200
├─ 总 Pod 数: 200,000
└─ 总容器数: 300,000

采集负载分析:

CPUCollector 的成本:
├─ 周期: 10 秒
├─ 每次采集 1 个节点需要时间:
│  ├─ 读 /proc/stat: 0.1ms
│  ├─ 读 200 个 Pod cgroup: 2ms
│  └─ 总计: 2.1ms
├─ 每秒的 CPU 占用: 2.1ms / 10s = 0.021% (可忽略)
└─ 1000 节点总开销: ~100ms/秒(一个核心的 2%)

MemoryCollector 的成本:
├─ 周期: 30 秒
├─ 每次采集 1 个节点: 5ms
├─ 每秒的 CPU 占用: 5ms / 30s = 0.017%
└─ 1000 节点总开销: ~170ms/秒

NetworkCollector 的成本:
├─ 周期: 15 秒
├─ 每次采集 1 个节点: 3ms
├─ 每秒的 CPU 占用: 3ms / 15s = 0.02%
└─ 1000 节点总开销: ~200ms/秒

总体评估:
├─ CPU 占用: < 1 个核心(1000 节点集群中很小)
├─ 内存占用: 每节点 ~50 MB × 1000 = 50 GB
│  (分布在各节点,不是中央集中)
└─ 网络开销: 仅向中央 MetricCache 推送(推拉模式)

采集频率的自适应

动态调整采集周期:

正常情况:
├─ CPU: 10 秒
├─ 内存: 30 秒
└─ 网络: 15 秒

检测到异常时加速:

CPU 高于 80% 时:
├─ 加速到 5 秒
├─ 目的: 更快发现下一步操作的机会

内存增长超过 1%/min 时:
├─ 加速到 5 秒
├─ 目的: 提早发现泄漏

网络丢包 > 1% 时:
├─ 加速到 5 秒
├─ 目的: 实时监测恢复情况

检测到恢复正常时降速:
├─ 如果异常持续 10 分钟以上无变化
├─ 降速回到基础周期
├─ 目的: 节省资源

5.9 数据质量和去噪

指标的噪声问题

原始数据的质量问题:

问题 1: 频率波动
├─ CPU 使用率: 0%, 5%, 0%, 2%, 8%, 1%
├─ 原因: 程序的碎片化执行
├─ 影响: 难以看出真实趋势
└─ 解决: 滑动平均

问题 2: 偶发毛刺
├─ 某次采集时值异常高
├─ 原因: 采集时点的巧合,或采集过程耗时
├─ 影响: 错误告警
└─ 解决: 异常值剔除 + 分位数

问题 3: 采集延迟
├─ /proc 文件系统不是实时的
├─ 可能反映的是过去的状态
└─ 解决: 考虑延迟偏移和平滑

去噪技术示例:

原始序列:
    0,  5,  0,  2,  8,  1,  3, 98,  2,  4  (最后一个 98 是毛刺)

步骤 1: 去除毛刺(异常值检测)
    使用 3-sigma 规则: 超过 mean ± 3*std 的值认为异常
    平均值: 12.3, std: 28.5, 阈值: 12.3 ± 85.5 = [-73, 97.8]
    结果: 98 被检测为异常
    └─ 去除或用中位数代替

步骤 2: 滑动平均(平滑)
    窗口大小: 3
    结果:
    ├─ (0+5+0)/3 = 1.67
    ├─ (5+0+2)/3 = 2.33
    ├─ (0+2+8)/3 = 3.33
    ├─ (2+8+1)/3 = 3.67
    ├─ (8+1+3)/3 = 4.0
    ├─ (1+3+2)/3 = 2.0   (98 已去除)
    └─ 最终: 光滑的序列

步骤 3: 分位数统计
    P50 (中位数): 3.0   (代表典型值)
    P95: 4.5          (代表 95% 的情况)
    P99: 4.8          (代表极端情况)

使用场景:
├─ P50: 作为常用决策值(通常情况)
├─ P95: 作为高负载规划(高峰准备)
└─ P99: 作为极端容量规划(最坏情况)

总 - 生产调优指南

5.10 采集性能优化

问题 1:采集延迟过高

症状:从采集数据到 MetricCache 可用,延迟 > 30 秒

可能原因:
├─ /proc 文件系统 I/O 慢(老式硬盘)
├─ cgroup 数量过多(> 10000)
├─ 其他进程频繁读取 /proc

诊断:
$ time cat /proc/stat  # 测试单次读取时间
$ time cat /proc/*/stat > /dev/null  # 测试全量读取

优化方案:
1. 批量读取
   └─ 不要逐个 Pod 读取 cgroup,用 cgroup.events 批量读取

2. 缓存中间结果
   └─ 缓存 /proc/[pid] 的查询,减少 inode 查询

3. 使用 cgroup v2
   └─ cgroup v2 的接口更高效,开销减少 30%

4. 采集周期调整
   └─ 如果 I/O 是瓶颈,延长周期(不影响决策延迟)

问题 2:内存占用过高

症状:MetricAdvisor 占用 > 500 MB 内存

可能原因:
├─ 保存的历史数据过多
├─ MetricCache 没有及时清理过期数据
├─ 采集的自定义指标过多

诊断:
$ pprof http://localhost:6060/debug/pprof/heap

优化方案:
1. 减少数据保留时间
   └─ 改为 1 周保留,而不是 42. 下采样
   └─ 降采样旧数据: 1min -> 5min -> 1hour

3. 删除不需要的指标
   └─ 关闭不使用的 CustomCollector

问题 3:采集数据不准确

症状:MetricCache 中的 CPU 使用率与 top 命令的结果不匹配

可能原因:
├─ 采集周期过长,无法捕捉短突发
├─ cgroup 的计数器有 bug(某些内核版本)
├─ 包含了不应该包含的进程

诊断:
$ cat /proc/[pid]/stat  # 对比单个进程
$ cat /proc/stat        # 对比全局值
$ cgroup stats          # 对比 cgroup 报告

优化方案:
1. 缩短采集周期
   └─ CPU 改为 5 秒

2. 升级内核
   └─ 某些 cgroup bugs 在新内核中已修复

3. 验证计算公式
   └─ 确保单位一致(nanoseconds vs jiffies)

5.11 生产配置建议

# MetricAdvisor 配置示例
apiVersion: v1
kind: ConfigMap
metadata:
  name: koordlet-config
  namespace: koordinator-system
data:
  koordlet-config.yaml: |
    metricAdvisor:
      # CPU 采集
      cpu:
        enabled: true
        collectIntervalSeconds: 10  # 正常 10 秒
        fastCollectIntervalSeconds: 5  # 高负载 5 秒
        cpuThresholdPercent: 80     # 超过 80% 触发加速
      
      # 内存采集
      memory:
        enabled: true
        collectIntervalSeconds: 30
        fastCollectIntervalSeconds: 5
        leakDetectionThresholdPercent: 5  # 1h 增长 5% 以上
      
      # 网络采集
      network:
        enabled: true
        collectIntervalSeconds: 15
        fastCollectIntervalSeconds: 5
        packetLossThresholdPercent: 1  # 丢包 > 1% 加速
      
      # 数据保留
      dataRetention:
        metricsWindowDuration: 168h  # 保留 1 周
        queryTimeout: 10s
        downSampleRules:
          - after: 24h
            step: 5m    # 1 天后降采样到 5 分钟
          - after: 168h
            step: 1h    # 7 天后降采样到 1 小时

5.12 监控指标

# MetricAdvisor 关键指标

# 采集延迟
koordlet_metric_collector_latency_milliseconds{collector="cpu"}
koordlet_metric_collector_latency_milliseconds{collector="memory"}
koordlet_metric_collector_latency_milliseconds{collector="network"}

# 采集失败率
koordlet_metric_collector_errors_total{collector="cpu"}

# 缓存大小
koordlet_metric_cache_size_bytes

# 内存泄漏告警
koordlet_memory_growth_rate_percent  # > 5 触发

# 网络丢包告警
koordlet_network_packet_loss_percent  # > 1 触发

总结 - 章节要点汇总

5.13 关键概念速查

概念含义采集周期
CPUCollectorCPU 使用率采集10s(加速 5s)
MemoryCollector内存使用采集30s(加速 5s)
NetworkCollector网络指标采集15s(加速 5s)
CustomCollector应用特定指标可配置
去噪异常值检测和平滑实时进行

5.14 最佳实践

  • 根据异常自动加速采集,降低发现延迟
  • 定期验证采集数据与实际系统状态的一致性
  • 合理保留历史数据,不要过长也不要过短
  • 采用分位数而不仅看平均值,掌握真实分布
  • 监控采集器本身的性能,不让监控成为瓶颈

本章要点

  • 理解指标采集的四种核心收集器和采集原理
  • 掌握自适应采集频率的决策逻辑
  • 学会通过指标数据提早发现问题和预测异常
  • 理解大规模集群中采集的性能与资源成本