Kubernetes 磁盘 IO QoS 设计:根磁盘与本地 PVC 双维度隔离

在多租户 Kubernetes 集群中,容器共享磁盘 IO 是典型的”噪邻”问题来源。本文介绍针对两类场景的 IO QoS 设计方案:根磁盘 IO QoS(保护容器根目录和 emptyDir 的 IO 公平性)与本地 PVC 磁盘 IO QoS(为本地动态卷提供带宽/IOPS 预留和调度保证)。


一、根磁盘 IO QoS

1.1 问题背景

多个容器共享同一节点根磁盘(容器层、emptyDir 均落在根磁盘分区),高 IO 容器会影响同节点其他容器。对于容器内部读写(非持久化卷),平台需要限制磁盘 IO 以避免资源争抢,同时防止根磁盘被写满。

推荐高性能或大 IO 场景使用网络卷或本地持久化卷,而非根磁盘。

1.2 整体流程

1
2
3
4
5
6
7
8
9
10
kubelet 暴露根磁盘 IO 容量(按磁盘类型:SSD/HDD/NVME)


kube-scheduler 为 Pod 设置默认读写 IOPS 和吞吐量


调度器计算每个节点的可用根磁盘 IO 并打分


kubelet 在 Pod 创建时设置 Pod 级别 cgroup IO 限制

1.3 调度评分算法

调度器以节点可用 IO 资源占比打分:

1
score_per_node = available_iops / total_iops + available_throughput / total_throughput

根磁盘 IO 评分作为调度优先级(Priority)而非过滤(Predicate)参与计算,允许 IO 资源超卖;仅磁盘空间不允许超卖。

1.4 emptyDir 特殊处理

部分 Pod 将 emptyDir 作为数据卷(而非临时存储空间)使用。处理规则:

  • 若 emptyDir 声明大小 超过默认值 5 倍(当前默认 2 GiB,阈值 10 GiB)→ 认为该 Pod 是重 IO 用户,为其分配 10 倍默认 IOPS/吞吐量
  • 否则 → 使用标准默认值

1.5 cgroup 设置策略

  • 仅在 Pod 级别 设置 cgroup IO 限制,不细分到容器级别
  • 原因:各团队可能通过 Webhook 注入 Sidecar 容器,这些容器通常不使用磁盘 IO;在 Pod 级别限制更简洁,避免为每个注入容器浪费配额
  • 若同一 Pod 内两个容器都有高 IO 需求,建议拆分为独立 Pod

1.6 跳过限制

通过特定注解可让 Pod 跳过 IOPS/BPS 限制,适用于确实需要大量容器层读写的场景。


二、本地 PVC 磁盘 IO QoS

2.1 问题背景

本地动态卷(local-dynamic)场景下,多个 Pod 共享同一块物理磁盘的 IO,高 IO Pod 会影响同盘其他 Pod 的基本 IO 保证。

2.2 整体架构流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
本地 CSI 驱动初始化
├─ 开启 cgroup io.cost.qos / io.cost.model
└─ 在节点 Annotation 上暴露磁盘 IO 容量

PVC 创建(用户通过 Annotation 声明 IO 需求)


kube-scheduler Normalization(标准化到 16K 块大小基准)


kube-scheduler Priority(三维评分:IOPS + 吞吐量 + 容量)


本地 CSI Provisioner 执行 Volume Allocation(选择最优设备)


kubelet 设置 Pod cgroup io.weight(按原始设备 major:minor)

2.3 节点容量暴露

本地 CSI 驱动将每个 VG 的容量和 IO 性能发布到节点 Annotation:

1
2
3
4
5
# 容量
csi.volume.kubernetes.io/kubernetes.io.csi.local: '{"vg10000":"5245425942528"}'

# IO 性能(16K 块大小随机读写各 50% 下的 IOPS 和吞吐量)
csi.volume.example.io/disk_io.kubernetes.io.csi.local: '{"io_throughput":"5000","iops":"100000"}'

选择 16K 块大小作为基准,是因为它是 IOPS 和吞吐量的共同瓶颈点:

块大小 (KB) IOPS 吞吐量 (Mbps)
4 1,000 4
8 1,000 8
16 1,000 16
32 500 16
64 250 16
128 128 16

2.4 用户 IO 需求声明

用户通过 PVC Annotation 声明 IO 需求:

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
# 吞吐量和 IOPS 请求(单位:MiB/s 和 IOPS)
csi.volume.example.io/disk_tp_write: "5"
csi.volume.example.io/disk_tp_read: "10"
csi.volume.example.io/disk_iops_read: "200"
csi.volume.example.io/disk_iops_write: "100"
# 应用实际使用的块大小
csi.volume.example.io/disk_blocksize: "4096"

对于不熟悉 IO 参数的用户,提供性能等级预设:

1
2
# 性能等级:guarantee / high / medium / low / besteffort
csi.volume.example.io/disk_performance_class: "high"

每个等级映射到一组预定义的 IOPS 和吞吐量模板。

2.5 调度器 Normalization(标准化)

将不同块大小的 IO 请求统一转换为 16K 基准:

1
2
3
4
5
A = disk_blocksize / (16 * 1024)

若 A = 1: real_io = request_io
若 A > 1: real_io_iops = request_io_iops * A;real_io_tp = request_io_tp
若 A < 1: real_io_tp = request_io_tp / A;real_io_iops = request_io_iops

对于 HDD(读写性能不对称),引入读写比系数:

1
2
3
4
5
Request_io_iops = request_io_read_iops + RW_iops × request_io_write_iops
Request_io_tp = request_io_read_tp + RW_tp × request_io_write_tp

# RW_iops = max_read_iops / max_write_iops
# SSD/NVME: RW_iops = RW_tp = 1

2.6 调度器 Priority(优先级评分)

满分 300 分,三个维度各 100 分:

1
2
3
4
5
6
7
8
9
10
11
12
13
used_io_iops = Σ(pv_io_iops × backend_device_number)
used_io_tp = Σ(pv_io_tp × backend_device_number)

available_io_iops = total_io_iops - used_io_iops - real_io_iops
iops_score = max(0, available_io_iops / total_io_iops × 100)

available_io_tp = total_io_tp - used_io_tp - real_io_tp
tp_score = max(0, available_io_tp / total_io_tp × 100)

capacity_score = (total_io_capacity - request_size - used_size) / total_io_capacity × 100

Total_io_score = iops_score + tp_score + capacity_score
调度器权重设为 10(默认为 1)

2.7 IO Weight 计算

cgroup v2 的 io.weight 用于在 IO 竞争时按比例分配带宽。从 PVC 的 IO 请求推导 io.weight:

以 16K 基准点(1000 IOPS / 16 Mbps)为参考:

1
io_weight = max(request_io_iops / A, request_io_tp / B) × 900 + 100

其中 A = 16K 基准 IOPS,B = 16K 基准吞吐量,结果范围 [100, 1000]。

  • 未声明 IO 需求的 PVC → io.weight = 100(尽力而为)
  • 声明最大 IO 需求的 PVC → io.weight = 1000

2.8 Volume Allocation(卷分配)

CSI 驱动维护每块磁盘的分配状态:

1
2
3
4
5
6
7
8
9
10
type AllocationLayout struct {
Total_iops int64
Total_tp int64
Total_size int64
Used_iops int64
Used_tp int64
Used_size int64
IO_weight int64
}
// allocLayout = map[string]AllocationLayout (key: 设备路径)

算法一:最小 IO 负担优先

  1. 扫描所有设备,计算可用资源(avail_iops / avail_tp / avail_size)
  2. 找唯一满足容量的设备 → 直接选用
  3. 找满足容量且 IO 资源充足的设备中 io.weight 最小者 → 选用
  4. 找 IO 资源充足的设备 → 按 io.weight 最小选用
  5. 综合评分选最优:(avail_iops-req_iops)/total_iops + (avail_tp-req_tp)/total_tp + req_weight/(IO_weight+req_weight)

算法二:当前最高 IO 可用优先

  1. 过滤满足 IO 需求(avail_tp ≥ req_tp && avail_iops ≥ req_iops)的设备
  2. 在满足条件的设备中选容量足够且 io.weight 最小者
  3. 若无单设备满足,跨设备合并分配(按可用容量从小到大排序)
  4. 均不满足时选最大容量设备

2.9 根磁盘 IO 保护(与本地 PVC 共存)

部分节点存在根磁盘分区与 PVC VG 共享同一物理盘的情况。处理策略:

  1. CSI 驱动初始化时,从共享盘的 VG 总 IO 中预留一部分给根磁盘,不计入可分配 IO
  2. Volume Allocation 时,对根磁盘的评分乘以惩罚系数(如 0.8),优先将 PVC 分配到非根盘

三、磁盘性能基准数据

3.1 各型号实测数据

SKU 类型 4K (IOPS/Mbps) 8K 16K 32K 64K 128K
p3g6 SSD 78.1k / 306 50.2k / 411 28k / 458 12.7k / 418 7k / 458 3.4k / 451
p3g7 SSD 68.1k / 279 50.8k / 416 25.2k / 412 13.4k / 438 7.5k / 489 4k / 531
p5g7 NVME 224.6k / 900 187.6k / 1500 125.8k / 1966 66.8k / 2086 33.9k / 2120 18.9k / 2358
p3fg7 NVME 201k / 823 184.6k / 1510 106.5k / 1745 55k / 1805 27.4k / 1794 15.2k / 1994

3.2 标准化基准值(取各型号平均)

类型 4K (IOPS/Mbps) 8K 16K 32K 64K 128K
SSD 70k / 300 50k / 410 26.5k / 430 13.6k / 430 7.2k / 470 3.7k / 500
NVME 210k / 860 185k / 1500 116k / 1855 60k / 2000 30k / 2000 17k / 2100

HDD 设备(主要用于 Cassini RAID 或大数据低性能场景)不纳入本地 PVC IO 分配体系;若检测到 HDD 磁盘,节点 Annotation 中跳过 IOPS 和 BPS 的记录。


四、cgroup v2 IO 控制接口参考

io.max(硬限速)

1
8:16 rbps=2097152 wbps=max riops=max wiops=max
字段 含义
rbps 最大读字节/秒
wbps 最大写字节/秒
riops 最大读 IOPS
wiops 最大写 IOPS

io.weight(权重调度)

1
2
3
4
# 全局默认 + 设备覆盖
100
8:16 200
8:0 50

权重范围 [1, 10000],只在 IO 竞争时生效(cfq/bfq 调度器或 io.cost 启用时)。

io.cost.qos(延迟感知 QoS)

1
8:32 enable=1 ctrl=auto rpct=95.00 rlat=75000 wpct=95.00 wlat=150000 min=80.00 max=120.0
  • rpct=95.00 rlat=75000:读请求 P95 延迟超过 75ms 则认为磁盘饱和
  • wpct=95.00 wlat=150000:写请求 P95 延迟超过 150ms 则认为磁盘饱和
  • min=80.00 max=120.0:IO 发放速率在 [80%, 120%] 之间动态调整
  • ctrl=auto:内核自动调控;设置任意延迟参数后切换为 user 模式

io.cost.model(设备性能模型,根 cgroup 专属)

1
8:32 ctrl=auto model=linear

配合 io.cost.qos 使用,描述设备的线性 IO 成本模型。

io.prio.class(优先级类别)

选项 行为
no-change 不修改请求优先级
promote-to-rt 非 RT 类请求提升为 RT,优先级 level=4
restrict-to-be RT 或无优先级请求降为 BE,level=0
idle 降为 IDLE 类

注意:promote-to-rt 需要特殊权限,实际可设置的模式通常为 restrict-to-beidle


五、设计要点总结

维度 根磁盘 IO QoS 本地 PVC IO QoS
问题场景 容器层 + emptyDir 共享根盘 多 Pod 共享本地 PVC 磁盘
调度阶段 Priority(允许超卖) Predicate(容量)+ Priority(IO)
限制粒度 Pod 级 cgroup Pod 级 cgroup + 原始设备 major:minor
用户自定义 不支持(系统自动设置) 支持声明 IOPS / 吞吐量 / 块大小 / 性能等级
跳过机制 注解跳过 不设 IO 请求 → io.weight=100 最低优先级
共存处理 共享根盘时预留 IO + 分配时降权