13 | 容器磁盘限速:我的容器里磁盘读写为什么不稳定?

本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。

一、场景再现

运行 代码 运行一下make image来做一个带 fio 的容器镜像,用来测试磁盘文件读写性能的工具。

mkdir -p /tmp/test1
docker stop fio_test1;docker rm fio_test1
docker run --name fio_test1 --volume /tmp/test1:/tmp  registery/fio:v1 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1  -name=/tmp/fio_test1.log

容器文件系统不适合频繁的读写。
需要频繁读写的数据,容器需要把它们放在volume中,可以是一个本地磁盘,也可以是一个网络磁盘。

上面的例子中,把本地磁盘上/tmp/test1 目录作为 volume 挂载到容器的 /tmp 目录下。

启动容器后,运行fio命令结果如下:
IOPS 是 18K,带宽 (BW) 是 70MB/s 左右
在这里插入图片描述

启动两个容器测试结果如下:


mkdir -p /tmp/test1
mkdir -p /tmp/test2

docker stop fio_test1;docker rm fio_test1
docker stop fio_test2;docker rm fio_test2

docker run --name fio_test1 --volume /tmp/test1:/tmp  registery/fio:v1 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1  -name=/tmp/fio_test1.log &

docker run --name fio_test2 --volume /tmp/test2:/tmp  registery/fio:v1 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1  -name=/tmp/fio_test2.log &

在这里插入图片描述

在容器 fio_test1 里,IOPS 是 15K 左右,带宽是 59MB/s 了,比之前单独运行的时候性能下降了不少。

多个容器挂载同一个磁盘进行数据读写的时候,性能收到了干扰。

Cgroup v1 中有 blkio 子系统,它可以限制磁盘的I/O。 下面详细说明下。

二、知识详解

2.1、Blkio Cgroup
  • IOPS:Input/Output Operations Per Second 每秒钟磁盘读写的次数,数值越大性能越好
  • 吞吐量(Throughput):是指每秒钟磁盘中数据的读取量,一般以MB/s为单位。这个值可以称为吞吐量,也可以被称为宽带。

吞吐量 = 数据块大小 * IOPS

blkio Cgroup 也是Cgroups 里的一个子系统,在Cgroups V1里,blkio Cgroup的虚拟文件挂载点一般是 “/sys/fs/cgroup/blkio”

和之前的CPU,memory Cgroup 一样,我们在这个/sys/fs/cgroup/blkio 目录下创建子目录作为控制组,再把需要做I/O限制的进程pid 写到控制组的 cgroup.procs参数中就可以了。

在 blkio Cgroup 中有四个最主要的参数,它们可以用来限制磁盘 I/O 性能,如下:

#磁盘读取IOPS的限制
blkio.throttle.read_iops_device
#磁盘读取吞吐量的限制
blkio.throttle.read_bps_device
#磁盘写入IOPS的限制
blkio.throttle.write_iops_device
#磁盘写入吞吐量的限制
blkio.throttle.write_bps_device

内核blkio文档

举例如下:

要对一个控制组做限制,限制它对磁盘/dev/vdb 的写入吞吐量不超过 10MB/s,命令如下:

echo "252:16 10485760" > $CGROUP_CONTAINER_PATH/blkio.throttle.write_bps_device

命令中 “252:16” 是 /dev/vdb 的主次设备号,可以通过 ls -l /dev/vdb 查看
10485760 就是10MB/s的带宽限制。

# ls -l /dev/vdb -l
brw-rw---- 1 root disk 252, 16 Nov  2 08:02 /dev/vdb

运行如下的例子:限制了一个容器blkio 的读写磁盘吞吐量,再测试fio

mkdir -p /tmp/test1
rm -f /tmp/test1/*

docker stop fio_test1;docker rm fio_test1

docker run -d --name fio_test1 --volume /tmp/test1:/tmp  registery/fio:v1 sleep 3600

sleep 2

CONTAINER_ID=$(sudo docker ps --format "{{.ID}}\t{{.Names}}" | grep -i fio_test1 | awk '{print $1}')

echo $CONTAINER_ID

CGROUP_CONTAINER_PATH=$(find /sys/fs/cgroup/blkio/ -name "*$CONTAINER_ID*")

echo $CGROUP_CONTAINER_PATH

# To get the device major and minor id from /dev for the device that /tmp/test1 is on.

echo "253:0 10485760" > $CGROUP_CONTAINER_PATH/blkio.throttle.read_bps_device

echo "253:0 10485760" > $CGROUP_CONTAINER_PATH/blkio.throttle.write_bps_device

docker exec fio_test1 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=100MB -numjobs=1  -name=/tmp/fio_test1.log

docker exec fio_test1 fio -direct=1 -rw=read -ioengine=libaio -bs=4k -size=100MB -numjobs=1  -name=/tmp/fio_test1.log

在这里例子的机器上 /tmp/test1 所在磁盘主次设备号是”253:0”,你在自己运行这组命令的时候,需要把主次设备号改成你自己磁盘的对应值。

不同数据块大小,在性能测试中可以适用于不同的测试目的,在这里不是重点,为了方便理解使用一个固定值。

在这里插入图片描述
在这里插入图片描述
上面的测试结果可以看到,这个容器对磁盘无论是读还是写,它的最大值不会超过10MB/s

这样多个容器读写同一个磁盘,被限制后就会相互干扰了。

使用如下脚本验证下:


#!/bin/bash

mkdir -p /tmp/test1
rm -f /tmp/test1/*
docker stop fio_test1;docker rm fio_test1

mkdir -p /tmp/test2
rm -f /tmp/test2/*
docker stop fio_test2;docker rm fio_test2

docker run -d --name fio_test1 --volume /tmp/test1:/tmp  registery/fio:v1 sleep 3600
docker run -d --name fio_test2 --volume /tmp/test2:/tmp  registery/fio:v1 sleep 3600

sleep 2

CONTAINER_ID1=$(sudo docker ps --format "{{.ID}}\t{{.Names}}" | grep -i fio_test1 | awk '{print $1}')
echo $CONTAINER_ID1

CGROUP_CONTAINER_PATH1=$(find /sys/fs/cgroup/blkio/ -name "*$CONTAINER_ID1*")
echo $CGROUP_CONTAINER_PATH1

# To get the device major and minor id from /dev for the device that /tmp/test1 is on.

echo "253:0 10485760" > $CGROUP_CONTAINER_PATH1/blkio.throttle.read_bps_device

echo "253:0 10485760" > $CGROUP_CONTAINER_PATH1/blkio.throttle.write_bps_device

CONTAINER_ID2=$(sudo docker ps --format "{{.ID}}\t{{.Names}}" | grep -i fio_test2 | awk '{print $1}')
echo $CONTAINER_ID2

CGROUP_CONTAINER_PATH2=$(find /sys/fs/cgroup/blkio/ -name "*$CONTAINER_ID2*")
echo $CGROUP_CONTAINER_PATH2

# To get the device major and minor id from /dev for the device that /tmp/test1 is on.
echo "253:0 10485760" > $CGROUP_CONTAINER_PATH2/blkio.throttle.read_bps_device

echo "253:0 10485760" > $CGROUP_CONTAINER_PATH2/blkio.throttle.write_bps_device

docker exec fio_test1 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=100MB -numjobs=1  -name=/tmp/fio_test1.log &

docker exec fio_test2 fio -direct=1 -rw=write -ioengine=libaio -bs=4k -size=100MB -numjobs=1  -name=/tmp/fio_test2.log &

fio 运行输出的结果,在两个容器中执行的结果都是 10MB/s了。
在这里插入图片描述
在这里插入图片描述

将fio命令中的 “-direct=1” 给去掉,也就是不让fio 运行在Direct I/O 模式,使用 Buffered I/O 模式再运行一次,看看fio 执行的输出。

同时我们也可以运行 iostat 命令,查看实际的磁盘写入速度。

这时候你会发现,即使我们设置了 blkio Cgroup,也根本不能限制磁盘的吞吐量了。

2.2、Direct I/O 和Buffered I/O

Direct I/O 模式:用户进程写磁盘文件,会通过Linux内核的文件系统层(filesystem)-》块设备层(block layer)-》磁盘驱动 -》磁盘硬件 的过程。

Buffered I/O模式:用户进程只是把文件数据写到内存中(page cache)就返回了,而Linux 内核自己有线程会把内存中的数据再写入到磁盘中。Linux里,由于考虑到性能问题,绝大多数的应用都会使用Buffered I/O模式

在这里插入图片描述

前面的测试发现: Direct I/O 可以通过blkio Cgroup 来限制磁盘I/O,但是Buffered I/O 不能被限制。

原因就是被Cgroups v1的架构限制了。

v1 版本中每一个子系统都是独立的,资源的限制只能在子系统中发生。

在这里插入图片描述

每个子系统之间,相互不关联,就会有漏洞产生,可以绕过某个限制。

进程 pid_y,它可以分别属于 memory Cgroup 和 blkio Cgroup。
但是在 blkio Cgroup 对进程 pid_y 做磁盘 I/O 做限制的时候,blkio 子系统是不会去关心 pid_y 用了哪些内存,哪些内存是不是属于 Page Cache,而这些 Page Cache 的页面在刷入磁盘的时候,产生的 I/O 也不会被计算到进程 pid_y 上面。

就是这个原因,导致了 blkio 在 Cgroups v1 里不能限制 Buffered I/O。

2.3、Cgroup v2

Cgroup v2 相比 Cgroup v1 做的最大的变动就是一个进程属于一个控制组,每个控制组中可以定义自己需要的多个子系统。

在这里插入图片描述

Cgroup 对进程 pid_y 的磁盘I/O 做限制的时候,就可以考虑到进程pid_y 写入到 Page Cache 内存的页面了,这样 buffered I/O 的磁盘限速就实现了。

目前Linux 系统中缺省还是Cgroup v1 版本,打开Cgroup v2 版本的方法:
配置一个kernel参数"cgroup_no_v1=blkio,memory" 这表示把Cgroup v1的blkio 和memory 两个子系统给禁止,这样Cgroup v2的io和memory 两个子系统就打开了。

可以把参数配置到grub 中,然后重启Linux 机器,这时Cgroup v2的io和memory 两个子系统功能就打开了。

系统重启后,会看到Cgroup v2的虚拟文件系统被挂载到了 /sys/fs/cgroup/unified 目录下。

然后使用下面的脚本测试 Cgroup v2 io 的限速配置,并且fio看看buffered I/O 是否可以被限速。

# Create a new control group
mkdir -p /sys/fs/cgroup/unified/iotest

# enable the io and memory controller subsystem
echo "+io +memory" > /sys/fs/cgroup/unified/cgroup.subtree_control

# Add current bash pid in iotest control group.
# Then all child processes of the bash will be in iotest group too,
# including the fio
echo $$ >/sys/fs/cgroup/unified/iotest/cgroup.procs

# 256:16 are device major and minor ids, /mnt is on the device.
echo "252:16 wbps=10485760" > /sys/fs/cgroup/unified/iotest/io.max
cd /mnt
#Run the fio in non direct I/O mode
fio -iodepth=1 -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1  -name=./fio.test

此例子中建立了 iotest 控制组,并在控制组中加入了 io 和Memory两个控制子系统,对磁盘最大吞吐量的设置为10MB。运行fio的时候不加 “-direct=1”,也就是让fio 运行在buffered I/O模式下。

运行fio 写入1GB数据,fio 马上执行完了。
因为系统上有足够的内存,fio 把数据写入内存就返回了,再运行 “iostat -xz 10” 查看磁盘的I/O,可以看到磁盘 vdb 上稳定的写入速率是 10240wkb/s,也就是io Cgroup里限制的10MB/s。

在这里插入图片描述

结果证实了 Cgroupv2 io +memory 两个子系统一起使用,就可以对buffer I/O 控制磁盘写入速率。

三、重点总结

目的:保证容器读写磁盘速率的稳定,特别是多个容器同时读写一个磁盘的时候,减少相互干扰。

Cgroup v1中的blkio子系统,只能控制Direct I/O的读写文件做磁盘限速,对Buffered I/O的文件读写限速不了。

原因:Buffered I/O 会把数据先写入到内存page cache中,然后由内核线程把数据写入磁盘,而Cgroup v1 blkio 子系统独立于memory 子系统,无法统计到由page cache刷入到磁盘的数据量。

Cgroup v2 解决了上述的问题。
在新的架构上允许一个控制组里有多个子系统协同运行,这样在一个控制组里只要有io和memory子系统,就可以对Buffered I/O 做磁盘读写的限速。

但是实际,目前runC、containerd、kubernetes都是刚刚开始支持Cgroup v2。

四、评论

1、
问题:cgroup v2在buffered I/O 模式下能被限制,是不是可以理解为:写入的1G数据对应的page cache属于该进程,内核同步该部分page cache时产生的I/O会被计算到该进程的I/O中

回复:是的

2、
问题:老师,假如一个容器跨多块磁盘是不是需要拿到每块磁盘的主次设备号都设置一遍iops限制?

回复:如果一个容器用到多个磁盘(volume),并且都要做io限制,那么都要设置一遍。

3、
问题:吞吐量 = 数据块大小 *IOPS。 如果限制了IOPS, 是不是也可以做到容器的写入磁盘也不会互相干扰了?

回复:iops也是blkio里的限制参数之一。我在例子中用了吞吐量来限制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值