揭秘云原生混布资源调度器Koordinator (三)Koordlet 整体架构与工作流程

33 阅读11分钟

Koordlet 的核心地位

3.1 What - Koordlet 是什么?

Koordlet 是 Koordinator 在每个 Kubernetes Node 上运行的 DaemonSet,是节点侧的核心执行引擎

核心职责(指标采集 → 策略计算 → 资源执行的闭环):

  1. 指标采集:收集节点和容器的性能指标(CPU、内存、IO、干扰信号等)
  2. 状态同步:监听 Pod、Node、NodeSLO 的变化,维护本地缓存
  3. 策略计算:根据指标和 NodeSLO 配置,计算资源调控策略
  4. 资源执行:实际修改 CGroup 参数,落地调控策略
  5. 指标上报:聚合指标数据,上报给控制面供调度和管理决策

3.2 Why - 为什么需要 Koordlet?

问题 1:原生系统无法感知干扰程度

原生 kubelet 的局限:
├─ 只能看到 Pod 是否 OOM(Binary:是/否)
├─ 无法量化 BE 任务对 LS 应用的干扰程度
├─ 无法判断应该采取什么行动(抑制/驱逐?多少程度?)
└─ 导致不得不采取保守的资源隔离策略

Koordlet 的方案:
├─ 采集底层干扰指标:CPU schedule latency、缓存未命中率等
├─ 精确量化干扰程度(0-100%)
├─ 根据干扰程度动态调整 CPU quota(从 4 core → 1.2 core)
└─ 实现精细化的混部调度

问题 2:容器启动后无法修改资源配置

原生方案:
├─ Pod 启动时指定 CPU request/limit
├─ 一旦创建,这些值就固定不变
└─ 无法根据集群资源压力动态调整

Koordlet 的方案:
├─ 运行时动态修改 CGroup 的 cpu.max(CPU 配额)
├─ 运行时动态修改 memory.high(内存限制)
├─ 平时宽松,紧急时严格
└─ 实现真正的动态资源管理

问题 3:无法精细化隔离 LS 和 BE

原生 CGroup 的隔离机制:
├─ CPU share:比例式分配(有限制条件下)
├─ Memory limit:硬限制(容易 OOM)
└─ 无法区分优先级(高优先级和低优先级混合在一起)

Koordlet 的创新:
├─ CPU group identity:内核级的优先级(比 share 更优先)
│  └─ groupIdentity = 0(LSR)优先级最高
│  └─ groupIdentity = -1(BE)优先级最低
├─ Memory QoS:三级保护(min/low/high)
│  └─ LSR: min=100%, low=100% → 即使系统紧张也很难被回收
│  └─ BE: min=0%, low=0% → 优先被回收
├─ LLC isolation:缓存隔离
│  └─ 防止 BE 的大容量工作集污染 LS 的热数据
└─ 多维度的隔离组合,远强于原生方案

分 - Koordlet 的七大核心模块

3.3 完整架构与模块组成

┌─────────────────────────────────────────────────────────────┐
│                       Koordlet DaemonSet                    │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 1️⃣ StatesInformer (状态同步与维护)                  │  │
│  │                                                      │  │
│  │  职责:维护本地资源状态视图                         │  │
│  │  ├─ Watch 并缓存所有本节点 Pod                     │  │
│  │  ├─ Watch 本节点信息                               │  │
│  │  ├─ Watch NodeSLO 配置                             │  │
│  │  └─ 提供高效的查询接口                             │  │
│  │                                                      │  │
│  │  数据规模(200 Pod 节点):                         │  │
│  │  ├─ Pod 缓存:~50 MB                               │  │
│  │  ├─ Watch 延迟:< 1 秒(通常)                      │  │
│  │  └─ 查询延迟:< 1 ms                               │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 2️⃣ MetricAdvisor (指标采集框架)                    │  │
│  │                                                      │  │
│  │  职责:采集节点和容器的性能指标                     │  │
│  │  ├─ NodeCollector:节点级指标                      │  │
│  │  │  ├─ CPU/Memory/IO 使用率                       │  │
│  │  │  ├─ CPI(Cycles Per Instruction)             │  │
│  │  │  ├─ Cache miss ratio                           │  │
│  │  │  └─ 采集源:cgroup、procfs、BPF、perf          │  │
│  │  │                                                  │  │
│  │  ├─ ContainerCollector:容器级指标                 │  │
│  │  │  ├─ 各容器 CPU/Memory/IO 使用                  │  │
│  │  │  ├─ Throttled 时间(被抑制)                   │  │
│  │  │  └─ 采集源:cgroup、Docker stats、CRI API      │  │
│  │  │                                                  │  │
│  │  └─ BECPUSuppressionCollector:BE 干扰检测        │  │
│  │     ├─ CPU schedule latency(通过 BPF)          │  │
│  │     ├─ LLC miss ratio(通过 perf)               │  │
│  │     └─ 用于检测干扰程度                            │  │
│  │                                                      │  │
│  │  采集周期:                                          │  │
│  │  ├─ 默认:60 秒                                     │  │
│  │  ├─ 可配置:10-600 秒                              │  │
│  │  └─ 紧急情况:可加速到 10 秒                        │  │
│  │                                                      │  │
│  │  数据规模(200 Pod 节点,1 min 采集):           │  │
│  │  ├─ 单次采集:~100 KB                             │  │
│  │  ├─ 每小时:~100 KB × 60 = ~6 MB                 │  │
│  │  └─ 保留 7 天:~1 GB                              │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 3️⃣ MetricCache (指标缓存与查询)                    │  │
│  │                                                      │  │
│  │  职责:缓存历史指标,支持查询和聚合                 │  │
│  │  ├─ 数据结构:时间序列数据库(TSDB)               │  │
│  │  ├─ 存储方式:内存或 SQLite                        │  │
│  │  ├─ 缓存时间:保留 24-72 小时                      │  │
│  │  └─ 支持查询:                                      │  │
│  │     ├─ 当前值                                      │  │
│  │     ├─ P50/P95/P99 百分位值                       │  │
│  │     ├─ 平均值、最大值                              │  │
│  │     └─ 趋势(上升/下降)                           │  │
│  │                                                      │  │
│  │  查询延迟:< 10 ms                                  │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 4️⃣ QOSManager (QoS 策略执行框架)                   │  │
│  │                                                      │  │
│  │  职责:计算和执行 QoS 策略                         │  │
│  │  ├─ 周期:每秒执行一次                             │  │
│  │  ├─ Plugin 架构(可扩展):                         │  │
│  │  │  ├─ CPUSuppress:动态调整 CPU quota           │  │
│  │  │  │  └─ 降低 BE Pod 的 CPU quota               │  │
│  │  │  ├─ MemoryEvict:内存驱逐                      │  │
│  │  │  │  └─ 选择并驱逐低优先级 Pod                  │  │
│  │  │  ├─ CPUBurst:CPU 动态提升                    │  │
│  │  │  │  └─ 在有空闲时允许超额使用                  │  │
│  │  │  ├─ Resctrl:缓存隔离                          │  │
│  │  │  │  └─ 配置 LLC CAT(Cache Allocation Tag)  │  │
│  │  │  ├─ BlkIO:磁盘限流                            │  │
│  │  │  │  └─ 限制 IO 吞吐量和 IOPS                  │  │
│  │  │  └─ CGReconcile:CGroup 参数协调              │  │
│  │  │     └─ 解决多个 Plugin 的参数冲突              │  │
│  │  │                                                  │  │
│  │  └─ 执行流程:                                      │  │
│  │     ├─ 收集指标 → 判断压力等级 → 执行 Plugin      │  │
│  │     ├─ 合并冲突 → 批量提交 ResourceExecutor       │  │
│  │     └─ 上报状态                                    │  │
│  │                                                      │  │
│  │  性能指标:                                         │  │
│  │  ├─ 周期延迟:通常 < 100 ms                        │  │
│  │  ├─ 能同时管理:500+ Pod                          │  │
│  │  └─ CPU 开销:< 5% 单核                           │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 5️⃣ ResourceExecutor (资源执行器)                   │  │
│  │                                                      │  │
│  │  职责:实际修改 CGroup 参数                        │  │
│  │  ├─ 操作类型:                                      │  │
│  │  │  ├─ 创建 CGroup                                │  │
│  │  │  ├─ 更新 CGroup(cpu.max、memory.high 等)    │  │
│  │  │  ├─ 删除 CGroup                                │  │
│  │  │  └─ 查询 CGroup                                │  │
│  │  ├─ 优化:                                          │  │
│  │  │  ├─ 批量提交(减少系统调用)                    │  │
│  │  │  ├─ 错误重试(自动恢复)                        │  │
│  │  │  └─ 幂等性(多次调用结果相同)                  │  │
│  │  └─ 支持 CGroup v1 和 v2                          │  │
│  │                                                      │  │
│  │  延迟:< 10 ms(通常)                              │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 6️⃣ RuntimeHooks (运行时钩子)                       │  │
│  │                                                      │  │
│  │  职责:在容器生命周期中注入资源策略                 │  │
│  │  ├─ 工作方式:                                      │  │
│  │  │  ├─ CRI Proxy 模式:代理 kubelet → 容器运行时  │  │
│  │  │  └─ NRI 模式:使用新的 Node Resource Interface │  │
│  │  ├─ 钩子点:                                        │  │
│  │  │  ├─ Pre-create:容器创建前,计算 CGroup 参数  │  │
│  │  │  ├─ Post-start:容器启动后,注入策略           │  │
│  │  │  └─ Pre-kill:容器销毁前,清理资源             │  │
│  │  └─ 优势:                                          │  │
│  │     └─ 容器启动时就已经应用正确的隔离策略          │  │
│  │        (相比 QOSManager 的延迟应用更早)            │  │
│  │                                                      │  │
│  │  成功率:> 99%(除非容器运行时不支持)              │  │
│  └──────────────────────────────────────────────────────┘  │
│                        ↓                                    │
│  ┌──────────────────────────────────────────────────────┐  │
│  │ 7️⃣ PredictServer (负载预测,可选)                 │  │
│  │                                                      │  │
│  │  职责:预测未来的资源需求                           │  │
│  │  ├─ 功能:                                          │  │
│  │  │  ├─ 历史数据分析                                │  │
│  │  │  ├─ 峰值预测                                    │  │
│  │  │  └─ 趋势分析                                    │  │
│  │  ├─ 应用:                                          │  │
│  │  │  ├─ 调度器提前感知可能的需求增长                │  │
│  │  │  ├─ 避免调度新 Pod 到即将高峰的节点              │  │
│  │  │  └─ 提前触发重调度避免冲突                      │  │
│  │  └─ 预测准确率:通常 70-80%(取决于工作负载)      │  │
│  │                                                      │  │
│  │  成熟度:Beta(功能完善但仍在优化)                  │  │
│  └──────────────────────────────────────────────────────┘  │
│                                                              │
└─────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────┐
│                    Linux Kernel & CGroup                    │
│  ├─ cpu.max / cpu.max_burst (CPU 配额)                 │
│  ├─ memory.min / memory.low / memory.high (内存保护)   │
│  ├─ resctrl (缓存隔离)                                  │
│  └─ blkio (磁盘限流)                                    │
└──────────────────────────────────────────────────────────────┘

Koordlet 的完整工作流程

3.4 秒级工作循环

Koordlet 每秒的工作流程(周期 = 1 秒):

T=0ms
│
├─→ StatesInformer.Sync()
│    └─ 同步 Pod/Node/NodeSLO 的最新状态
│       └─ 延迟:< 1 秒
│
├─→ MetricAdvisor.Collect()(周期 60s,但每秒检查一次)
│    ├─ 如果本周期需要采集,从 cgroup/procfs 收集指标
│    └─ 延迟:0-10 ms(取决于是否采集)
│
├─→ MetricCache.Update()
│    └─ 如果有新指标,写入缓存
│       └─ 延迟:< 1 ms
│
├─→ QOSManager.Run()
│    ├─ 分析 → 决策 → 执行(核心步骤)
│    │
│    ├─ Step 1:分析资源压力
│    │  ├─ cpuUsagePercent = 当前 CPU 使用率
│    │  ├─ memUsagePercent = 当前内存使用率
│    │  └─ cpuScheduleLatency = CPU 调度延迟
│    │
│    ├─ Step 2:判断压力等级
│    │  ├─ 如果 cpuUsagePercent < 65% → NONE(无压力)
│    │  ├─ 如果 65% ≤ cpuUsagePercent < 80% → MID(中等压力)
│    │  └─ 如果 cpuUsagePercent ≥ 80% → HIGH(高压力)
│    │
│    ├─ Step 3:执行各 Plugin(并行)
│    │  │
│    │  ├─ CPUSuppress Plugin
│    │  │  ├─ 根据压力等级计算 BE Pod 的新 CPU quota
│    │  │  │  ├─ NONE: quota = request(全速)
│    │  │  │  ├─ MID: quota = request × 0.770%)
│    │  │  │  └─ HIGH: quota = request × 0.330%)
│    │  │  └─ 输出:{pod_xxx: {cpu.max: 1200m}, ...}
│    │  │
│    │  ├─ MemoryEvict Plugin
│    │  │  ├─ 如果 memUsagePercent > 75%
│    │  │  │  ├─ 选择低优先级 Pod(按优先级和内存占用排序)
│    │  │  │  └─ 标记为驱逐候选
│    │  │  └─ 输出:{evict: [pod_aaa, pod_bbb], ...}
│    │  │
│    │  ├─ CPUBurst Plugin
│    │  │  ├─ 计算是否有空闲 CPU
│    │  │  │  └─ 如果 cpuUsagePercent < 60% → 允许 Burst
│    │  │  └─ 输出:{pod_yyy: {cpu.max_burst: 50000}, ...}
│    │  │
│    │  └─ ... 其他 Plugin ...
│    │
│    ├─ Step 4:合并冲突
│    │  ├─ 如果多个 Plugin 对同一个 Pod 有不同的决策
│    │  ├─ 合并策略:取最严格的值(保护 LS)
│    │  └─ 输出:最终的 CGroup 更新列表
│    │
│    └─ Step 5:提交给 ResourceExecutor
│       └─ 延迟:< 50 ms
│
├─→ ResourceExecutor.Update()
│    ├─ 批量更新 CGroup
│    │  └─ 写入 cpu.max、memory.high 等参数
│    └─ 延迟:< 20 ms(批量提交优化)
│
├─→ Metrics Report(可能,周期 60s)
│    ├─ 如果本周期需要上报
│    ├─ 将聚合的指标转换为 NodeMetric 格式
│    └─ 上报给 API Server
│       └─ 延迟:< 500 ms(网络)
│
T=1000ms(1 秒后,循环重复)

3.5 生产中的实际场景案例

案例:1024 核集群,突发流量导致的动态抑制

初始状态(T=0):
├─ LS Pod:400 个,每个 2 core request,平均使用 1.5 core
│  └─ 总使用:600 core
├─ BE Pod:200 个,每个 4 core request,平均使用 2 core
│  └─ 总使用:400 core
├─ 节点总 CPU:1024 core
└─ 集群利用率:(600+400)/1024 = 98%,但资源仍然有序

T=10s:突然大量用户请求涌入(突发流量)
├─ LS Pod 的实际 CPU 使用快速上升
└─ MetricAdvisor 采集到数据(每 10s 一次,这次碰上了)

T=20s(采集周期到):MetricAdvisor 发现异常
├─ CPU 使用率:(700+400)/1024 = 107% ❌ 溢出!
│  └─ 不对,应该是 <100%,表示 CPU 被过度使用
├─ 实际情况:用户请求导致 LS 需要 750 core,但只有 650 core 可用
│  └─ 原因:BE Pod 占用了 400 core
└─ 上报到 MetricCache

T=21s:QOSManager 分析并执行
├─ CPUSuppress Plugin 评估
│  ├─ CPU 使用率 > 80% → 高压力
│  └─ BE Pod CPU quota:4 core × 0.3 = 1.2 core
├─ 计算释放资源:200 BE Pod × (4-1.2) core = 560 core
│  └─ 释放后,BE 总占用:200 × 1.2 = 240 core
├─ 新的资源分配:
│  ├─ LS 需要 750 core → 现在有 1024-240 = 784 core ✅
│  ├─ LS 实际使用:750 core(充足)
│  └─ 延迟恢复正常
└─ ResourceExecutor 批量更新所有 BE Pod 的 cpu.max

T=22s:Effect 立刻生效
├─ LS Pod 获得充分 CPU,延迟从 50ms 降回 5ms
├─ BE Pod 被限制,但仍在正常运行(只是变慢)
└─ 系统恢复稳定

T=30s(采集周期再到):继续监控
├─ 流量是否继续上升 → 继续调整
├─ 流量是否下降 → 逐步放松抑制
└─ 保持动态平衡

预期指标:
✅ LS P99 延迟:5ms(保证 SLO)
✅ BE 任务运行时间:增加 30%(被抑制,但仍在进行)
✅ 集群利用率:仍达 90%+ (比不混部的 65% 高很多)

总 - 与其他组件的协作

3.6 Koordlet 与 Control Plane 的交互

koord-manager
    │ (1) 计算并创建/更新 NodeSLO
    │    └─ 根据 ConfigMap 和节点特性
    │       └─ 每当 ConfigMap 变化时更新
    │
    ↓
NodeSLO CRD(节点级 QoS 参数)
    │
    ├──────→ Koordlet (StatesInformer Watch)
    │         │
    │         ├─→ (2) 应用 NodeSLO 配置到本节点
    │         │   └─ 更新 QOSManager 的参数
    │         │
    │         └─→ (3) 上报 NodeMetric
    │             └─ 聚合采集的指标
    │
    ↓
NodeMetric CRD(节点实时指标)
    │
    ├──────→ koord-scheduler
    │         │ (4) 读取 NodeMetric
    │         │
    │         ├─→ LoadAware Plugin
    │         │    └─ 根据节点负载做出调度决策
    │         │       └─ 避免调度到高负载节点
    │         │
    │         └─→ 其他 Plugin
    │             └─ 检查资源充足性等
    │
    ├──────→ koord-manager (NodeResource Controller)
    │         │ (5) 读取 NodeMetric
    │         │
    │         └─→ 聚合计算可超卖资源量
    │             └─ 更新各节点的可分配资源
    │
    └──────→ koord-descheduler
             │ (6) 读取 NodeMetric
             │
             └─→ 发现过载节点
                 └─ 触发重调度

时间线:
T=0s    Koordlet 启动
        └─ 监听 NodeSLO、Pod、Node 变化

T=1-60s Koordlet 采集指标
        └─ 每 1s 执行 QOSManager,每 60s 采集新指标

T=60s   Koordlet 上报 NodeMetric
        └─ koord-scheduler 和其他组件获得最新信息

T=60-120s koord-scheduler 基于新的 NodeMetric 做出调度决策
          └─ 新 Pod 避免调度到高负载节点

持续的闭环和反馈

Koordlet 的性能与可靠性

3.7 生产环境的性能指标

指标期望值实际范围备注
内存占用500 MB200-1000 MB取决于 Pod 数量和缓存配置
CPU 使用< 5%2-8%单核(采集周期有关)
QOSManager 周期延迟< 100 ms50-150 ms通常 < 100 ms
CGroup 更新延迟< 20 ms10-50 ms批量提交优化
NodeMetric 上报延迟< 1 s500-1500 ms网络延迟
Watch 延迟< 1 s100-1000 ms取决于 API Server 负载
MetricCache 查询延迟< 10 ms1-5 ms内存操作
最大管理 Pod 数> 500500-2000取决于内存和 CPU

3.8 常见问题与故障排查

问题 1:Koordlet 内存占用过高

症状:Koordlet Pod 内存占用 > 2 GB

可能原因:
1. 节点 Pod 过多(> 1000 个)
2. MetricCache 保留数据过多
3. 内存泄漏(某个模块持续增长)

诊断方法:
$ kubectl logs -f pod/koordlet-xxx -n koordinator-system | grep memory

$ kubectl top pod koordlet-xxx -n koordinator-system

$ kubectl exec -it pod/koordlet-xxx -n koordinator-system -- \
    curl localhost:6060/debug/pprof/heap > heap.dump

解决方案:
1. 减少 MetricCache TTL:
   --metric-cache-ttl=1800s(从 3600s 改为 1800s)

2. 启用定期 GC:
   --gc-interval=30m

3. 查找内存泄漏:
   分析 heap.dump 找出持续增长的对象

问题 2:CPU 抑制不生效

症状:BE Pod 被设置了 cpu.max,但 CPU 使用仍然很高

可能原因:
1. CGroup v2 未启用(系统使用 CGroup v1)
2. Kernel 版本不支持 cpu.max
3. Pod 的 limit 已经比 cpu.max 更小
4. 更新失败(权限不足或路径不对)

诊断方法:
$ kubectl get cgroup version
v2  # 应该是 v2

$ cat /proc/cmdline | grep cgroup
cgroup_enable=memory  # 检查是否启用了 cgroup2

$ kubectl describe node <node> | grep -i cgroup

$ # 检查实际的 cgroup 限制
$ cat /sys/fs/cgroup/pods/<pod-id>/cpu.max
# 应该显示正确的值,如 "1200000 100000"

解决方案:
1. 升级系统和内核(需要 >= Linux 5.4)
2. 启用 CGroup v2
3. 确保 Koordlet 有权限写入 cgroup

$ # 查看 Koordlet 日志
$ kubectl logs pod/koordlet-xxx -n koordinator-system | \
    grep "cpu.max\|error\|fail" | tail -20

问题 3:NodeMetric 上报延迟大

症状:NodeMetric 的 updateTime 远后于当前时间

可能原因:
1. API Server 负载过高
2. 网络延迟
3. Koordlet 采集/上报的周期太长

诊断方法:
$ kubectl get nodemetric node-001 -o yaml | grep updateTime
updateTime: "2024-01-01T12:00:00Z"  # 应该是最近的时间

$ # 查看 Koordlet 采集是否正常
$ kubectl logs pod/koordlet-xxx -n koordinator-system | \
    grep "collect\|report" | tail -10

解决方案:
1. 减少上报周期:
   --nodemetric-report-interval=30s(从 60s 改为 30s)

2. 增加 API Server 的资源限制

3. 检查网络连接:
   $ kubectl exec pod/koordlet-xxx -n koordinator-system -- \
       curl -v https://kubernetes.default.svc/api/v1/

总结 - 章节关键要点

3.9 快速参考表

模块周期输入输出关键参数
StatesInformer实时Watch Pod/Node/NodeSLO状态缓存watch_timeout=30s
MetricAdvisor60scgroup/procfs/BPF指标数据collect_interval=60s
MetricCache实时指标数据可查询的缓存ttl=3600s
QOSManager1s指标+NodeSLOCGroup 更新计划qos_interval=1s
ResourceExecutor按需CGroup 更新计划修改后的参数batch_size=100
RuntimeHooks生命周期Pod 创建事件注入的参数hook_timeout=5s
PredictServer60s历史指标预测值predict_window=3600s

3.10 后续深入学习

下一章(第 4 章)将深入 StatesInformer 的实现细节

  • 如何高效地维护 Pod/Node 状态缓存
  • Watch 机制的优化
  • 状态一致性保证

本章核心收获: 理解 Koordlet 的 7 大核心模块及职责 掌握每秒工作循环的完整流程 学会从生产案例分析动态调控过程 了解性能指标和故障排查方法 理解 Koordlet 与 Control Plane 的协作机制