1. 容器文件的 Quota
容器虽然有自己的文件系统,但是容器在容器文件系统(overlayfs)中写入的数据,最终还是存到宿主机的磁盘上,因为容器文件系统其实只是宿主机上的一个目录而已。那么,这不仅影响容器本身,还会影响宿主机。
一种方式是通过给容器挂载一个 volume,这个 volume 可以是选择文件啥的。并且,这也是推荐的方式,因为对于容器来说,如果有大量的写操作是不建议写入容器文件系统的,一般是需要给容器挂载一个 volume,用来满足大量的文件读写。
第二种方式是对容器写入 OverlayFS 的数据量做限制,比如只允许一个容器写 100MB 的数据。OverlayFS 文件系统的特性中没有直接限制文件写入量的特性。那么,我们可以从 overlay 的本质出发来进行限制。因为 overlayFS 是通过 lowerdir 和 upperdir 两层目录联合挂载来实现的,lowerdir 是只读的,数据只会写在 upperdir 中。那么,其实我们只要通过限制 upperdir 目录容量的方式,就可以限制一个容器 OverlayFS 根目录的写入数据量。
那么宿主机上的文件系统是否支持对一个目录限制容量呢?其实 Linux 上最常用的两个文件系统 XFS 和 ext4,它们有一个特性 Quota 可以限制一个目录的使用量。
需要注意的是:ext4 自身支持 project quota 功能(Linux 4.5 版本之后)。但是,overlay2 over ext4 不支持 project quota 功能。
官方文档也有提到:https://docs.docker.com/engine/reference/commandline/run/#set-storage-driver-options-per-container
For the overlay2 storage driver, the size option is only available if the backing fs is xfs and mounted with the pquota mount option.
1.1. XFS Quota
在 Linux 系统里的 XFS 文件系统缺省都有 Quota 的特性,这个特性可以为 Linux 系统里的一个用户(user),一个用户组(group)或者一个项目(project)来限制它们使用文件系统的额度(quota),也就是限制它们可以写入文件系统的文件总量。由于同一个用户或者用户组可以操作多个目录,多个用户或者用户组也可以操作同一个目录,假如对一个用户或者用户组的限制,其实很难实现对一个目录的限制。那么对一个目录的限制应该使用 projetc 方式。
-
首先我们要使用 XFS Quota 特性,必须在文件系统挂载的时候加上对应的 Quota 选项,比如我们目前需要配置 Project Quota,那么这个挂载参数就是"pquota"。并且对于根目录来说,这个参数还必须作为一个内核启动的参数"rootflags=pquota",这样设置就可以保证根目录在启动挂载的时候,带上 XFS Quota 的特性并且支持 Project 模式。
我们可以从 /proc/mounts 信息里,看看根目录是不是带"prjquota"字段。如果里面有这个字段,就可以确保文件系统已经带上了支持 project 模式的 XFS quota 特性。
-
之后的第一步是给目标目录打上一个 Project ID,这个 ID 最终是写到目录对应的 inode 上。那么一旦目录打上这个 ID 之后,在这个目录下的新建的文件和目录也都会继承这个 ID。
-
第二步,在 XFS 文件系统中,我们需要给这个 project ID 设置一个写入数据块的限制。有了 ID 和限制值之后,文件系统就可以统计所有带这个 ID 文件的数据块大小总和,并且与限制值进行比较。一旦所有文件大小的总和达到限制值,文件系统就不再允许更多的数据写入了。
我们可以使用 XFS 文件系统自带的工具 xfs_quota 来完成上述效果:
-
首先新建的目录 /tmp/xfs_prjquota,由于我们想对它做 Quota 限制。所以在这里要对它打上一个 Project ID。在这里就通过 xfs_quota 这条命令,我们给 /tmp/xfs_prjquota 打上 Project ID 值 101,这个 101 就是个 ID 标识。
# mkdir -p /tmp/xfs_prjquota # xfs_quota -x -c 'project -s -p /tmp/xfs_prjquota 101' / Setting up project 101 (path /tmp/xfs_prjquota)... Processed 1 (/etc/projects and cmdline) paths for project 101 with recursion depth infinite (-1).
-
之后,还是使用 xfs_quota 命令,对 101(我们刚才建立的这个 Project ID)做 Quota 限制。命令里面的"-p bhard=10m 101"就代表限制 101 这个 project ID,限制它的数据块写入量不能超过 10MB。
# xfs_quota -x -c 'limit -p bhard=10m 101' /
-
做好限制之后,我们可以尝试往 /tmp/xfs_prjquota 写入 20MB 数据。可以看到,执行 dd 写入命令,就会有个出错返回信息"No space left on device"。这表示已经不能再往这个目录下写入数据了,而最后写入数据的文件 test.file 大小也停留在了 10MB。
# dd if=/dev/zero of=/tmp/xfs_prjquota/test.file bs=1024 count=20000 dd: error writing '/tmp/xfs_prjquota/test.file': No space left on device 10241+0 records in 10240+0 records out 10485760 bytes (10 MB, 10 MiB) copied, 0.0357122 s, 294 MB/s # ls -l /tmp/xfs_prjquota/test.file -rw-r--r-- 1 root root 10485760 Oct 31 10:00 /tmp/xfs_prjquota/test.file
1.2. Docker 容器中的限制实现
Docker 也已经实现了限流功能,也就是用 XFS Quota 来限制容器的 OverlayFS 大小。在用 docker run 启动容器的时候,加上一个参数 --storage-opt size= ,就能限制住容器 OverlayFS 文件系统可写入的最大数据量了。同时使用 df -h 命令查看的时候,看到的容量其实是做了限制的。
Docker 里 SetQuota() 函数(https://github.com/moby/moby/blob/19.03/daemon/graphdriver/quota/projectquota.go#L155)就是用来实现 XFS Quota 限制的,我们可以看到它里面最重要的两步,分别是 setProjectID() 和 setProjectQuota()。而这两个函数里分别调用了 ioctl() 和 quotactl() 这两个系统调用来修改内核中 XFS 的数据结构,从而完成 project ID 的设置和 Quota 值的设置。
1.3. kubernetes 中的实现
k8s 1.14 开始,默认开启 LocalStorageCapacityIsolation,可以通过限制resources.limits.ephemeral-storage 和resources.requests.ephemeral-storage 来保护宿主机 rootfs了。k8s 通过du来检查ephemeral-storage相关volume/目录的大小,然后如果超出limit就evict pod。
du 的开销比较大, 并且 evict pod 只适合 stateless pod,所以最新的 k8s,可能是在用filesystem quota来限制emptyDir的大小了。
巨人的肩膀
- 极客时间.《容器实战》