数月前,我曾撰文探讨如何在容器内部加速容器构建过程。该文重点关注容器镜像拉取速度,并介绍了通过主机卷挂载及Buildah的“附加存储”机制预填充镜像存储库的多种方案。
Buildah 是一款命令行工具,可快速轻松地构建符合开放容器倡议(即兼容 Docker 和 Kubernetes)的镜像。它易于集成到脚本和构建管道中,最重要的是,构建镜像时无需运行容器守护进程。
本文将探讨在容器内使用 dnf/yum 命令时构建速度的第二个问题。请注意,本文将使用上游名称 dnf(而非某些下游版本使用的 yum)。这些说明同时适用于 dnf 和 yum。
你是否注意到,有时当你时隔许久首次运行 dnf -y update或 dnf -y install 时,命令会在开始下载 RPM 包前长时间停滞?这是怎么回事?
dnf 首先会下载庞大的缓存文件。这些文件采用 XML 格式编写,包含远程仓库中的每个软件包,以及大量关于软件包的详细信息。甚至包含软件包内部的所有路径。这些数据对于执行类似 dnf -y install /usr/bin/httpd 的命令至关重要——dnf 正是通过这些信息来确定需要安装的软件包。许多软件包包含类似 (requires: /usr/bin/sendmail) 的指令,正是利用了此特性,使 dnf 能自动获取满足需求的相应软件包。
提取这些庞大的文件 —— 更重要的是处理这些文件 —— 可能需要超过一分钟的时间。这些数据由 libsolv 使用,因此必须转换为 solv 格式,而这个过程相当耗时。当您仅在主机上定期执行此操作时,速度问题并不突出,但在构建容器时,这便成了一个重大问题。
尽管 Buildah 允许您直接在终端中构建容器镜像,但大多数人仍使用 Dockerfile 和 Containerfile 来构建容器并定义可复现的镜像配方。Buildah 现已默认搜索 Containerfile 和 Dockerfile 文件。由于两者采用相同语法,本文后续将统一使用 Containerfile 进行说明。
在 Containerfile 中常见的操作是使用如下语法:
FROM ubi8 RUN dnf -y update; dnf -y install nginx; dnf -y clean all … RUN dnf -y install jboss; dnf -y clean all
让我们来检查一下 dnf 行。第一行 dnf 是:
(dnf -y update; dnf -y install nginx; dnf -y clean all):
更新容器内的所有软件包。
安装所选的软件包 nginx 。
清理所有内容。
由于 ubi8 镜像很可能创建于较早时期,且其 /var/cache/dnf 目录在容器镜像内部可能不存在,dnf 必须先下载 XML 缓存文件并进行处理。随后,dnf 会安装实际软件包,最后通过 dnf -y clean all 命令清除所有冗余数据——这些数据(如日志和缓存文件)是由先前命令写入镜像的。
建议用户运行 clean all 命令以尽可能缩小镜像体积。每次 RUN 命令都会创建新层,即使后续 RUN 命令清空内容,初始层仍会保留全部内容。这意味着所有拉取该镜像的用户最终都会获取日志和缓存文件。若您的容器文件包含一个或多个dnf命令,这种代价将反复累积。不仅如此,每次重建镜像时都需重新付出代价。若在构建服务器上操作,每次构建的容器镜像都会反复下载这些XML文件,造成海量资源与时间的浪费。
我们注意到上述问题,认为可以采用更优方案处理。我们能否直接将XML数据拉取到主机,在主机上处理后,再通过卷挂载到容器中?或许可以设置一个cron任务或systemd定时器,为每个需要构建容器镜像的操作系统版本执行一次dnf makecache?您可以在主机上每天运行一次或多次该任务,然后让所有容器构建器将相应的缓存卷挂载到buildah容器中。
Buildah支持将主机上的卷挂载到容器目录中。这本应解决问题,实际也确实如此。但问题在于,容器在更新缓存时往往需要向该目录写入数据,因此缓存必须以读写模式挂载到容器中。这便造成了巨大的安全漏洞。试想这样一种情况:恶意容器构建者向缓存写入内容,随后被其他容器构建者读取。这可能诱使第二个容器安装被篡改的软件。我们需要一种方案:内容被挂载到容器中,容器无法写入,但从容器视角看仍可写入。这正是Overlay挂载点的核心原理。
覆盖文件系统将下层目录挂载后,再将上层目录附加到合并后的挂载点。当进程向合并目录中的新文件写入时,新文件会被写入上层目录。当进程修改下层目录中的现有文件时,内核会将文件从下层目录向上层目录复制,并允许进程在上层目录中修改该文件。
我们向 Buildah 引入了 Overlay 挂载的概念。现在您可以通过以下方式运行构建:
buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f /tmp/Containerfile /tmp
容器内的Dnf仍会检查仓库是否有更新内容,若存在则会拉取新内容。但若主机上的内容已为最新版本,则会快速使用主机的缓存。建议您至少每天更新一次主机缓存。
我们为Buildah覆盖挂载新增的一项功能是在每次RUN指令执行后销毁上层目录。回顾示例中,我们使用了多个RUN命令,每个命令都执行了dnf -y clean all。该命令会导致上级目录显示下级目录的所有内容已被删除。若后续dnf命令复用先前创建的上级目录,将误判缓存为空,从而强制重新下载XML数据存储并进行处理。移除上级目录后,每个dnf命令将重新访问主机的下级目录,并继续共享主机缓存。
我将创建一个简单的容器文件,其中包含两个 dnf run 命令。
FROM fedora:31 RUN dnf -y install net-utils; dnf -y clean all RUN dnf -y install iputils; dnf -y clean all
在我的 Fedora 31 主机上本地运行这个:
# time -f "Elapsed Time: %E" buildah bud -f Containerfile . STEP 1: FROM fedora:31 STEP 2: RUN dnf -y install procps-ng; dnf -y clean all Fedora Modular 31 - x86_64 2.0 MB/s | 5.2 MB 00:02 Fedora Modular 31 - x86_64 - Updates 1.6 MB/s | 4.0 MB 00:02 Fedora 31 - x86_64 - Updates 4.2 MB/s | 19 MB 00:04 Fedora 31 - x86_64 1.8 MB/s | 71 MB 00:39 Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:55:54 2020. Dependencies resolved. ================================================================================ Package Architecture Version Repository Size ================================================================================ Installing: procps-ng x86_64 3.3.15-6.fc31 fedora 326 k Transaction Summary ================================================================================ Install 1 Package Total download size: 326 k Installed size: 966 k Downloading Packages: procps-ng-3.3.15-6.fc31.x86_64.rpm 375 kB/s | 326 kB 00:00 -------------------------------------------------------------------------------- Total 218 kB/s | 326 kB 00:01 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1 Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1 Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1 Installed: procps-ng-3.3.15-6.fc31.x86_64 Complete! 33 files removed STEP 3: RUN dnf -y install iputils; dnf -y clean all Fedora Modular 31 - x86_64 741 kB/s | 5.2 MB 00:07 Fedora Modular 31 - x86_64 - Updates 928 kB/s | 4.0 MB 00:04 Fedora 31 - x86_64 - Updates 3.8 MB/s | 19 MB 00:05 Fedora 31 - x86_64 7.9 MB/s | 71 MB 00:08 Last metadata expiration check: 0:00:01 ago on Wed Feb 5 13:57:13 2020. Dependencies resolved. ================================================================================ Package Architecture Version Repository Size ================================================================================ Installing: iputils x86_64 20190515-3.fc31 fedora 141 k Transaction Summary ================================================================================ Install 1 Package Total download size: 141 k Installed size: 387 k Downloading Packages: iputils-20190515-3.fc31.x86_64.rpm 252 kB/s | 141 kB 00:00 -------------------------------------------------------------------------------- Total 141 kB/s | 141 kB 00:01 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : iputils-20190515-3.fc31.x86_64 1/1 Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1 Verifying : iputils-20190515-3.fc31.x86_64 1/1 Installed: iputils-20190515-3.fc31.x86_64 Complete! 33 files removed STEP 4: COMMIT Getting image source signatures Copying blob ac0b803c5612 skipped: already exists Copying blob 922380d685bc done Copying config 566e2afbb4 done Writing manifest to image destination Storing signatures 566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb 566e2afbb417f0119109578a87950250b566a3b4908868627975a4c7428accfb Elapsed Time: 2:15.00
本次运行耗时2分15秒,成功创建了包含两个新软件包的新容器镜像。
现在让我们尝试使用来自主机的覆盖挂载进行操作。
# dnf -y makecache # time -f "Elapsed Time: %E" buildah bud -v /var/cache/dnf:/var/cache/dnf:O -f Containerfile . STEP 1: FROM fedora:31 STEP 2: RUN dnf -y install procps-ng; dnf -y clean all Last metadata expiration check: 0:02:34 ago on Wed Feb 5 13:51:54 2020. Dependencies resolved. ================================================================================ Package Architecture Version Repository Size ================================================================================ Installing: procps-ng x86_64 3.3.15-6.fc31 fedora 326 k Transaction Summary ================================================================================ Install 1 Package Total download size: 326 k Installed size: 966 k Downloading Packages: procps-ng-3.3.15-6.fc31.x86_64.rpm 496 kB/s | 326 kB 00:00 -------------------------------------------------------------------------------- Total 245 kB/s | 326 kB 00:01 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : procps-ng-3.3.15-6.fc31.x86_64 1/1 Running scriptlet: procps-ng-3.3.15-6.fc31.x86_64 1/1 Verifying : procps-ng-3.3.15-6.fc31.x86_64 1/1 Installed: procps-ng-3.3.15-6.fc31.x86_64 Complete! 285 files removed STEP 3: RUN dnf -y install iputils; dnf -y clean all Last metadata expiration check: 0:02:41 ago on Wed Feb 5 13:51:54 2020. Dependencies resolved. ================================================================================ Package Architecture Version Repository Size ================================================================================ Installing: iputils x86_64 20190515-3.fc31 fedora 141 k Transaction Summary ================================================================================ Install 1 Package Total download size: 141 k Installed size: 387 k Downloading Packages: iputils-20190515-3.fc31.x86_64.rpm 556 kB/s | 141 kB 00:00 -------------------------------------------------------------------------------- Total 222 kB/s | 141 kB 00:00 Running transaction check Transaction check succeeded. Running transaction test Transaction test succeeded. Running transaction Preparing : 1/1 Installing : iputils-20190515-3.fc31.x86_64 1/1 Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1 Verifying : iputils-20190515-3.fc31.x86_64 1/1 Installed: iputils-20190515-3.fc31.x86_64 Complete! 285 files removed STEP 4: COMMIT Getting image source signatures Copying blob ac0b803c5612 skipped: already exists Copying blob 524bb3b83d61 done Copying config 0f82aa6064 done Writing manifest to image destination Storing signatures 0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518 0f82aa6064814ff3dcb603c34c75e516e00817811681b83b8632f3e9b694e518 Elapsed Time: 0.17.44
借助Overlay挂载技术,我们仅用17秒就完成了包含两个新增软件包的新镜像构建,而传统方式需耗时2分15秒。这意味着构建相同容器镜像的速度提升了近8倍。
这表明,若在预先缓存了dnf元数据的主机操作系统上构建镜像,可大幅提升安装速度。但若您的构建系统需为不同操作系统版本构建镜像呢?例如您既要构建Fedora 30镜像,又要构建Fedora 31镜像。注:此方法同样适用于RHEL8系统,当您需要同时构建RHEL7甚至RHEL6镜像时。
Dnf具备一项实用功能:通过--releasever选项可指定不同版本的内容拉取。此外,您还能使用--setopt=cachedir参数指定缓存目录的替代路径。
在下面的示例中,我将先在主机上卸载两个缓存,然后在命令行模式下使用 Buildah。
# dnf -y makecache --releasever=31 --setopt=cachedir=/var/cache/dnf/31
# dnf -y makecache --releasever=30 --setopt=cachedir=/var/cache/dnf/30
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/31:/var/cache/dnf:O ${ctr31} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:41 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 192 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 107 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 0:06.85
# ctr30=$(buildah from fedora:30)
# time -f 'Elapsed Time: %E' buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils
Last metadata expiration check: 0:00:15 ago on Wed Feb 5 14:17:47 2020.
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20180629-4.fc30 fedora 123 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 123 k
Installed size: 351 k
Downloading Packages:
iputils-20180629-4.fc30.x86_64.rpm 370 kB/s | 123 kB 00:00
--------------------------------------------------------------------------------
Total 138 kB/s | 123 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20180629-4.fc30.x86_64 1/1
Running scriptlet: iputils-20180629-4.fc30.x86_64 1/1
Verifying : iputils-20180629-4.fc30.x86_64 1/1
Installed:
iputils-20180629-4.fc30.x86_64
Complete!
Elapsed Time: 0:08.88如您所见,我们成功在同一构建主机上使用两个不同版本的Fedora的dnf缓存运行了Buildah容器,其中Fedora 31的容器耗时6秒以上,而Fedora 30的构建耗时8秒以上。
注:我选择将缓存文件存放在/var/cache/dnf的子目录中,以确保SELinux标签正确。仅执行 dnf clean all 无法清理 /var/cache/dnf/31 目录。需执行 dnf clean all --setopt=cachedir=/var/cache/dnf/31 才能彻底清除仓库缓存文件,但部分残留物(如 gpg 密钥、空目录)仍会存在。
现在只需测试在未挂载Overlay的情况下,Fedora 31系统构建过程所需耗时。
# ctr31=$(buildah from fedora:31)
# time -f 'Elapsed Time: %E' buildah run ${ctr31} dnf -y install iputils
Fedora Modular 31 - x86_64 1.2 MB/s | 5.2 MB 00:04
Fedora Modular 31 - x86_64 - Updates 875 kB/s | 4.0 MB 00:04
Fedora 31 - x86_64 - Updates 2.4 MB/s | 19 MB 00:07
Fedora 31 - x86_64 1.7 MB/s | 71 MB 00:41
Dependencies resolved.
================================================================================
Package Architecture Version Repository Size
================================================================================
Installing:
iputils x86_64 20190515-3.fc31 fedora 141 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 141 k
Installed size: 387 k
Downloading Packages:
iputils-20190515-3.fc31.x86_64.rpm 279 kB/s | 141 kB 00:00
--------------------------------------------------------------------------------
Total 129 kB/s | 141 kB 00:01
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : iputils-20190515-3.fc31.x86_64 1/1
Running scriptlet: iputils-20190515-3.fc31.x86_64 1/1
Verifying : iputils-20190515-3.fc31.x86_64 1/1
Installed:
iputils-20190515-3.fc31.x86_64
Complete!
Elapsed Time: 1:29.85在此情况下,运行相同容器耗时近1.5分钟。而使用Overlay挂载的Buildah运行速度提升了14倍。
此前所有示例均使用Containerfiles以root身份运行构建。但您同样可通过无根容器实现此功能。在接下来的几个示例中,我将让Buildah使用buildah run语法运行容器,以演示缓存的使用。
执行:
$ buildah run -v /var/cache/dnf/30:/var/cache/dnf:O ${ctr30} dnf -y install iputils运行正常。只要用户能读取/var/cache/dnf/30目录,下级目录即可读取。但必须依赖主机端定期更新缓存。
用户若需要,甚至可使用dnf在个人主目录中创建缓存。
$ dnf -y makecache --releasever=30 --setopt=cachedir=$HOME/dnfcache
$ chcon --reference /var/cache/dnf -R $HOME/dnfcache
$ ctr30=$(buildah from fedora:30)
$ buildah run -v $HOME/dnfcache:/var/cache/dnf:O ${ctr30} dnf -y install iputils请注意,我不得不更改$HOME/dnfcache目录的SELinux标签,以便SELinux允许容器读取用于Overlay挂载的下层目录。
要加快容器构建速度,需要理解安装软件包时的底层机制。在主机端预缓存dnf数据,并通过Buildah使用Overlay挂载将缓存挂载到容器中,可显著提升构建速度并减少构建集群所需资源。
Buildah以简洁著称,同时具备Overlay挂载和附加存储等强大功能,能有效加速容器镜像构建。
[ 容器新手?下载《容器入门指南》学习Linux容器基础知识。 ]
原文地址:https://www.redhat.com/en/blog/speeding-container-buildah