Podman 通过部分拉取更快地拉取容器镜像

你有没有想过,为什么使用像Podman这样的容器工具从容器 registry 拉取容器镜像需要这么长时间?这个Fedora基础镜像相当小巧,在高速网络连接下仅需20秒即可下载完成。我听说有些庞大的镜像需要数分钟才能拉取完毕。更糟糕的是,每次Fedora镜像——或任何镜像——更新时,你都必须重新拉取整个镜像,而非仅更新差异部分。

你有没有想过,为什么使用像Podman这样的容器工具从容器 registry 拉取容器镜像需要这么长时间?

$ time podman pull fedora
Resolved "fedora" as an alias (/etc/containers/registries.conf.d/000-shortnames.conf)
Trying to pull registry.fedoraproject.org/fedora:latest...
Getting image source signatures
Copying blob 944c4b241113 done   
Copying config 191682d672 done   
Writing manifest to image destination
Storing signatures
191682d6725209667efcfd197c4dc93be5ab33729b7a4a2a45d5cf2bc1f589e0
 
real    0m19.329s
user    0m4.213s
sys    0m0.829s

这个Fedora基础镜像相当小巧,在高速网络连接下仅需20秒即可下载完成。我听说有些庞大的镜像需要数分钟才能拉取完毕。更糟糕的是,每次Fedora镜像——或任何镜像——更新时,你都必须重新拉取整个镜像,而非仅更新差异部分。

大型镜像还会引发存储问题。你是否曾核查过容器镜像在磁盘上占用的空间?有些用户仅因下载了数百甚至数千个容器镜像,就导致家目录空间告急。这些镜像往往包含大量重复文件。

另一个需要考虑的是内核内存。Linux 内核足够智能,能够识别当两个不同进程加载相同的容器(如共享库)时,应仅将其加载到内存中一次。例如,当10个同时运行的程序都使用libc库时,/usr/lib/libc.so.6 代码仅会被加载到内核内存中一次。但当使用镜像运行容器时,若多个不同镜像中都包含相同的 /usr/lib/libc.so.6,内核就会陷入混乱,导致相同内容的多个版本被加载到内存中,造成资源浪费。由于当前容器镜像的存储方式,这种情况非常普遍。

本文介绍了已整合到容器工具中的新技术,这些技术旨在:

  • 让拉取镜像的速度快得多

  • 让磁盘上的文件存储更加精简

  • 让Linux内核知道内容何时可以在内存中共享

容器管理员可以通过使用之前存储在磁盘上的内容来完成所有这些操作,而无需拉取和复制内容。

我们一直以来是如何拉取容器镜像的

开放容器倡议(OCI)容器镜像以一系列层的形式分发。每层包含镜像中文件的子集。这些层以压缩的tarball归档形式存储在注册表中。

使用覆盖后端时,容器运行时会将每层提取到不同的目录中。

运行时,每层都会作为底层用于最终容器覆盖文件系统的挂载。

由以下行组成的容器文件(Dockerfile)将生成一系列不同的层:

  • FROM fedora 生成基础Fedora镜像层

  • RUN yum install -y nginx 包含所有由 yum 创建的新文件

  • COPY ./config/foo /etc/nginx 包含文件 /etc/nginx/foo

目前,用户仅能执行逐层去重操作。不同图像可共享图层,但需严格规范操作。增加图层数量虽有助于去重,却会带来运行时开销——因为叠加文件系统每次查找时都需要扫描更多图层,并构建目录列表。

新模型试图解决什么问题

新的存储模型正试图通过做出以下更改来解决一系列问题:

  • Containerfile 的作者无需担心注册表将如何存储镜像以及优化重复数据删除。他们也可以创建压缩镜像,而不必担心重复数据删除的问题。

  • 容器引擎不必拉取本地已有的文件。

  • 存在于多个图层中的文件只能存储一次(这需要文件系统的支持)。

  • 被多个图层或容器使用的只读文件在内存中只需映射一次。

[了解有关红帽OpenShift容器平台的更多信息]

新模型的镜像格式选项

当前容器引擎使用的图像格式——gzip压缩的tar包——缺乏足够的信息来实现这些优化。

第一步是创建支持单独文件检索的分层结构。

目前支持两种候选方案:eStargz 和 zstd:chunked。这些新格式保留了tar包中每个文件的元数据,包括其校验和。

eStargz

eStargz 是 containerd 用于延迟镜像拉取的文件格式,基于谷歌概念验证项目 CRFS 开发。该格式将 gzip 压缩的镜像层转换为等效的 tarball 文件,其中每个文件均独立压缩。系统可逐个文件检索,无需完整获取并解压整个 tarball。

图层元数据作为 tar 流的一部分直接附加至 tarball 文件本身。这构成重大变更,因为解压后的 tarball 不仅校验和不同,还包含额外文件。

受容器及镜像 API 限制,我们的容器工具目前仅能读取此类镜像,尚无法创建。

zstd:chunked

我们创建了一个名为 zstd:chunked 的新方案,以解决 eStargz 格式在更改 DiffID 并将元数据作为 tarball 组成部分添加时存在的问题。zstd:chunked 利用了 zstd 压缩格式。

在 zstd:chunked 格式中,eStargz 使用的相同元数据被添加到压缩流内。zstd 解压器会忽略这些附加元数据,确保解压文件的摘要值保持不变。此外,zstd 的压缩速度远快于 gzip,且压缩率更高。

不过采用此格式仍存在若干问题:

  • Moby项目最近合并了这个拉取请求,这将在Docker的下一个版本中增加对zstd的支持。使用zstd的镜像在旧版本的Docker上无法运行。

  • Quay.io 目前尚不接受 OCI 镜像,但该问题正在解决中。

如何实现新格式

当拉取以这两种格式之一存储的层时,容器引擎会执行以下步骤:

  • 它会从注册表中检索该层的元数据。这是一个JSON文件,用于描述镜像层的内容及其包含的文件。

  • 本地已有的文件会通过重连(reflinks)进行复制。目前这一功能在XFS和BTRFS文件系统上受支持。如果不支持重连,则会复制文件,且不会执行存储去重。

  • 容器引擎会准备一个HTTP多范围请求,该请求指定所有本地尚未知晓的文件,并向注册表请求这些文件。

  • 新文件是根据注册表发送的数据创建的。

因此,重复数据删除在拉取时发生(已知文件不会被检索),同时在存储层也进行重复数据删除,因为相同文件会通过引用链接(在支持的情况下)实现去重。

部分拉取功能无需为对象文件额外存储空间,因为它直接从最终检出的存储目录中读取这些文件。

如何提取tar包

目前,tarball解压操作在独立进程中执行,该进程运行于chroot环境中。此机制可防止恶意构造的镜像利用符号链接解析机制,在目标目录外创建文件。

由于新增的重复数据删除功能需要访问目标目录外的文件,容器运行时无法沿用现有的解压代码。

[ You might also be interested in reading about the principles of container-based application design. ]

启用部分提取功能时将使用新的提取器。该功能需要调用新增于 Linux 内核 5.6 的 openat2 系统调用。openat2 允许以与 chroot 相同的行为限制文件查找。若提取器无法使用 openat2 系统调用,代码将回退至旧机制以提取完整层级。

如何对主机进行重复数据删除

在使用OSTree的系统上,您可以利用已进行哈希处理的系统文件来执行重复数据删除。要使此功能正常工作,您必须通过有效负载校验和启用OSTree跟踪,例如:

$ ostree --repo=/ostree/repo config set core.payload-link-threshold 100

如何对内存进行重复数据删除

引用链接具有不同的inode,而Linux虚拟文件系统(VFS)层无法识别它们,因为它们由文件系统直接处理。

当访问两个使用引用链接的inode时,即使文件系统中仅存储一次数据,内核仍会将相同数据加载到内存中两次。

若需内存去重,可配置为使用硬链接替代引用链接。

我们建议仅在内存资源匮乏的特定场景下使用硬链接去重方案。此举将构成存储模型的重大变更:由于所有inode元数据(如访问时间、修改时间、创建时间)在共享inode的去重文件间同步,部分镜像行为可能发生变化。此外,n_link属性将记录文件被去重处理的次数。

提取时避免锁定

容器存储长期存在的问题在于,当解压tarball并创建新层时会保持锁定状态。这是因为存储驱动程序需要知道每个已解压层的摘要值,才能应用下一层。

新提取器功能的附带效果解决了此问题:由于检出操作在独立的暂存目录中进行,各层可并行提取。仅在将暂存目录原子性移动至最终目标位置时才需要锁定。

构建一个 zstd:chunked 镜像

Buildah 新增了一些用于构建zstd:chunked镜像的选项。压缩格式是在将镜像推送到 registry 时指定的。

$ buildah bud --squash --format oci -t example.com/my-new-zstd-chunked-image
$ buildah push --compression-format zstd:chunked example.com/my-new-zstd-chunked-image

启用并使用部分拉取

部分拉取功能仍处于实验阶段,默认情况下未启用。

要启用该功能,您必须在 storage.conf 文件的 storage.options 部分添加以下配置:

pull_options = {enable_partial_images = "true", use_hard_links = "false", ostree_repos = “”}

这些额外的标志控制重复数据删除的执行方式:

  • use_hard_links 告知容器引擎在重复数据删除时使用硬链接。

  • ostree_repos 是一个以列分隔的OSTree存储库列表,用于查找文件。

总结

新的存储模型试图更好地利用磁盘空间并减少内存消耗。拉取操作可能会更高效,因此也会更快。要了解更多信息,请观看此演示,它展示了部分拉取如何改进Podman拉取。

原文地址:https://www.redhat.com/en/blog/faster-container-image-pulls

  

最具挑战性的挑战莫过于提升自我。——迈克尔·F·斯特利
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号