核心使命与设计理念
5.1 What - MetricAdvisor 是什么?
MetricAdvisor 是 Koordlet 中的指标采集和分析模块,负责实时收集节点和 Pod 的各种资源使用指标。
核心职责:
- 采集节点级指标(CPU、内存、磁盘 I/O)
- 采集 Pod 级指标(CPU、内存、网络)
- 采集容器级指标(CGroup 统计)
- 进行预处理和去噪
- 定期推送指标到 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 调用服务 B,p99 延迟从 5ms 增加到 50ms
├─ 用户投诉购物车响应变慢
├─ 是不是服务 B 的问题?
传统排查(无指标):
├─ 查看 B 的 CPU: 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 周保留,而不是 4 周
2. 下采样
└─ 降采样旧数据: 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 关键概念速查
| 概念 | 含义 | 采集周期 |
|---|---|---|
| CPUCollector | CPU 使用率采集 | 10s(加速 5s) |
| MemoryCollector | 内存使用采集 | 30s(加速 5s) |
| NetworkCollector | 网络指标采集 | 15s(加速 5s) |
| CustomCollector | 应用特定指标 | 可配置 |
| 去噪 | 异常值检测和平滑 | 实时进行 |
5.14 最佳实践
- 根据异常自动加速采集,降低发现延迟
- 定期验证采集数据与实际系统状态的一致性
- 合理保留历史数据,不要过长也不要过短
- 采用分位数而不仅看平均值,掌握真实分布
- 监控采集器本身的性能,不让监控成为瓶颈
本章要点:
- 理解指标采集的四种核心收集器和采集原理
- 掌握自适应采集频率的决策逻辑
- 学会通过指标数据提早发现问题和预测异常
- 理解大规模集群中采集的性能与资源成本