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 | kubelet 暴露根磁盘 IO 容量(按磁盘类型:SSD/HDD/NVME) |
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 | 本地 CSI 驱动初始化 |
2.3 节点容量暴露
本地 CSI 驱动将每个 VG 的容量和 IO 性能发布到节点 Annotation:
1 | # 容量 |
选择 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 | apiVersion: v1 |
对于不熟悉 IO 参数的用户,提供性能等级预设:
1 | # 性能等级:guarantee / high / medium / low / besteffort |
每个等级映射到一组预定义的 IOPS 和吞吐量模板。
2.5 调度器 Normalization(标准化)
将不同块大小的 IO 请求统一转换为 16K 基准:
1 | A = disk_blocksize / (16 * 1024) |
对于 HDD(读写性能不对称),引入读写比系数:
1 | Request_io_iops = request_io_read_iops + RW_iops × request_io_write_iops |
2.6 调度器 Priority(优先级评分)
满分 300 分,三个维度各 100 分:
1 | used_io_iops = Σ(pv_io_iops × backend_device_number) |
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 | type AllocationLayout struct { |
算法一:最小 IO 负担优先
- 扫描所有设备,计算可用资源(avail_iops / avail_tp / avail_size)
- 找唯一满足容量的设备 → 直接选用
- 找满足容量且 IO 资源充足的设备中 io.weight 最小者 → 选用
- 找 IO 资源充足的设备 → 按 io.weight 最小选用
- 综合评分选最优:
(avail_iops-req_iops)/total_iops + (avail_tp-req_tp)/total_tp + req_weight/(IO_weight+req_weight)
算法二:当前最高 IO 可用优先
- 过滤满足 IO 需求(avail_tp ≥ req_tp && avail_iops ≥ req_iops)的设备
- 在满足条件的设备中选容量足够且 io.weight 最小者
- 若无单设备满足,跨设备合并分配(按可用容量从小到大排序)
- 均不满足时选最大容量设备
2.9 根磁盘 IO 保护(与本地 PVC 共存)
部分节点存在根磁盘分区与 PVC VG 共享同一物理盘的情况。处理策略:
- CSI 驱动初始化时,从共享盘的 VG 总 IO 中预留一部分给根磁盘,不计入可分配 IO
- 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 | # 全局默认 + 设备覆盖 |
权重范围 [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-be和idle。
五、设计要点总结
| 维度 | 根磁盘 IO QoS | 本地 PVC IO QoS |
|---|---|---|
| 问题场景 | 容器层 + emptyDir 共享根盘 | 多 Pod 共享本地 PVC 磁盘 |
| 调度阶段 | Priority(允许超卖) | Predicate(容量)+ Priority(IO) |
| 限制粒度 | Pod 级 cgroup | Pod 级 cgroup + 原始设备 major:minor |
| 用户自定义 | 不支持(系统自动设置) | 支持声明 IOPS / 吞吐量 / 块大小 / 性能等级 |
| 跳过机制 | 注解跳过 | 不设 IO 请求 → io.weight=100 最低优先级 |
| 共存处理 | — | 共享根盘时预留 IO + 分配时降权 |