揭秘云原生混布资源调度器Koordinator (八)资源隔离机制

0 阅读24分钟

核心使命与设计理念

8.1 What - 资源隔离是什么?

资源隔离是 Koordinator 通过 Linux CGroup 机制,实现不同 QoS 等级 Pod 之间的资源边界控制,防止相互干扰。

核心目标

  1. CPU 隔离:防止 BE Pod 占用 LS Pod 的 CPU 时间
  2. 内存隔离:防止 BE Pod 的内存使用影响 LS Pod
  3. I/O 隔离:防止 BE Pod 的磁盘/网络 I/O 抢占
  4. 优先级保障:确保高优先级 Pod 优先获得资源
  5. 动态调整:根据实际负载动态调整隔离策略

隔离的层次

┌─────────────────────────────────────────────┐
        Koordinator 资源隔离体系             
├─────────────────────────────────────────────┤
                                             
 L1: QoS 等级隔离                            
    ├─ LSE: 独占 CPU 核心                   
    ├─ LSR: 预留 CPU,可轻微共享            
    ├─ LS: 共享 CPU,有最低保障             
    └─ BE: 使用剩余 CPU,可被抑制           
                                             
 L2: CPU 子系统隔离                          
    ├─ cpu.shares: CPU 时间片比例          
    ├─ cpu.cfs_quota_us: CPU 硬限制        
    ├─ cpu.cfs_period_us: 调度周期         
    └─ cpuset.cpus: 绑定到特定核心         
                                             
 L3: 内存子系统隔离                          
    ├─ memory.limit_in_bytes: 硬限制       
    ├─ memory.soft_limit_in_bytes: 软限制  
    ├─ memory.oom_control: OOM 控制        
    └─ memory.swappiness: Swap 倾向        
                                             
 L4: I/O 子系统隔离                          
    ├─ blkio.weight: 磁盘 I/O 权重         
    ├─ blkio.throttle.read_bps_device      
    ├─ blkio.throttle.write_bps_device     
    └─ net_cls: 网络流量分类               
                                             
 L5: 特殊机制(CGroup v2)                  
    ├─ CPU Group Identity                   
    ├─ Memory QoS (min/low/high)           
    └─ PSI (Pressure Stall Information)   
                                             
└─────────────────────────────────────────────┘

8.2 Why - 为什么需要资源隔离?

问题 1:Kubernetes 默认隔离不够精细

Kubernetes 原生的资源隔离局限:

场景:节点上运行 LS Pod 和 BE Pod

Kubernetes 的 CGroup 设置:
┌─────────────────────────────────────┐
│ LS Pod (request=2, limit=4 CPU)    │
├─────────────────────────────────────┤
│ cpu.shares: 2048                    │
│ cpu.cfs_quota_us: 400000            │
│ cpu.cfs_period_us: 100000           │
│                                     │
│ BE Pod (request=0, limit=不设置)    │
├─────────────────────────────────────┤
│ cpu.shares: 2 (最小值)              │
│ cpu.cfs_quota_us: -1 (无限制)       │
│ cpu.cfs_period_us: 100000           │
└─────────────────────────────────────┘

问题分析:

1. shares 机制的局限性
   ├─ shares 只在 CPU 竞争时生效
   ├─ 如果节点 CPU 不满,BE 可以用满所有 CPU
   ├─ 当 LS 突然需要 CPU 时,BE 不会立即让出
   └─ 导致 LS 延迟增加(需要等 BE 的时间片用完)

2. quota 机制的僵化
   ├─ LS 的 quota=400000,即使节点空闲也不能超
   ├─ BE 的 quota=-1,即使节点满载也能抢占
   └─ 缺乏动态调整能力

3. 缺少优先级保障
   ├─ 内核调度器不知道 LS 和 BE 的业务优先级
   ├─ 在高负载下,LS 和 BE 平等竞争
   └─ 无法保障 LS 的 SLO

实际测试数据(无隔离优化):
├─ LS Pod P99 延迟: 150ms(SLO < 100ms)❌
├─ LS Pod 可用性: 98.5%(目标 99.9%)❌
└─ BE Pod 占用: 持续满载,不让步

问题 2:不同 QoS 之间的干扰严重

典型干扰场景:

场景 1: CPU 争抢
┌─────────────────────────────────────┐
│ 时刻 T=10:00                        │
├─────────────────────────────────────┤
│ LS Pod: 需要 3.5 CPU                │
│ BE Pod: 正在使用 6 CPU              │
│ 节点总: 8 CPU                       │
│                                     │
│ 问题:                                │
│ ├─ LS 只能拿到 2 CPU(不够)        │
│ ├─ BE 仍在使用 6 CPU(不让步)      │
│ └─ LS 延迟从 30ms 增加到 200ms     │
└─────────────────────────────────────┘

场景 2: 内存驱逐连锁反应
┌─────────────────────────────────────┐
│ BE Pod 内存泄漏                     │
├─────────────────────────────────────┤
│ T=10:00 BE 内存: 10 GB              │
│ T=10:05 BE 内存: 15 GB              │
│ T=10:10 节点内存压力触发            │
│                                     │
│ Kubernetes 默认行为:                │
│ ├─ kubelet 开始驱逐 Pod             │
│ ├─ 按照 QoS (BestEffort 先驱逐)    │
│ ├─ 但如果 BE 是 Guaranteed?        │
│ └─ 可能误杀 LS Pod!                │
└─────────────────────────────────────┘

场景 3: I/O 干扰
┌─────────────────────────────────────┐
│ BE Pod 执行大数据写入               │
├─────────────────────────────────────┤
│ 磁盘吞吐: 500 MB/s                  │
│ LS Pod 数据库查询:                  │
│ ├─ 需要读取索引文件                 │
│ ├─ 被 BE 的写入阻塞                 │
│ └─ 查询延迟从 5ms → 50ms           │
└─────────────────────────────────────┘

问题 3:需要支持高级隔离特性

Koordinator 的高级需求:

需求 1: LSE Pod 的完全隔离
├─ LSE Pod 需要独占 CPU 核心
├─ 不能与任何其他 Pod 共享
├─ 例:交易系统要求延迟 < 1ms
└─ 需要: cpuset 绑核 + 独占

需求 2: 内存 QoS 分级
├─ LS Pod: memory.min 保障最低内存
├─ BE Pod: memory.low 回收时优先保护
├─ 需要: CGroup v2  memory.min/low/high
└─ Kubernetes 原生不支持

需求 3: CPU 调度优先级
├─ LSE/LSR: 高优先级,优先调度
├─ BE: 低优先级,可以等待
├─ 需要: CGroup v2  cpu.weight.nice
└─ Kubernetes 原生不支持

需求 4: 压力感知
├─ 实时监测资源压力
├─ 动态调整隔离参数
├─ 需要: PSI (Pressure Stall Information)
└─ Kubernetes 不使用

8.3 How - 隔离机制的实现架构

┌──────────────────────────────────────────────┐
      Koordinator 资源隔离实现流程            
├──────────────────────────────────────────────┤
                                              
 1. RuntimeHooks(容器创建时)               
    ├─ 拦截容器创建请求                      
    ├─ 根据 Pod QoS 等级注入隔离参数         
    └─ 设置初始 CGroup 配置                  
                                              
 2. QOSManager(运行时调整)                 
    ├─ 监控资源压力                          
    ├─ 决策隔离参数调整                      
    └─ 通知 ResourceExecutor 执行            
                                              
 3. ResourceExecutor(执行层)               
    ├─ 接收隔离指令                          
    ├─ 更新 CGroup 配置文件                  
    ├─ 验证执行结果                          
    └─ 反馈执行状态                          
                                              
 4. 隔离参数库                                
    ├─ LSE: 独占核心 + 最高优先级            
    ├─ LSR: 预留资源 + 高优先级              
    ├─ LS: 共享资源 + 中等优先级             
    └─ BE: 剩余资源 + 低优先级               
                                              
└──────────────────────────────────────────────┘

CPU 隔离机制详解

8.4 CPU Shares - 时间片权重

工作原理

cpu.shares 的含义:
└─ 控制 CPU 时间片的相对权重
└─ 只在 CPU 竞争时生效
└─ 不限制绝对使用量

公式:

Pod A 获得的 CPU 时间比例 = 
    Pod A 的 shares / (所有 Pod shares 之和)

示例:

节点上运行 3 个 Pod:
├─ LS Pod 1: shares=2048
├─ LS Pod 2: shares=2048
└─ BE Pod:   shares=2shares = 2048 + 2048 + 2 = 4098

CPU 完全满载时的分配:
├─ LS Pod 1: (2048/4098) * 8 CPU = 4.00 CPU
├─ LS Pod 2: (2048/4098) * 8 CPU = 4.00 CPU
└─ BE Pod:   (2/4098) * 8 CPU = 0.004 CPU

CPU 不满载时(例如 LS 只用 2 CPU):
├─ LS Pod 1: 1 CPU(实际需求)
├─ LS Pod 2: 1 CPU(实际需求)
├─ BE Pod:   6 CPU(剩余全部可用)
└─ shares 不生效,BE 可以用满剩余 CPU

Koordinator 的 shares 策略

QoS 等级         cpu.shares 值        说明
────────────────────────────────────────────────
LSE              262144 (1024×256)   最高权重
LSR              65536 (1024×64)     高权重
LS               2048 (1024×2)       中等权重
BE               2                   最低权重
SYSTEM           32768 (1024×32)     系统保留

计算公式:

shares = request_cpu_millicores * shares_per_cpu / 1000

例:
├─ LS Pod (request=2 CPU):
│  shares = 2000 * 1024 / 1000 = 2048
│
└─ LSE Pod (request=2 CPU):
   shares = 2000 * 262144 / 1000 = 524288

高权重的优势:
├─ CPU 竞争时,LSE 优先获得 CPU
├─ 延迟降低,调度延迟 < 1ms
└─ 但不满载时,BE 仍可使用剩余 CPU

生产案例:shares 保护 LS 服务

场景:高并发 API 服务 + 数据分析任务

配置:
├─ 节点: 16 CPU
├─ LS Pod (API): 4 个 Pod, 每个 request 2 CPU, shares=2048
├─ BE Pod (分析): 2 个 Pod, shares=2

时间线:

T=08:00  低流量
         ├─ LS 实际使用: 4 CPU (4 Pod × 1 CPU)
         ├─ BE 实际使用: 11 CPU (剩余全部)
         ├─ shares 不起作用(CPU 不满)
         └─ 节点利用率: 15 / 16 = 94%

T=10:00  流量激增
         ├─ LS 需求: 14 CPU (4 Pod × 3.5 CPU)
         ├─ BE 仍在使用: 11 CPU
         ├─ 总需求: 25 CPU > 16 CPU(竞争)
         │
         ├─ shares 开始生效:
         │  ├─ LS 总 shares: 4 × 2048 = 8192
         │  ├─ BE 总 shares: 2 × 2 = 4
         │  ├─ 总 shares: 8196
         │  │
         │  ├─ LS 获得: (8192/8196) * 16 = 15.99 CPU
         │  └─ BE 获得: (4/8196) * 16 = 0.01 CPU
         │
         └─ 结果: LS 几乎独占 CPU,BE 几乎无法运行

T=10:05  LS 延迟验证
         ├─ LS P99 延迟: 45ms ✅ (SLO < 100ms)
         ├─ LS 可用性: 99.99% ✅
         └─ BE 吞吐量: 降低 99%(预期)

T=12:00  流量回落
         ├─ LS 需求降到 6 CPU
         ├─ BE 恢复到 9 CPU
         └─ 正常混部状态

效果评估:
├─ shares 在 CPU 竞争时有效保护了 LS
├─ CPU 不满时,BE 可以充分利用
└─ 动态适应负载变化,无需人工干预

8.5 CPU Quota - 硬限制与动态调整

工作原理

cpu.cfs_quota_us 和 cpu.cfs_period_us:

cfs_period_us: 调度周期(默认 100000 us = 100 ms)
cfs_quota_us: 在一个周期内可用的 CPU 时间(微秒)

公式:

CPU limit = cfs_quota_us / cfs_period_us

例:
├─ quota=200000, period=100000 → limit = 2 CPU
├─ quota=400000, period=100000 → limit = 4 CPU
└─ quota=-1 → 无限制

Kubernetes 的设置:
├─ 如果 Pod 设置了 limit: quota = limit * period
└─ 如果 Pod 没有 limit: quota = -1(无限制)

问题:
├─ limit 是静态的,不能动态调整
├─ 即使节点 CPU 空闲,也不能超过 limit
└─ 限制了突发性能

Koordinator 的动态 quota 调整

优化策略:

1. CPUBurst 功能(针对 LS)
   ├─ 允许 LS Pod 临时超过 limit
   ├─ 根据节点空闲 CPU 动态调整 quota
   └─ 突发后恢复原 quota

2. BE Pod 的动态限流(针对 BE)
   ├─ 根据 CPU 压力动态调整 BE 的 quota
   ├─ 压力高: quota 降低
   ├─ 压力低: quota 增加
   └─ 完全由 QOSManager 控制

BE Pod 的动态 quota 算法:

base_quota = (total_cpu - ls_request - system) * period
dynamic_quota = base_quota * suppress_ratio

suppress_ratio 根据 CPU 压力计算

示例:

节点: 16 CPU, period=100000
LS request: 8 CPU
系统预留: 1 CPU
可用于 BE: 16 - 8 - 1 = 7 CPU

正常情况(压力 40):
├─ suppress_ratio = 1.0
├─ BE quota = 7 * 100000 * 1.0 = 700000
└─ BE limit = 7 CPU

高压情况(压力 80):
├─ suppress_ratio = 0.3
├─ BE quota = 7 * 100000 * 0.3 = 210000
└─ BE limit = 2.1 CPU

紧急情况(压力 95):
├─ suppress_ratio = 0.1
├─ BE quota = 7 * 100000 * 0.1 = 70000
└─ BE limit = 0.7 CPU

生产案例:动态 quota 应对突发流量

场景:电商促销活动

配置:
├─ 节点: 32 CPU
├─ LS Pod: 10 个 Pod, limit=3 CPU/pod
├─ BE Pod: 5 个 Pod, 初始 quota=1000000 (10 CPU)

时间线:

T=20:00:00  促销前
            ├─ LS 使用: 15 CPU
            ├─ BE quota: 1000000 (10 CPU)
            ├─ BE 使用: 9 CPU
            └─ CPU 压力: 45

T=20:00:10  促销开始,流量 5 倍
            ├─ LS 需求: 15 → 60 CPU
            ├─ 但 LS limit 限制在 30 CPU
            ├─ LS 延迟增加: 50ms → 300ms

T=20:00:15  CPUBurst 触发(针对 LS)
            ├─ 临时提升 LS quota: 300000 → 450000
            ├─ LS 新 limit: 3 → 4.5 CPU/pod
            ├─ LS 可用: 45 CPU
            ├─ 但 LS 实际只需 30 CPU
            └─ CPU 压力: 85

T=20:00:16  CPUSuppress 执行(针对 BE)
            ├─ suppress_ratio = 0.3(压力 85)
            ├─ BE 新 quota: 1000000 * 0.3 = 300000
            ├─ BE 新 limit: 3 CPU
            └─ BE 被强制限流

T=20:00:20  LS 延迟恢复
            ├─ LS P99 延迟: 300ms → 80ms ✅
            ├─ LS 可用性: 恢复到 99.9%
            └─ BE 吞吐量: 降低 70%

T=20:30:00  促销结束,流量回落
            ├─ LS 需求: 60 → 20 CPU
            ├─ CPU 压力: 85 → 50
            ├─ suppress_ratio: 0.3 → 0.8
            ├─ BE quota 恢复: 300000 → 800000
            └─ BE limit 恢复: 3 → 8 CPU

效果评估:
├─ LS SLO 违反时间: 仅 5 秒
├─ 动态 quota 调整反应速度: < 1 秒
└─ BE 临时牺牲,但促销后恢复

8.6 CPUSet - 核心绑定与独占

工作原理

cpuset.cpus 的含义:
└─ 将 Pod 绑定到特定的 CPU 核心
└─ 限制 Pod 只能在指定核心上运行
└─ 用于 NUMA 感知和独占场景

示例:

cpuset.cpus = "0-3"  → Pod 只能在 CPU 0,1,2,3 上运行
cpuset.cpus = "0,2,4,6" → Pod 只能在偶数核心上运行

优势:
├─ 减少 CPU 缓存失效(cache miss)
├─ 减少跨 NUMA 节点的内存访问
├─ 独占核心,零干扰
└─ 提升性能和稳定性

劣势:
├─ 灵活性降低(无法使用其他核心)
├─ 利用率可能下降(绑定核心空闲时)
└─ 需要精细规划

Koordinator 的 CPUSet 策略

QoS 等级         CPUSet 策略                 示例
─────────────────────────────────────────────────────
LSE              独占核心,严格绑定          cpuset="0-1"
LSR              预留核心,可轻微共享        cpuset="2-5"
LS               共享池,不绑定              cpuset="0-15"
BE               共享池,不绑定              cpuset="0-15"

LSE Pod 的核心分配算法:

输入:
├─ LSE Pod request: 2 CPU
├─ 节点总 CPU: 16 核(0-15)
└─ 已分配给其他 LSE: [0, 1, 2, 3]

输出:
└─ 为新 LSE Pod 分配: [4, 5]

分配策略:
1. 优先选择空闲核心
2. 考虑 NUMA 亲和性(同 NUMA 节点)
3. 避免跨 NUMA 访问内存
4. 记录分配状态,防止冲突

节点拓扑示例(2 个 NUMA 节点):
NUMA Node 0: CPU 0-7
NUMA Node 1: CPU 8-15

LSE Pod 1 (request=4 CPU, NUMA 0):
└─ 分配: cpuset="0-3"

LSE Pod 2 (request=2 CPU, NUMA 1):
└─ 分配: cpuset="8-9"

LS Pod (request=4 CPU):
└─ 分配: cpuset="0-15"(共享全部)

BE Pod:
└─ 分配: cpuset="0-15"(共享全部)

生产案例:LSE 独占核心的性能提升

场景:金融交易系统,要求延迟 < 1ms

对比测试:

测试 1:不使用 CPUSet(共享核心)
┌─────────────────────────────────────┐
 LSE Pod (交易撮合引擎)              
├─────────────────────────────────────┤
 配置:                                
 ├─ request/limit: 4 CPU             
 ├─ cpuset: 未设置(共享 0-15      
 ├─ shares: 262144 (最高)            
                                     
 同节点其他 Pod:                      
 ├─ LS Pod: 5 个,总 10 CPU         
 └─ BE Pod: 2 个,总 2 CPU          
                                     
 测试结果:                            
 ├─ P50 延迟: 0.8ms                
 ├─ P99 延迟: 2.5ms  (SLO < 1ms)  
 ├─ P999 延迟: 8ms                
 └─ CPU 缓存命中率: 85%              
                                     
 问题分析:                            
 ├─ LSE 虽然有最高 shares            
 ├─ 但仍会被调度到不同核心           
 ├─ 上下文切换导致 cache miss        
 └─ 偶尔与其他 Pod 共享核心          
└─────────────────────────────────────┘

测试 2:使用 CPUSet(独占核心)
┌─────────────────────────────────────┐
 LSE Pod (交易撮合引擎)              
├─────────────────────────────────────┤
 配置:                                
 ├─ request/limit: 4 CPU             
 ├─ cpuset: "0-3" (独占)             
 ├─ shares: 262144                   
                                     
 同节点其他 Pod:                      
 ├─ LS Pod: cpuset="4-15"            
 └─ BE Pod: cpuset="4-15"            
                                     
 测试结果:                            
 ├─ P50 延迟: 0.5ms                
 ├─ P99 延迟: 0.8ms  (SLO < 1ms)  
 ├─ P999 延迟: 1.2ms               
 └─ CPU 缓存命中率: 98%              
                                     
 改进:                                
 ├─ P99 延迟降低: 2.5ms  0.8ms (68%)│
 ├─ 缓存命中率提升: 85%  98%        
 ├─ 无上下文切换开销                 
 └─ 完全隔离,零干扰                 
└─────────────────────────────────────┘

性能对比总结:

指标                不使用 CPUSet    使用 CPUSet    改进
─────────────────────────────────────────────────────
P50 延迟            0.8ms           0.5ms          37.5%
P99 延迟            2.5ms           0.8ms          68%
P999 延迟           8ms             1.2ms          85%
CPU 缓存命中率      85%             98%            13%
上下文切换/秒       5000            200            96%

结论:
└─ CPUSet 对超低延迟场景(< 1ms)至关重要
└─ 独占核心可以显著降低尾延迟
└─ 适合 LSE 等级的关键业务

分 - 内存隔离机制详解

8.7 Memory Limit - 硬限制与 OOM

工作原理

memory.limit_in_bytes 的含义:
└─ Pod 可使用的最大内存量(包括 RSS + Cache)
└─ 超过 limit 触发 OOM Killer

Kubernetes 的 memory limit 设置:
├─ Pod limit=4GB → memory.limit_in_bytes=4294967296
└─ 超过 limit → 内核 OOM Killer 杀死进程

OOM Killer 的选择逻辑:
1. 计算每个进程的 oom_score
   oom_score = (进程内存 / 总内存) * 1000 + oom_score_adj

2. 选择 oom_score 最高的进程杀死

3. oom_score_adj 的范围: -1000 到 1000
   ├─ -1000: 永不杀死
   ├─ 0: 默认
   └─ 1000: 优先杀死

Koordinator 的 OOM 控制策略

QoS 等级         oom_score_adj       OOM 优先级
─────────────────────────────────────────────────
LSE              -900                极低(最后)
LSR              -700                低
LS               -300                中等
BE               1000                高(优先)
SYSTEM           -1000               永不

设置目的:
├─ 内存压力时,优先杀死 BE Pod
├─ 保护 LS/LSR/LSE Pod 不被误杀
└─ 系统组件永不被杀

示例:

节点内存压力,OOM Killer 触发:
├─ 计算所有进程的 oom_score
│
├─ BE Pod 进程:
│  ├─ 内存占用: 8 GB / 128 GB = 6.25%
│  ├─ oom_score = 6.25% * 1000 + 1000 = 1062.5
│
├─ LS Pod 进程:
│  ├─ 内存占用: 4 GB / 128 GB = 3.125%
│  ├─ oom_score = 3.125% * 1000 + (-300) = -268.75
│
└─ OOM Killer 选择: BE Pod(oom_score 最高)

生产案例:OOM 保护 LS Pod

场景:节点内存压力,多个 Pod 竞争

初始状态:
├─ 节点: 128 GB 内存
├─ LS Pod: 10 个,每个 limit 4 GB,实际使用 3 GB
├─ BE Pod: 5 个,每个 limit 8 GB,实际使用 6 GB
└─ 总使用: 10 × 3 + 5 × 6 = 60 GB

时间线:

T=10:00  某 BE Pod 内存泄漏
         ├─ BE Pod 1 内存: 6 → 12 GB
         ├─ 总使用: 66 GB
         └─ 内存压力: 51%

T=10:05  内存泄漏继续
         ├─ BE Pod 1 内存: 12 → 20 GB
         ├─ 总使用: 74 GB
         └─ 内存压力: 58%

T=10:10  多个 BE Pod 同时增长
         ├─ BE Pod 1: 20 GB
         ├─ BE Pod 2: 6 → 10 GB
         ├─ BE Pod 3: 6 → 9 GB
         ├─ 总使用: 83 GB
         └─ 内存压力: 65%

T=10:15  接近节点 limit
         ├─ 总使用: 115 GB
         ├─ 可用内存: < 10 GB
         ├─ 内核开始内存回收(kswapd)
         └─ 性能下降

T=10:16  BE Pod 1 触发 OOM
         ├─ BE Pod 1 内存: 20 GB(超过 limit 8 GB)
         ├─ 内核触发 OOM Killer
         ├─ 计算 oom_score:
         │  └─ BE Pod 1: 1000 + 15.6% * 1000 = 1156
         ├─ 杀死 BE Pod 1 的主进程
         └─ 释放 20 GB 内存

T=10:17  内存压力缓解
         ├─ 总使用: 115 - 20 = 95 GB
         ├─ 可用内存: 33 GB
         └─ LS Pod 未受影响

对比:不使用 oom_score_adj 的情况
─────────────────────────────────
T=10:16  OOM Killer 触发
         ├─ 所有 Pod 的 oom_score_adj = 0
         ├─ OOM Killer 根据内存占用选择
         ├─ BE Pod 1: 20 GB → oom_score = 156
         ├─ LS Pod 某个: 3 GB → oom_score = 23
         ├─ 但如果 BE Pod 1 的进程较小?
         └─ 可能误杀 LS Pod!❌

使用 Koordinator oom_score_adj 的优势:
├─ BE Pod oom_score 至少 1000 起步
├─ LS Pod oom_score 最多几百
├─ 保证 BE 优先被杀,LS 受保护
└─ LS SLO 不受影响

8.8 Memory QoS - 分级保护(CGroup v2)

CGroup v2 的内存 QoS 机制

CGroup v2 新增的内存控制参数:

memory.min:  硬保证,即使全局内存压力也不回收
memory.low:  软保证,尽量不回收,压力大时可回收
memory.high: 软限制,超过时触发回收,但不 OOM
memory.max:  硬限制,超过时触发 OOM(等同于 v1 的 limit)

四级保护机制:

Level 1: memory.min(硬保证)
├─ 内核保证至少有 min 大小的内存可用
├─ 即使全局 OOM,也不回收 min 内的内存
└─ 用于 LSE/LSR 的关键内存

Level 2: memory.low(软保证)
├─ 尽量不回收,但压力极大时可以回收
└─ 用于 LS 的工作内存

Level 3: memory.high(软限制)
├─ 超过 high 时,触发主动回收
├─ 但不会 OOM,只是变慢
└─ 用于控制 BE 的内存增长

Level 4: memory.max(硬限制)
├─ 超过 max 时,触发 OOM
└─ 所有 QoS 都有 max

Koordinator 的 Memory QoS 配置

QoS 等级     memory.min    memory.low    memory.high    memory.max
────────────────────────────────────────────────────────────────────
LSE          request       request×1.2   limit×0.9      limit
LSR          request×0.8   request       limit×0.9      limit
LS           request×0.5   request×0.8   limit×0.9      limit
BE           0             0             limit×0.7      limit

配置示例:

LS Pod (request=4GB, limit=8GB):
├─ memory.min = 4 × 0.5 = 2 GB(硬保证)
├─ memory.low = 4 × 0.8 = 3.2 GB(软保证)
├─ memory.high = 8 × 0.9 = 7.2 GB(软限制)
└─ memory.max = 8 GB(硬限制)

工作流程:

内存使用 0-2 GB:
└─ 完全安全,绝不回收

内存使用 2-3.2 GB:
└─ 优先保护,极少回收

内存使用 3.2-7.2 GB:
└─ 正常使用,按需回收

内存使用 7.2-8 GB:
└─ 触发主动回收,性能下降

内存使用 > 8 GB:
└─ 触发 OOM Killer

生产案例:Memory QoS 防止误回收

场景:节点内存压力,多层级保护

配置:
├─ 节点: 128 GB 内存
├─ LSR Pod: 5 个,每个 request 8 GB, limit 16 GB
├─ LS Pod: 10 个,每个 request 4 GB, limit 8 GB
├─ BE Pod: 5 个,每个 limit 8 GB

Memory QoS 设置:

LSR Pod:
├─ min = 8 × 0.8 = 6.4 GB
├─ low = 8 GB
├─ high = 16 × 0.9 = 14.4 GB
└─ max = 16 GB

LS Pod:
├─ min = 4 × 0.5 = 2 GB
├─ low = 4 × 0.8 = 3.2 GB
├─ high = 8 × 0.9 = 7.2 GB
└─ max = 8 GB

BE Pod:
├─ min = 0
├─ low = 0
├─ high = 8 × 0.7 = 5.6 GB
└─ max = 8 GB

时间线:

T=10:00  正常运行
         ├─ LSR 使用: 5 × 7 = 35 GB
         ├─ LS 使用: 10 × 3.5 = 35 GB
         ├─ BE 使用: 5 × 5 = 25 GB
         ├─ 总使用: 95 GB
         └─ 可用: 33 GB

T=10:05  BE Pod 开始消耗内存
         ├─ BE 使用: 25 → 45 GB
         ├─ 总使用: 115 GB
         ├─ 可用: 13 GB
         └─ 内存压力开始

T=10:06  内核内存回收触发
         ├─ 检查各 Pod 的 memory.high
         │
         ├─ BE Pod: 使用 9 GB > high 5.6 GB
         │  └─ 主动回收 BE 的 page cache
         │  └─ BE 性能下降,但不 OOM
         │
         ├─ LS Pod: 使用 3.5 GB < high 7.2 GB
         │  └─ 不触发回收
         │
         └─ LSR Pod: 使用 7 GB < high 14.4 GB
            └─ 不触发回收

T=10:07  BE 内存被回收
         ├─ BE page cache 被回收: 45 → 35 GB
         ├─ 总使用: 105 GB
         ├─ 可用: 23 GB
         └─ 压力缓解

T=10:10  极端情况:全局 OOM
         ├─ 假设内存压力继续增加
         ├─ 可用内存 < 5 GB
         │
         ├─ 内核回收顺序:
         │  1. BE Pod(min=0,无保护)
         │  2. LS Pod 超过 low 的部分(3.2-3.5 GB)
         │  3. LSR Pod 超过 low 的部分(很少)
         │  4. LS Pod 的 low-min 部分(2-3.2 GB)
         │  5. LSR Pod 的 low-min 部分
         │  6. LS Pod 的 min 部分(2 GB)← 硬保护
         │  7. LSR Pod 的 min 部分(6.4 GB)← 硬保护
         │
         └─ LSR 和 LS 的关键内存受到多层保护

效果对比:

不使用 Memory QoS(仅 limit):
├─ 内存压力时,LS 和 BE 平等回收
├─ LS 的 page cache 被回收
├─ LS 性能下降,延迟增加
└─ 无法区分关键内存和缓存

使用 Memory QoS:
├─ BE 优先被回收(min=0)
├─ LS 的关键内存(min 部分)受硬保护
├─ 多层级保护,逐级降级
└─ LS 性能受影响最小

I/O 隔离机制详解

8.9 Block I/O 隔离

工作原理

blkio CGroup 子系统的控制参数:

blkio.weight: I/O 权重(100-1000)
├─ 类似 CPU shares
├─ 只在 I/O 竞争时生效
└─ 权重越高,获得的 I/O 时间越多

blkio.throttle.read_bps_device: 读取字节/秒限制
blkio.throttle.write_bps_device: 写入字节/秒限制
├─ 硬限制,无论是否竞争
└─ 格式: "<major>:<minor> <bytes_per_sec>"

blkio.throttle.read_iops_device: 读取 IOPS 限制
blkio.throttle.write_iops_device: 写入 IOPS 限制
├─ 限制每秒的 I/O 操作次数
└─ 适用于小文件场景

Koordinator 的 I/O 隔离策略

QoS 等级     blkio.weight    读 BPS 限制    写 BPS 限制
──────────────────────────────────────────────────────
LSE          1000            无限制         无限制
LSR          800             无限制         无限制
LS           500             无限制         无限制
BE           100             500 MB/s       200 MB/s

设置示例:

BE Pod 的 I/O 限制:
├─ blkio.weight = 100
├─ blkio.throttle.read_bps_device = "8:0 524288000"
│  └─ 设备 8:0 (sda),读取限制 500 MB/s
└─ blkio.throttle.write_bps_device = "8:0 209715200"
   └─ 写入限制 200 MB/s

目的:
├─ 防止 BE 的大数据写入影响 LS 的数据库查询
├─ LS 的 I/O 不受限制,可以充分利用磁盘
└─ BE 被限流,但仍能处理任务

生产案例:I/O 隔离保护数据库服务

场景:在线数据库 + 离线数据分析

配置:
├─ 节点: SSD 磁盘,顺序读写 1 GB/s
├─ LS Pod (数据库): 需要低延迟随机 I/O
├─ BE Pod (数据分析): 大量顺序读写

不使用 I/O 隔离的问题:
┌─────────────────────────────────────┐
│ T=10:00  BE Pod 启动数据导出        │
├─────────────────────────────────────┤
│ BE 写入: 900 MB/s(几乎占满)       │
│ 磁盘队列: 饱和                      │
│                                     │
│ LS 数据库查询:                      │
│ ├─ 需要读取索引(随机 I/O)         │
│ ├─ 被 BE 的顺序写入阻塞             │
│ ├─ I/O 延迟: 5ms150ms           │
│ └─ 查询延迟: 10ms200ms ❌       │
└─────────────────────────────────────┘

使用 I/O 隔离的效果:
┌─────────────────────────────────────┐
│ T=10:00  BE Pod 启动数据导出        │
├─────────────────────────────────────┤
│ BE 写入限制: 200 MB/s               │
│ BE I/O weight: 100                  │
│                                     │
│ LS 数据库查询:                      │
│ ├─ LS I/O weight: 500               │
│ ├─ I/O 竞争时,LS 优先获得磁盘     │
│ ├─ I/O 延迟: 5ms8ms ✅          │
│ └─ 查询延迟: 10ms15ms ✅        │
│                                     │
│ 磁盘利用率:                          │
│ ├─ LS: 150 MB/s(随机 I/O)        │
│ ├─ BE: 200 MB/s(被限流)           │
│ └─ 总: 350 MB/s(未饱和)           │
└─────────────────────────────────────┘

效果评估:
├─ LS 查询延迟增加: 仅 50%(可接受)
├─ BE 吞吐量: 降低到 22%200/900)
└─ LS SLO 保持在 99.9%

8.10 网络 I/O 隔离

工作原理

网络隔离的机制:

1. net_cls CGroup(流量分类)
   ├─ 为不同 QoS 的 Pod 打上 classid
   ├─ 配合 tc (Traffic Control) 进行流量整形
   └─ 限制带宽、优先级

2. tc qdisc(队列规则)
   ├─ HTB (Hierarchical Token Bucket): 分层限速
   ├─ PRIO: 优先级队列
   └─ 按 classid 分配带宽和优先级

配置示例:

为 BE Pod 设置网络限速:
# 设置 classid
echo "0x00010002" > /sys/fs/cgroup/net_cls/kubepods/besteffort/pod-uid/net_cls.classid

# 配置 tc 规则
tc qdisc add dev eth0 root handle 1: htb default 12
tc class add dev eth0 parent 1: classid 1:1 htb rate 10gbit
tc class add dev eth0 parent 1:1 classid 1:2 htb rate 500mbit ceil 1gbit
  # classid 1:2 对应 BE Pod,限速 500 Mbit

tc filter add dev eth0 protocol ip parent 1:0 prio 1 handle 2: cgroup
  # 根据 cgroup classid 分类流量

Koordinator 的网络隔离策略

QoS 等级     带宽限制        优先级      classid
────────────────────────────────────────────────
LSE          无限制          最高 (0)    0x00010001
LSR          无限制          高 (1)      0x00010002
LS           无限制          中 (2)      0x00010003
BE           500 Mbit        低 (3)      0x00010004

网络优先级的作用:
├─ 网络拥塞时,优先发送高优先级的包
├─ LSE 的网络包优先于 BE
└─ 保障低延迟服务的网络性能

生产案例:网络隔离保护 API 服务

场景:API 服务 + 数据同步任务

配置:
├─ 节点网卡: 10 Gbit/s
├─ LS Pod (API 服务): 响应用户请求
├─ BE Pod (数据同步): 同步到远程数据中心

时间线:

T=10:00  正常运行
         ├─ LS 网络: 500 Mbit(API 流量)
         ├─ BE 网络: 200 Mbit(增量同步)
         └─ 网络利用率: 7%

T=10:05  BE 启动全量数据同步
         ├─ BE 网络: 200 → 8000 Mbit
         ├─ 几乎占满 10G 带宽

不使用网络隔离的问题:
         ├─ LS API 请求的网络包被 BE 的大包阻塞
         ├─ LS 网络延迟: 5ms → 100ms
         ├─ API 响应延迟: 20ms → 150ms ❌
         └─ 用户投诉增加

使用网络隔离的效果:
         ├─ BE 网络被限制: 8000 → 500 Mbit
         ├─ LS 网络优先级更高
         ├─ LS 网络延迟: 5ms → 8ms ✅
         ├─ API 响应延迟: 20ms → 25ms ✅
         └─ 用户体验良好

T=10:30  LS 流量高峰
         ├─ LS 网络: 500 → 2000 Mbit
         ├─ BE 网络: 500 Mbit(被限制)
         ├─ 总: 2500 Mbit < 10 Gbit
         └─ 充足的带宽余量

效果评估:
├─ LS 延迟增加: 仅 25%(可接受)
├─ BE 同步时间: 从 10 分钟 → 160 分钟
└─ 但 LS SLO 保持 99.95%

生产调优指南

8.11 隔离参数调优表

# 推荐的隔离参数配置
apiVersion: v1
kind: ConfigMap
metadata:
  name: koordlet-config
  namespace: koordinator-system
data:
  resource-qos-config.yaml: |
    # LSE 配置
    lseClass:
      cpu:
        shares: 262144       # 最高权重
        cpuset: "exclusive"  # 独占核心
      memory:
        min_ratio: 0.8       # request × 0.8
        low_ratio: 1.0       # request × 1.0
        oom_score_adj: -900
      io:
        blkio_weight: 1000
        network_priority: 0  # 最高
    
    # LSR 配置
    lsrClass:
      cpu:
        shares: 65536
        cpuset: "shared"     # 共享核心
      memory:
        min_ratio: 0.6
        low_ratio: 0.8
        oom_score_adj: -700
      io:
        blkio_weight: 800
        network_priority: 1
    
    # LS 配置
    lsClass:
      cpu:
        shares: 2048
        cpuset: "shared"
      memory:
        min_ratio: 0.5
        low_ratio: 0.8
        oom_score_adj: -300
      io:
        blkio_weight: 500
        network_priority: 2
    
    # BE 配置
    beClass:
      cpu:
        shares: 2
        cpuset: "shared"
        quota_burst_ratio: 0  # 不允许 burst
      memory:
        min_ratio: 0
        low_ratio: 0
        oom_score_adj: 1000
      io:
        blkio_weight: 100
        blkio_read_bps: 524288000   # 500 MB/s
        blkio_write_bps: 209715200  # 200 MB/s
        network_bandwidth: 524288000 # 500 Mbit/s
        network_priority: 3

8.12 常见问题排查

问题 1:LS Pod 延迟仍然过高

诊断步骤:

1. 检查 CPU shares 是否生效
   $ cat /sys/fs/cgroup/cpu/kubepods.slice/.../cpu.shares
   
   如果 shares 不是预期值(如 2048):
   └─ RuntimeHooks 可能未正确注入

2. 检查 cpuset 绑定
   $ cat /sys/fs/cgroup/cpuset/kubepods/.../cpuset.cpus
   
   如果 LSE Pod 未绑定独占核心:
   └─ CPUSet 分配可能失败

3. 检查内存是否被回收
   $ cat /sys/fs/cgroup/memory/kubepods/.../memory.stat
   
   如果 pgpgin/pgpgout 频繁增加:
   └─ 内存被频繁回收,影响性能

4. 检查 I/O 限流
   $ cat /sys/fs/cgroup/blkio/kubepods/.../blkio.throttle.io_service_bytes
   
   如果 I/O 被限流:
   └─ LS 不应该被限流,检查配置

解决方案:
├─ 调低 CPU 压力阈值,更早触发抑制
├─ 为 LSE 启用 cpuset 独占
├─ 调高 memory.min,防止关键内存被回收
└─ 移除 LS 的 I/O 限流配置

问题 2:BE Pod 被过度限制

诊断:

1. 检查 BE 的 CPU quota
   $ cat /sys/fs/cgroup/cpu/kubepods.slice/.../cpu.cfs_quota_us
   
   如果 quota 过小(如 50000 = 0.5 CPU):
   └─ suppress_ratio 可能过于激进

2. 检查节点 CPU 利用率
   $ top
   
   如果节点 CPU < 50%,但 BE 仍被限制:
   └─ 压力阈值设置过低

解决方案:
├─ 提高 cpuPressure.medium 阈值(40 → 60)
├─ 调整 suppressRatio(0.8 → 0.9)
└─ 延长 suppress 冷却时间

问题 3:OOM Killer 误杀 LS Pod

诊断:

1. 检查 oom_score_adj
   $ cat /proc/<pid>/oom_score_adj
   
   LS Pod 的值应该是 -300
   BE Pod 的值应该是 1000

2. 检查 OOM 日志
   $ dmesg | grep -i oom
   
   查看被杀进程的 oom_score

解决方案:
├─ 确认 RuntimeHooks 正确设置 oom_score_adj
├─ 调高 LS Pod 的 memory.min
└─ 启用 MemoryEvict,提前驱逐 BE

8.13 监控指标

# 隔离相关监控指标

# CPU shares
koordlet_pod_cpu_shares{qos="LS"}
koordlet_pod_cpu_shares{qos="BE"}

# CPU quota(动态调整)
koordlet_pod_cpu_quota{qos="BE"}

# CPUSet 绑定状态
koordlet_pod_cpuset_cores{qos="LSE"}

# 内存 QoS 参数
koordlet_pod_memory_min_bytes{qos="LS"}
koordlet_pod_memory_low_bytes{qos="LS"}

# OOM 分数
koordlet_pod_oom_score_adj{qos="LS"}
koordlet_pod_oom_score_adj{qos="BE"}

# I/O 权重和限流
koordlet_pod_blkio_weight{qos="LS"}
koordlet_pod_blkio_throttle_bps{qos="BE", direction="write"}

# 网络优先级
koordlet_pod_network_priority{qos="LS"}

总结 - 章节要点汇总

8.14 关键概念速查

隔离机制控制参数LSELSRLSBE
CPU 权重cpu.shares2621446553620482
CPU 限制cpu.cfs_quota_us动态动态动态动态
CPU 绑核cpuset.cpus独占共享共享共享
内存保护memory.min80%60%50%0%
OOM 分数oom_score_adj-900-700-3001000
I/O 权重blkio.weight1000800500100
网络优先级tc priority0123

8.15 最佳实践

  • 为超低延迟场景(< 1ms)启用 LSE + CPUSet 独占核心
  • 使用 Memory QoS (CGroup v2) 保护 LS 的关键内存
  • 设置合理的 oom_score_adj,防止误杀 LS Pod
  • 对 BE 进行 I/O 限流,避免影响 LS 的数据库查询
  • 监控隔离参数的实际生效情况,及时调整

本章核心收获

  • 理解 CPU/内存/I/O 的多层次隔离机制
  • 掌握 CGroup v1 和 v2 的核心参数配置
  • 学会为不同 QoS 等级配置差异化隔离策略
  • 理解 CPUSet、Memory QoS、OOM 控制的生产实践
  • 掌握隔离机制的监控和故障排查方法