1.需求背景
线上出现一种情况,当批量创建本地盘虚拟机的时候,大量的本地盘导入操作系统镜像会影响已有虚机的磁盘读写。
2. cgroup blkio介绍
blkio 是 cgroup v1 中的一个子系统,使用 cgroup v1 blkio 子系统主要是为了减少进程之间共同读写同一块磁盘时相互干扰的问题。
cgroup v1 blkio 控制子系统可以限制进程读写的 IOPS 和吞吐量,但它只能对 Direct I/O 的文件读写进行限速,对 Buffered I/O 的文件读写无法限制。
Buffered I/O 指会经过 PageCache 然后再写入到存储设备中。这里面的 Buffered 的含义跟内存中 buffer cache 不同,这里的 Buffered 含义相当于内存中的buffer cache+page cache。
在 blkio cgroup 中,主要有以下四个参数来限制磁盘 I/O:
1 |
blkio.throttle.read_bps_device |
如果要限制某个控制组对8:0磁盘的写入吞吐量不超过 10M/s,我们可以对blkio.throttle.write_bps_device参数进行配置:
1
|
echo "8:0 10485760" > /sys/fs/cgroup/blkio/blkio.throttle.write_bps_device
|
在 Linux 中,文件默认读写方式为 Buffered I/O,应用程序一般将文件写入到 PageCache 后就直接返回了,然后内核线程会异步将数据从内存同步到磁盘中。而 Direct I/O 不会和内存打交道,而是直接写入到存储设备中。
为什么 cgroup v1 不支持非 Buffer IO 的限制
cgroup v1 通常是每个层级对应一个子系统,子系统需要挂载使用,而每个子系统之间都是独立的,很难协同工作,比如 memory cgroup 和 blkio cgroup 能分别控制某个进程的资源使用量,但是blkio cgroup 对进程资源限制的时候无法感知 memory cgroup 中进程资源的使用量,导致对 Buffered I/O 的限制一直没有实现。
cgroup v1 因为有很多缺陷也导致了 linux 的开发者重新设计了 cgroup,也就有了 cgroup v2,在 cgroup v2 中就可以解决 Buffered I/O 限制的问题。cgroup v2 使用了统一层级(unified hierarchy),各个子系统都可以挂载在统一层级下,一个进程属于一个控制组,每个控制组里可以定义自己需要的多个子系统。cgroup v2 中 io 子系统等同于 v1 中的 blkio 子系统。
cgroup v2 具有以下要求:
- 操作系统发行版启用 cgroup v2
- Linux 内核为 5.8 或更高版本
- 容器运行时支持 cgroup v2。例如:
- containerd v1.4 和更高版本
- cri-o v1.20 和更高版本
- kubelet 和容器运行时被配置为使用 systemd group
Linux 发行版 cgroup v2 支持
有关使用 cgroup v2 的 Linux 发行版的列表, 请参阅 cgroup v2 文档。
- Container-Optimized OS(从 M97 开始)
- Ubuntu(从 21.10 开始,推荐 22.04+)
- Debian GNU/Linux(从 Debian 11 Bullseye 开始)
- Fedora(从 31 开始)
- Arch Linux(从 2021 年 4 月开始)
- RHEL 和类似 RHEL 的发行版(从 9 开始)
3. 设计方案
虽然blkio cgroup只能限制direct io,但我们目标只是不想在导入操作系统镜像到本地盘的时候,影响已经开通的虚机,当数据从pagecache回写到磁盘时,限制direct io还是能起到一定作用的。
目前k8s是不支持blkio cgroup的设置的,但底层容器运行时runc本身是支持blkio设置的,所以这里需要通过crio提供的hook机制在特定容器启动时,
执行程序设置blkio.throttle.read_bps_device和blkio.throttle.write_bps_device,只有在hook阶段才能对cgroup进行修改。
第一步需要到/usr/share/containers/oci/hooks.d目录加入hook配置文件,如下
cat blkio_hook.json { "version" : "1.0.0" , "hook" : { "path" : "/usr/local/bin/blkio_hook" , // 设置blkio cgroup的程序 "args" : [], "env" : [] }, "when" : { "annotations" : { "ecx.io/blkio_limit" : "true" // 只有匹配对应annotation的容器才会执行 } }, "stages" : [ "prestart" ] // hook在容器prestart阶段执行 } |
第二步需要开发blkio_hook,blkio_hook需要根据runc传入的参数,找到对应容器在宿主机的cgroup路径,找到磁盘的major,minor号,设置到blkio.throttle.read_bps_device和blkio.throttle.write_bps_device。
以下是开发好的例子,创建一个pod,限制读写速度为100k/s,然后用fio测试读写速度
--- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: blkio-pvc-block spec: accessModes: - ReadWriteMany volumeMode: Block storageClassName: zfs-sc resources: requests: storage: 20Gi --- apiVersion: v1 kind: Pod metadata: annotations: ecx.io/blkio_limit : "true" name: blktest spec: containers: - name : my-container image: openebs/perf-test : latest imagePullPolicy: IfNotPresent command: - sleep - "3600" volumeDevices: - devicePath : /dev/block name: my-volume volumes: - name : my-volume persistentVolumeClaim: claimName: blkio-pvc-block |