当 docker 容器使用主机安装的卷启动时,磁盘 I/O 是多少,我该如何减少它?

问题描述

在 RHEL 上运行带有主机挂载卷(来自 dockerdocker-compose)的 docker 容器时,我观察到大量磁盘 I/O(使用 dstat)之前容器启动。 I/Odockerd 进程相关联,我可以通过挂载或删除主机卷来明确增加或减少 I/O。 如果我不挂载任何主机卷,容器会立即启动。如果我挂载覆盖大部分文件系统的卷,I/O 很重要,在我的情况下大约 20 Gb,在容器启动前大约需要三分钟。在某些情况下,这会导致 docker-compose up 业务流程超时。

典型的运行命令如下所示

docker run -it --rm --name my_container \
-v /host/app/src:/app:ro,z \                         # host volume defined here
-v my_ro_data:/data/read_only/files:ro,z \           # external named volume
-v /host/data/write:/data/container_output/files:z \ # another host volume
my/image:latest

无论卷是否是预定义的命名卷,也无论是否使用语法将其标记为只读,都会发生 I/O。 但是当定义一个外部命名卷时,它看起来是这样的:

docker volume create \
--driver local \
--opt type=none \
--opt o=bind \
--opt device=/host/data/files \
my_ro_data

我假设 I/O 与覆盖文件系统有关,但我找不到任何明确的解释来说明究竟写入了什么、写入的位置以及如何优化配置以减少 I/O在容器启动之前。分明不是整卷的内容,所以好像有什么区别吧?但是,假设我有某种大规模数据管道,并且我想将我的容器指向主机源或具有 TB 级文件的目标目录...如何挂载主机卷而对容器启动延迟的影响较小?

更新: 根据@BMitch 的指导,我专注于 SELinux 相关的 ":z" 标签

简史: 最初(大约在帖子发布前一年),如果没有此标签,我们的 RHEL w/SELinux 服务器上的 docker 容器无法访问已安装的卷。尽管 --volumes-from一个不同的 cli 选项,但它具有其他来源在解决访问问题时所引用的最佳解释:

像 SELinux 这样的标签系统需要在上面放置适当的标签内容安装到容器中。没有标签,安全 系统可能会阻止容器内运行的进程 使用内容认情况下,Docker 不会更改标签集 由操作系统。要更改容器上下文中的标签,您可以添加 卷安装的两个后缀 :z 或 :Z 之一。这些后缀 告诉 Docker 重新标记共享卷上的文件对象。 z 选项告诉 Docker 两个容器共享卷内容。作为一个 结果,Docker 使用共享内容标签标记内容。共享 卷标允许所有容器读/写内容。 Z选项 告诉 Docker 使用私有的非共享标签标记内容

这个解释伴随着一个警告:elswhere:

使用 Z 绑定挂载系统目录,例如 /home 或 /usr 选项使您的主机无法运行,您可能需要 手动重新标记主机文件

所以我使用 ":z" 有时使用 ":ro,z" 并且一切正常。

事实证明,这个标签导致了预启动磁盘 I/O。我不太了解 SELinux 安全性和标签,但我认为 I/O 是在挂载卷时实际更改文件标签,因此文件越多,磁盘 I/O 时间越长。

我的观察是删除标签而不做任何其他事情会导致相同的行为。这意味着 docker 引擎现在的认行为是将 SELinux 挂载的卷视为标记":z"。我相信这是一种可能在过去一年中引入的新行为……或者其他一些系统更改……因为现在可以在没有标签的情况下访问卷(或者标签可能是永久性的,允许后续 docker 访问)。

但是,删除 :z 并不能解决 I/O 和启动时间长的问题。然后我发现 this github conversation 声称 :z 和 :Z 都是潜在危险的选择和评论

如果您的容器确实需要更广泛地访问系统目录, 然后将“--security-opt label:disable”与“docker run”一起使用 命令是更好的选择。请注意,使用上述选项 相反,将禁用对该容器的 SELinux 检查。

所以我添加了这个选项,事实上,卷是可访问的,并且磁盘 I/O 为零(或最少)和启动延迟。

也就是说,我真的不明白 --security-opt label:disable 的影响,欢迎任何其他建议或解释。

解决方法

几种可能性,根据您的问题尚不清楚哪种可能性。

如果是覆盖文件系统,那将是出乎意料的,因为没有文件复制来设置它。而且这也不能反映您的描述,因为它只发生在卷上,并且您为每个容器都有一个覆盖文件系统(假设图形驱动程序设置为覆盖)。

对于无根 docker 守护进程(dockerd 不以 root 身份运行),您可以看到图形驱动程序切换到本机,这意味着它为每个层和容器复制图像文件系统,这非常昂贵。但是您会在使用大量磁盘空间时注意到这一点,而在没有卷的情况下也会发生这种情况。

对于命名卷,当该卷为空并且使用命名卷创建容器时,docker 将使用映像的内容初始化命名卷。这包括所有文件、权限、所有权和其他元数据。当命名卷已经有数据时会跳过这个初始化步骤,而主机卷不会发生这种情况。

如果您的问题特定于主机卷,那么我唯一能想到的就是使用卷上的“z”选项设置的 selinux 标签。否则,默认情况下命名卷和主机卷都是 Linux 绑定挂载,这些通常是非常快速的操作。

最后,不清楚“在容器启动之前”是否包括容器内的应用程序进入就绪状态所花费的时间。要将 docker 步骤与容器内的应用程序分开,请将应用程序更改为诸如 --entrypoint true 之类的简单内容,这将导致容器在创建后立即退出。如果需要 30 秒才能退出,您知道 docker 很慢,但如果它立即进入退出状态,那么问题与 docker 无关,问题在于您在容器内运行的内容。