核心使命与设计理念
8.1 What - 资源隔离是什么?
资源隔离是 Koordinator 通过 Linux CGroup 机制,实现不同 QoS 等级 Pod 之间的资源边界控制,防止相互干扰。
核心目标:
- CPU 隔离:防止 BE Pod 占用 LS Pod 的 CPU 时间
- 内存隔离:防止 BE Pod 的内存使用影响 LS Pod
- I/O 隔离:防止 BE Pod 的磁盘/网络 I/O 抢占
- 优先级保障:确保高优先级 Pod 优先获得资源
- 动态调整:根据实际负载动态调整隔离策略
隔离的层次:
┌─────────────────────────────────────────────┐
│ 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=2
总 shares = 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 延迟: 5ms → 150ms │
│ └─ 查询延迟: 10ms → 200ms ❌ │
└─────────────────────────────────────┘
使用 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 延迟: 5ms → 8ms ✅ │
│ └─ 查询延迟: 10ms → 15ms ✅ │
│ │
│ 磁盘利用率: │
│ ├─ 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 关键概念速查
| 隔离机制 | 控制参数 | LSE | LSR | LS | BE |
|---|---|---|---|---|---|
| CPU 权重 | cpu.shares | 262144 | 65536 | 2048 | 2 |
| CPU 限制 | cpu.cfs_quota_us | 动态 | 动态 | 动态 | 动态 |
| CPU 绑核 | cpuset.cpus | 独占 | 共享 | 共享 | 共享 |
| 内存保护 | memory.min | 80% | 60% | 50% | 0% |
| OOM 分数 | oom_score_adj | -900 | -700 | -300 | 1000 |
| I/O 权重 | blkio.weight | 1000 | 800 | 500 | 100 |
| 网络优先级 | tc priority | 0 | 1 | 2 | 3 |
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 控制的生产实践
- 掌握隔离机制的监控和故障排查方法