我正在调查将两个依赖于同一共享对象的进程容器化对内存的影响。我的主要问题是共享对象是否会在内存中加载两次。
这个问题之前已经有人问过了,但是答案并不完整,而且与我的实验相矛盾。我将引用我已经遇到的情况:
共享库的加载和 RAM 的使用--> 对 .so 使用 -fPIC
docker 容器是否共享从同一层但不同映像映射的文件内存的 RAM? --> 是的,但取决于您的设置。您可以通过检查已加载共享对象的设备和 inode id 来证明这一点。没有指示要使用什么设置。
https://stackoverflow.com/questions/35863608/shared-library-in-containers --> 是的,取决于您的存储驱动程序。aufs、overlay 或 overlay2 存储驱动程序支持此功能。
https://stackoverflow.com/questions/63145223/about-loading-dynamic-library-in-container --> 每个容器都是自己的执行单元。不发生共享。与前文相矛盾。
我使用了以下设置:
- 基础层 --> 包含操作系统和两个共享对象 (G 和 S - 使用 -fPIC 预编译)
- DependsOnG 层 --> 从基础层开始,包含仅依赖于 G 的可执行文件(预编译)
- DependsOnGandS 层 --> 从基础层开始,包含依赖于 G 和 S 的可执行文件(预编译)
存储驱动程序:Overlay
下面您可以看到来自 grep glib /proc/PID/maps 的信息。PID 是容器内可执行文件的进程。
| /proc/17/maps:7efd3b630000-7efd41980000 | r-xp | 00000000 | 00:a2 | 16802420 | /app/libglib.so |
| /proc/17/maps:7efd41980000-7efd41b7f000 | ---p | 06350000 | 00:a2 | 16802420 | /app/libglib.so |
| /proc/17/maps:7efd41b7f000-7efd41b80000 | r--p | 0634f000 | 00:a2 | 16802420 | /app/libglib.so |
| /proc/17/maps:7efd41b80000-7efd41b81000 | rw-p | 06350000 | 00:a2 | 16802420 | /app/libglib.so |
| /proc/39/maps:7fca97208000-7fca9d558000 | r-xp | 00000000 | 00:bb | 16802420 | /app/libglib.so |
| /proc/39/maps:7fca9d558000-7fca9d757000 | ---p | 06350000 | 00:bb | 16802420 | /app/libglib.so |
| /proc/39/maps:7fca9d757000-7fca9d758000 | r--p | 0634f000 | 00:bb | 16802420 | /app/libglib.so |
| /proc/39/maps:7fca9d758000-7fca9d759000 | rw-p | 06350000 | 00:bb | 16802420 | /app/libglib.so |
如果我正确地解释了结果,那么两个进程将查看完全不同的文件(设备 ID 不同),因此共享对象将在内存中加载两次(似乎与我使用 free 命令看到的一致)。
根据我在网上找到的信息,这种情况不应该发生。
我有以下问题:
- 共享对象是否应该仅加载一次?
- 覆盖存储驱动程序是否支持此功能?
- 我应该以不同的方式构建我的图像/容器吗?(我使用卷而不是公共层进行了相同的实验,并且文件确实是相同的,但这并不是我想要的)
提前致谢!
您对这个话题感到困惑有两个原因:
简单的答案是,默认情况下,当文件来自同一层时,docker 会在容器之间共享同一文件的内存。更改 sorage 驱动程序可能会导致它执行其他操作(如此处指出的那样)。
不,事实并非如此。 提示:两个相同的文件在两个不同的文件系统上偶然具有相同的 inode 编号的可能性很小。
您现在看到的行为记录在覆盖下:
https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#inode-properties
我不知道在Stephen Kitt回答之后这种情况是如何/何时发生变化的。我知道我当时检查了结果。
由于 docker 镜像的层和可写层都位于同一个文件系统上,因此即使同一个底层文件系统对象只映射过一次,您也应该期望不同的设备 (st_dev)。这是因为每个容器都有自己的可写层,因此 Docker 将为每个容器安装一个新的覆盖层。
但覆盖只是简单地传递到底层文件系统上未修改的文件。
文档
https://docs.docker.com/storage/storagedriver/overlayfs-driver/#the-image-layers
我没有检查结果,但由于图像是通过硬链接合成的,您应该能够调查图像层的内容并使用简单的
stat <file>
命令确认它们是相同的文件。因此,使用覆盖时,文件内容将是存储在磁盘上同一位置的同一文件,而不是副本。这样磁盘缓存只需加载一次。
Podman 目前正在添加对composefs的支持。该集成尚未完全投入生产,因为我发现最近几周(2024 年 7 月)有一些错误正在修复。
希望在运行 rootful podman 时,对 composefs 的支持能够带来以下功能:
(相同的文件不需要属于同一层)
有关 composefs 的介绍,请参阅此演示文稿(视频 + 幻灯片) https://fosdem.org/2024/schedule/event/fosdem-2024-3250-composefs-and-containers/
目前正在进行开发,开始在基于镜像的操作系统(例如 Fedora Silverblue、Fedora CoreOS)中使用 composefs。将来,甚至可能在主机和容器之间共享相同文件的磁盘空间和页面缓存。