Podman --device-write-bps 详解:设备写入带宽限制的原理、用法与约束
【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman
--device-write-bps是 Podman 用于限制容器向指定块设备写入带宽(字节/秒)的核心资源控制选项,支持在podman run、create、container clone、update以及pod create、pod clone中直接使用。本指南将围绕该选项的语法、底层实现(cgroup 限速与块设备校验)、可验证的观测方法以及 rootless 与 cgroups V1 等约束条件展开,帮助你在多容器、多租户场景下精准控制磁盘写带宽。
选项总览:作用范围与格式
--device-write-bps定义于 docs/source/markdown/options/device-write-bps.md,这是一个被多个命令共用的“选项文件”。从该文件头部的注释可以看出,它会被以下命令的 man page 共同引用:
podman container clonepodman createpodman pod clonepodman pod createpodman runpodman update
即无论是创建单个容器、克隆容器,还是创建/克隆 Pod,甚至是给已运行的容器动态调整资源,都可以使用同一套语法。对应 man page 分别位于 podman-create.1.md.in、podman-run.1.md.in、podman-update.1.md.in、podman-container-clone.1.md.in、podman-pod-create.1.md.in 与 podman-pod-clone.1.md.in。
其基本语法为:
--device-write-bps=path:ratepath:目标块设备路径,例如/dev/sda;rate:每秒允许写入的字节数,例如1mb。
官方给出的最小示例为:
--device-write-bps=/dev/sda:1mb参数解析规则:从字符串到限速配置
在 CLI 层,该选项在 cmd/podman/common/create.go 中被定义为字符串数组(StringArrayVar),意味着可以多次指定,为多个不同设备分别设置不同的写入速率:
deviceWriteBpsFlagName := "device-write-bps" createFlags.StringArrayVar( &cf.DeviceWriteBPs, deviceWriteBpsFlagName, []string{}, "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", )随后,pkg/specgenutil/specgen.go中的parseThrottleBPSDevices(pkg/specgenutil/specgen.go)负责把用户输入的字符串解析为结构化的限速配置。从源码可以提炼出以下严格校验规则:
- 必须包含
:分隔符,否则报错bad format: <dev>; - 设备路径必须以
/dev/开头,否则报错bad format for device path: <dev>; - 速率值必须是正整数,单位可选,支持
kb、mb、gb(大小写不敏感,由units.RAMInBytes统一换算为字节数),解析失败或为负数都会报错:The correct format is ` : [ ]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb
因此以下写法均合法:
--device-write-bps=/dev/sda:1mb # 每秒 1 MiB --device-write-bps=/dev/sda:1048576 # 等价写法:直接写字节数 --device-write-bps=/dev/sda:512kb --device-write-bps=/dev/sda:2gb同时设置多个设备的写法:
--device-write-bps=/dev/sda:1mb --device-write-bps=/dev/sdb:500kb解析结果被存入 SpecGenerator 的ThrottleWriteBpsDevice字段(见 pkg/specgenutil/specgen.go)。
块设备校验:为什么非块设备会被拒绝
与 Docker 不同,Podman 在生成 OCI 运行时配置前会校验path是否真的是块设备(block device)。这一逻辑位于 pkg/specgen/utils_linux.go:
statBlkDev调用unix.Stat获取设备状态,并检查stat.Mode & S_IFMT == S_IFBLK;若不是块设备,返回%s: not a block device错误;- 校验通过后,进一步通过
unix.Major(rdev)与unix.Minor(rdev)解析出设备的主设备号(major)与次设备号(minor); fillThrottleDev将 major/minor 填入spec.LinuxThrottleDevice。
FinishThrottleDevices(pkg/specgen/utils_linux.go)会把解析后的设备最终写入ResourceLimits.BlockIO.ThrottleWriteBpsDevice。也就是说,cgroup 限速实际上是以major:minor 设备号而非路径字符串为键的。
这一点在系统级测试中有直接验证:test/system/280-update.bats中“podman update - non-block device rejected by --deviceoptions”用例(test/system/280-update.bats)对/dev/zero(字符设备,非块设备)依次传入--blkio-weight-device、--device-read-bps、--device-write-bps、--device-read-iops、--device-write-iops,断言全部以退出码 125 失败,且输出包含not a block device:
podman update "$cid" --device-write-bps=/dev/zero:10mb # 期望:Error ... /dev/zero: not a block device完整实战用法
1. 创建容器时限制写入带宽
podman run -d --name db \ --device-write-bps=/dev/sda:1mb \ --device-read-bps=/dev/sda:10mb \ postgres2. 在 Pod 中限制共享存储写入
podman pod create --name web \ --device-write-bps=/dev/sda:2mb \ -p 8080:80 podman run --pod web nginx3. 对已运行容器动态调整(update)
podman update支持在不重启容器的情况下调整设备写带宽。官方 man page(podman-update.1.md.in)给出的多选项综合示例:
podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \ --memory 1G --memory-swap 2G --memory-reservation 2G \ --blkio-weight-device /dev/sda:123 --blkio-weight 123 \ --device-read-bps /dev/sda:10mb --device-write-bps /dev/sda:10mb \ --device-read-iops /dev/sda:1000 --device-write-iops /dev/sda:1000 \ --pids-limit 123 ctrIDupdate 的实现路径同样复用 create 的解析逻辑:cmd/podman/containers/update.go的GetChangedDeviceLimits(cmd/podman/containers/update.go)把 SpecGenerator 中的ThrottleWriteBpsDevice取出,封装成UpdateContainerDevicesLimits,再经ContainerUpdate下发。
4. 克隆容器时继承或改写限速
podman container clone --device-write-bps=/dev/sda:500kb old-ctr new-ctr5. 结合 --latest 快捷操作
podman run -d --name app sleep infinity podman update --latest --device-write-bps=/dev/sda:3mb底层原理:cgroup 是如何“限速”的
设备读写限速最终由 Linux cgroup 的 block I/O 控制器实现,Podman 通过 OCI 运行时(crun/runc)将ThrottleWriteBpsDevice写入 cgroup 文件系统:
- cgroups v2:写入
io.max(格式为major:minor rbps=... wbps=...); - cgroups v1:写入
blkio.throttle.write_bps_device。
E2E 测试直接验证了这一事实。test/e2e/run_test.go中的 “podman run device-write-bps test” 用例(test/e2e/run_test.go)启动容器后,在容器内读取自身的 cgroupio.max文件来断言限速已生效:
podman run --rm --device-write-bps=/dev/nullb0:1mb alpine \ sh -c "cat /sys/fs/cgroup/\$(sed -e 's|0::||' < /proc/self/cgroup)/io.max"系统级测试test/system/280-update.bats的资源选项矩阵(test/system/280-update.bats)也把device-write-bps与io.max对应起来,并标注其在rootless 下不生效(表格中false列):
device-read-bps = $LOOPDEVICE:10mb | false | io.max = $devicemax device-read-iops = $LOOPDEVICE:2000 | false | io.max = $devicemax device-write-bps = $LOOPDEVICE:30mb | false | io.max = $devicemax device-write-iops = $LOOPDEVICE:4000 | false | io.max = $devicemax验证与观测:如何确认限速已生效
除了读取容器内 cgroup 文件,还可以通过podman inspect从“管理面”确认配置:
podman inspect <ctr> --format '{{.HostConfig.BlkioDeviceWriteBps}}'在 libpod/container_inspect_linux.go 中,Podman 会把 OCI 配置里的ThrottleWriteBpsDevice(major/minor + Rate)通过主机设备节点映射回设备路径,输出为HostConfig.BlkioDeviceWriteBps,与 Docker 的 inspect 输出结构保持一致,便于迁移与自动化脚本解析。
在 Docker 兼容 API 一侧,pkg/api/handlers/compat/containers_create.go(pkg/api/handlers/compat/containers_create.go)会把请求体HostConfig.BlkioDeviceWriteBps中的每个设备转换为<path>:<rate>字符串,再走与 CLI 相同的解析流程。因此通过curl调用/containers/create传入:
{ "HostConfig": { "BlkioDeviceWriteBps": [ {"Path": "/dev/sda", "Rate": 1048576} ] } }与podman run --device-write-bps=/dev/sda:1mb效果等价。
重要限制与约束
原文档明确了两条硬性限制,实际使用前务必确认运行环境:
- cgroups V1 rootless 系统不支持:
--device-write-bps在 cgroups V1 且以 rootless 模式运行时不可用。这源于 rootless 容器使用用户命名空间与 cgroup 委托机制,v1 控制器下无法完成设备级带宽委托。 - 非 root 用户可能无权修改资源限制:在部分基于 systemd 的系统上,非 root 用户没有资源限制委托(delegation)权限,设置资源限制会直接失败。
对于第二条,仓库根目录的 troubleshooting.md(第 26 节 “Running containers with resource limits fails with a permissions error”)给出了完整的排查与解决方案:
症状:运行带资源限制选项的容器时报错,例如:
Error: OCI runtime error: crun: the requested cgroup controller `cpu` is not available排查:检查当前用户的 cgroup 控制器委托情况:
cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers"如果输出中缺少io(或cpu、cpuset)控制器,说明委托未开启。
解决:为所有用户启用控制器委托,创建/etc/systemd/system/user@.service.d/delegate.conf:
[Service] Delegate=memory pids io cpu cpuset保存后重新登录,即可获得设置--device-write-bps等资源限制的权限。
与同族选项的关系
--device-write-bps属于 Podman 设备 I/O 限速家族中的一员,与其配套的还有:
| 选项 | 作用 | 底层控制器字段 |
|---|---|---|
--device-read-bps | 限制设备读带宽(字节/秒) | ThrottleReadBpsDevice |
--device-write-bps | 限制设备写带宽(字节/秒) | ThrottleWriteBpsDevice |
--device-read-iops | 限制设备读 IOPS | ThrottleReadIOPSDevice |
--device-write-iops | 限制设备写 IOPS | ThrottleWriteIOPSDevice |
--blkio-weight-device | 设置设备级相对权重(10–1000) | WeightDevice |
--blkio-weight | 设置全局相对权重(10–1000) | BlkIOWeight |
它们共用同一套path:value解析框架与块设备校验逻辑,其中 IOPS 族(parseThrottleIOPsDevices,见 pkg/specgenutil/specgen.go)要求速率必须是纯整数(无单位)。实际规划时可将BPS 限速与IOPS 限速组合使用:例如写入带宽限制1mb之外再加写 IOPS 上限100,从而同时约束吞吐与并发写请求数。
小结
--device-write-bps是 Podman 在多租户环境中隔离容器磁盘写带宽的关键开关,其价值在于:语法简洁(path:rate,支持 kb/mb/gb 单位与多设备组合)、创建/克隆/更新全生命周期可用、底层通过 cgroupio.max(v2)或blkio.throttle.write_bps_device(v1)强制执行,并有podman inspect与系统级 bats 测试双重可观测保障。部署前请重点核对两点:cgroups v1 rootless 环境不可用,以及非 root 用户是否具备 cgroup 控制器委托权限(参考 troubleshooting.md 第 26 节启用Delegate即可解决)。
【免费下载链接】podmanPodman: A tool for managing OCI containers and pods.项目地址: https://gitcode.com/gh_mirrors/po/podman
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考