Cgroup V2 IO 控制器基准测试:io.max、io.weight 与 iocost 实测分析
本文记录在生产级 Kubernetes 节点上对 Cgroup V2 IO 控制器的系统性基准测试,覆盖 io.max(带宽/IOPS 硬限)、io.weight(权重调度)、io.cost.qos(延迟模型 QoS)三类控制机制,以及 Direct IO vs Buffer IO、raw disk vs LVM、ext4 vs xfs 等不同组合下的实测数据。
测试环境
- OS:Ubuntu 22.04 LTS(kernel 5.15)
- Kubernetes:v1.23
- 存储:3 块 1.7T SSD(sda/sdb/sdc),LVM VG 横跨三块盘
- 测试工具:fio + 自定义 IO 统计脚本
磁盘布局:
1 | sda 1.7T |
FIO 基础命令:
1 | fio -filename=/mnt/test/xfs/testfile \ |
裸盘基准性能
| 4K 顺序读写 | 读 BW | 读 IOPS | 写 BW | 写 IOPS |
|---|---|---|---|---|
| Ext4 Direct | 359 MB/s | 89,887 | 391 MB/s | 97,825 |
| Ext4 Buffer | 336 MB/s | 84,110 | 728 MB/s | 182,086 |
| XFS Direct | 360 MB/s | 89,997 | 400 MB/s | 100,005 |
| XFS Buffer | 300 MB/s | 74,910 | 780 MB/s | 194,982 |
Buffer IO 的写带宽因 page cache 聚合写入而显著高于 Direct IO。
io.max 限速测试(Case 1)
单 volume,单线程,对单设备设置 io.max。
| Case | IO 类型 | 文件系统 | 层级 | 配置 | 实测写 BW | 实测写 IOPS | 磁盘利用率 |
|---|---|---|---|---|---|---|---|
| 1-1 | Direct | ext4 | raw | wbps=100MB/s | 100 MB/s | 21,113 | 31% |
| 1-2 | Direct | xfs | raw | wbps=100MB/s | 100 MB/s | 13,382 | 28% |
| 1-3 | Buffer | ext4 | raw | wbps=100MB/s | 100 MB/s | 90 | 25% |
| 1-4 | Buffer | xfs | raw | wbps=100MB/s | 100 MB/s | 90 | 25% |
| 1-5 | Direct | xfs | raw | wiops=1000 | 4 MB/s | 1,000 | 100% |
| 1-6 | Buffer | xfs | raw | wiops=10 | 12.6 MB/s | 14.5 | 3% |
| 1-7 | Direct | xfs | LVM | wiops=1000 | 0.4 MB/s | 100 | 4% |
| 1-8 | Buffer | xfs | LVM | wiops=100 | 475 MB/s | 2 | 1.6% |
| 1-9 | Direct | xfs | LVM | wbps=100MB/s | 100 MB/s | 25,570 | 28% |
| 1-10 | Buffer | xfs | LVM | wbps=100MB/s | 100 MB/s | 2 | 20% |
关键发现:Buffer IO 的 IOPS 限速不准确
Case 1-8(Buffer IO,LVM,wiops=100):实测 IOPS 仅为 2,但 BPS 达到 475 MB/s,远超预期。Case 1-6 类似(wiops=10,实测 14.5 IOPS)。
原因:Buffer IO 的 dirty page writeback 使用的实际 block size 并非 fio 设置的 4K,而是由内核的 writeback 聚合策略决定的(通常更大)。内存越少,dirty page 刷盘频率越高,block size 越小;内存充足时,多个小写入被合并为大块写入,导致实际 IOPS 远低于配置值,BPS 却可能超出限制。
结论:io.max 的 BPS 限速(wbps)在 Buffer IO 下准确,IOPS 限速(wiops)在 Buffer IO 下不可靠。
跨设备与多 Volume 测试(Case 2)
| Case | 配置 | Device 1 实测 | Device 2 实测 | LVM 总计 |
|---|---|---|---|---|
| 2-1 | dev1=100MB/s, dev2=无限 | 104 MB/s, 83 IOPS | 489 MB/s, 446 IOPS | IOPS 在 [0,100] 波动 |
| 2-2 | 两设备各 100MB/s | 100 MB/s, 100 IOPS | 100 MB/s, 100 IOPS | 100 MB/s, [5,70] IOPS |
跨设备 LVM volume 的限速以底层设备为粒度,LVM 层级的 IOPS 由于条带化导致数值波动较大。
io.weight 权重调度测试(Case 3)
两个 LVM volume,两个 fio 线程,测试 io.weight 比例调度效果。
| Case | 场景 | Volume 1 | Volume 2 | 磁盘总计 |
|---|---|---|---|---|
| 3-1.1 | Buffer,无限制 | 250 MB/s | 250 MB/s | 500 MB/s, 100% util |
| 3-1.2 | Direct,无限制 | 220 MB/s | 220 MB/s | 440 MB/s, 100% util |
| 3-2 | Direct,weight 100:300,user qos | 206 MB/s | 209 MB/s | 420 MB/s |
| 3-4 | Buffer,weight 100:300,user qos | 129 MB/s | 368 MB/s | 500 MB/s |
| 3-6 | Direct,weight 100:900,user qos | 153 MB/s | 155 MB/s | 304 MB/s |
| 3-8 | Buffer,weight 100:900,user qos | 57 MB/s | 442 MB/s | 500 MB/s |
关键发现:Direct IO 的 io.weight 在默认 iocost.qos 下无效
Case 3-2 和 3-6:权重设置为 100:300 和 100:900,但两个 volume 的实际带宽几乎相等(206 vs ~209,153 vs ~155)。
原因:默认的 io.cost.qos 会根据延迟自动调整权重(ctrl=auto),当 Direct IO 让磁盘达到 100% 利用率时,内核为了满足延迟目标会忽略用户设置的权重比例,重新分配。
Buffer IO 下权重调度效果正常(3-4: 129 vs 368 ≈ 1:2.8;3-8: 57 vs 442 ≈ 1:7.8),接近预期的 1:3 和 1:9。
结论:io.weight 在 Buffer IO 下效果良好;在 Direct IO 且磁盘满负载时,需要禁用 iocost.qos 的自动调整(设置 min=max=100)才能生效。
iocost QoS 精确控制测试(Case 6)
禁用 iocost 自动调整(min=max=100),两个 volume 权重 100:900。
| Case | 附加配置 | Volume 1 | Volume 2 | 磁盘总计 |
|---|---|---|---|---|
| 6-1 | 无 io.max | 22 MB/s, 83% util | 309 MB/s, 85% util | 322 MB/s |
| 6-2 | 无 io.max,权重 100:300 | 127 MB/s | 291 MB/s | 405 MB/s |
| 6-3 | io.max 各 150MB/s | 18 MB/s | 145 MB/s | 168 MB/s |
| 6-4 | io.max 各 100MB/s | 67 MB/s | 102 MB/s | 164 MB/s |
| 6-5 | v2 限制 10MB/s | 48 MB/s | 10 MB/s | 58 MB/s |
Case 6-1 权重 100:900 的实际比例约为 1:14(22 vs 309),超过预期 1:9。这是因为磁盘利用率约 85%,iocost 轻微调整了分配比例。禁用自动调整后(min=max=100),权重比例更接近配置值。
三 Volume 权重测试(Case 7)
三个 LVM volume,权重配置不同,io.cost.qos 设置 min=max=100(禁用自动调整)。
| Case | 权重配置 | Vol 1 | Vol 2 | Vol 3 | 总计 |
|---|---|---|---|---|---|
| 7-1 | 100:900:900 | 38 MB/s | 6 MB/s | 191 MB/s | 235 MB/s |
| 7-3 | 100:900:900,v2 限 10MB/s | 10 MB/s | 9.6 MB/s | 153 MB/s | 172 MB/s |
| 7-4 | 100:900:900,ctrl=auto | 20 MB/s | 112 MB/s | 122 MB/s | 254 MB/s |
| 7-5 | 100:300:900 | 9.7 MB/s | 30 MB/s | 155 MB/s | 194 MB/s |
| 7-6 | 100:300:600 | 14 MB/s | 43 MB/s | 118 MB/s | 175 MB/s |
| 7-9 | 无权重配置(基准) | 121 MB/s | 119 MB/s | 119 MB/s | 359 MB/s |
发现:
- 7-4 使用
ctrl=auto时,权重 900 的 vol3 实测 122 MB/s,仅略高于 vol2(112 MB/s),比例远不是 1:9,自动调整干扰了权重效果 - 设置 io.weight 后总带宽下降(235 vs 359 MB/s),
io.cost.qos可以缓解但无法完全消除 - 7-2(
ctrl=auto,无min=max)导致节点出现 CPU 软锁,测试中断
大规模 Pod 场景测试(Case 5 & 9)
100 个 Pod,每 Pod 10 个 fio 线程,测试 IO 调度器和 iocost 对节点整体的影响。
Case 5:不同 IO 调度器对比
| 场景 | Load Avg | CPU | D-state 进程 | 节点 IO PSI | 总 IO 带宽 |
|---|---|---|---|---|---|
| 无限制(mq-deadline) | 900 | 100% | 30 | 910ms | 300 MB/s |
| io.max 限速(mq-deadline) | 548 | 68.4% | 0 | 450ms | 200 MB/s |
| io.max 限速(bfq) | 700 | 93.4% | 0 | 791ms | 150 MB/s |
Case 9:容器数量对 IO 吞吐的影响
不同 IO 调度器和控制策略下,随容器数量增加的写入吞吐量:
| 场景 | 1容器 | 2容器 | 6容器 | 18容器 | 50容器 |
|---|---|---|---|---|---|
| SSD xfs,bfq | 500 MB/s | 500 MB/s | 220 MB/s | 220 MB/s | 300 MB/s |
| SSD xfs,mq-deadline | 500 MB/s | 500 MB/s | 500 MB/s | 500 MB/s | 500 MB/s |
| SSD xfs,bfq+iocost | 250 MB/s | 300 MB/s | 340 MB/s | 16~490 MB/s | 35~491 MB/s |
| SSD xfs,bfq+ioweight | 40 MB/s | 42 MB/s | 65 MB/s | 500 MB/s | 500 MB/s |
| NVMe xfs,bfq | 830 MB/s | 900 MB/s | 810 MB/s | 850 MB/s | 750 MB/s |
关键发现:
- BFQ 调度器在多容器下吞吐量下降显著:SSD 环境下从 500 MB/s 降至 220 MB/s(6容器),而 mq-deadline 全程保持 500 MB/s 稳定
- iocost 导致 IO 不稳定:启用 iocost 后,IO 吞吐在 16~490 MB/s 之间剧烈波动,某些情况下长时间低 IO 才能恢复(见 iocost 测试图示)
- NVMe 上 BFQ 相对稳定:NVMe 的高 IOPS 特性让 BFQ 的调度开销相对较小,多容器下吞吐下降幅度可接受
总结与使用建议
| 功能 | 场景 | 建议 |
|---|---|---|
io.max BPS 限速 |
Direct IO / Buffer IO | 可靠,直接使用 |
io.max IOPS 限速 |
Direct IO | 可靠 |
io.max IOPS 限速 |
Buffer IO | 不可靠,慎用 |
io.weight |
Buffer IO | 效果良好 |
io.weight |
Direct IO,磁盘满载 | 需配合 min=max=100 关闭 iocost 自动调整 |
io.cost.qos ctrl=auto |
任何场景 | 高并发下可能导致 CPU 软锁,生产谨慎使用 |
| IO 调度器 | 多容器高并发 | mq-deadline 比 bfq 更稳定,bfq 适合需要权重调度的场景 |
核心结论:Cgroup V2 的 IO 控制器功能强大,但各机制之间存在相互影响(iocost 会覆盖 io.weight,BFQ 在高并发下有性能衰减)。生产环境建议从 io.max BPS 限速入手,待充分测试后再逐步引入权重和 iocost 功能。