在 GitHub Actions 中缓存 Docker 构建:哪种方法最快?🤔 一项研究。

2025-06-04

在 GitHub Actions 中缓存 Docker 构建:哪种方法最快?🤔 一项研究。

摘要:在本文中,我尝试了 6 种不同的方法在 GitHub Actions 中缓存 Docker 构建,以加快构建过程,并比较了它们的结果。在对每种方法各尝试 10 次后,结果表明,使用 GitHub Packages 的 Docker 镜像仓库作为构建缓存(而非 GitHub Actions 的内置缓存)可获得最高的性能提升。

⚠️警告:本文自 2020 年 4 月发布以来未更新。此处概述的方法可能已过时。以下是有关此问题的更多更新观点:

背景和动机💬

厌倦了等待 GitHub Actions 上的构建。😖

与 Jenkins 等自托管运行器不同,大多数云托管构建运行器都是无状态的,每次运行都为我们提供一个原始的环境。我们无法保留之前运行的文件;任何需要持久化的内容都必须外部化。

GitHub Actions 内置了缓存功能,可以帮助实现这一点。但是创建缓存的方法有很多(我首先想到的docker save也是docker load有很多)。性能提升是否会抵消保存和加载缓存带来的开销?除了使用 GitHub Action 的内置缓存之外,还有其他方法吗?这正是本研究的目的所在。

考虑的方法💡

为了进行这个实验,我首先想出了一些关于如何缓存构建的 Docker 镜像的想法。我总共尝试了 8 种方法。

  1. 没有缓存🤷‍♂️

这是基线。

  1. 无缓存 (BuildKit)🤷‍♀️

这是另一个基线,但使用了BuildKit

  1. 使用docker save📦docker loadactions/cache

创建镜像后,我们可以使用docker save将镜像导出到 tarball 并使用 进行缓存actions/cache。在后续运行时,我们可以使用docker load从缓存的 tarball 中导入镜像。然后,我们可以使用--cache-from标志来构建镜像。

请注意,此方法与 BuildKit 不兼容,因为它仅支持位于注册表上的外部缓存(不支持拉取/导入的图像)。

  1. actions/cache使用🤯缓存 /var/lib/docker

也许我们不需要做所有的导入导出工作,只需缓存整个/var/lib/docker目录即可!

  1. actions/cache使用带有🐳的本地注册表

我们可以在本地主机上运行一个基于文件系统的 Docker 镜像仓库,将构建好的镜像推送到该仓库,并使用 进行缓存actions/cache。后续运行时,我们可以从本地主机镜像仓库拉取最新的镜像,并使用 arg 参数使用拉取到的镜像--cache-from

  1. 使用 (BuildKit) 的本地注册表actions/cache🐋

与上面相同,但使用 BuildKit。

设置上有一个细微的区别:使用 BuildKit 我们无需运行docker pull。BuildKit已经预设缓存镜像位于 registry 中。虽然这看起来像是一个限制,但它也带来了性能提升:如果缓存不适用,BuildKit 将跳过镜像拉取。

  1. 使用 GitHub Packages 的 Docker 注册表作为缓存🐙

GitHub 已经提供了一个包注册表,所以我们可能根本不需要运行我们自己的文件系统支持的注册表!

  1. 使用 GitHub Packages 的 Docker 注册表作为缓存(BuildKit) 🦑

与上面相同,但使用 BuildKit。

失败的方法💥

  • 方法🤯,使用 缓存 /var/lib/docker失败actions/cache因为生成缓存时出现大量权限错误。即使使用 ,也sudo chmod可以创建正确的缓存。
  • 方法 🦑:使用 GitHub Packages 作为缓存 (BuildKit),失败了,因为截至撰写本文时,GitHub Package Registry 不支持请求缓存清单。后续构建在尝试重新使用缓存镜像时将导致此消息:ERROR: httpReaderSeeker: failed open: could not fetch content descriptor

因此,我们剩下 4 种方法(📦、🐳、🐋、🐙),不包括 2 个基线(🤷‍♂️ 和 🤷‍♀️)。

图像设置🏛

在优化构建时,虽然我们可以减少运行时间docker build🤩,但不幸的是会引入一些开销😞。

间接时间包括:

  • 恢复已保存的缓存(方法📦🐳🐋);
  • 从缓存中加载的 tarball 导入图像(方法📦);
  • 运行本地 Docker 注册表(方法🐳🐋);
  • 从 Docker 注册表中提取 (方法🐳 , 🐋 , 🐙 );
  • 将构建的图像导出到 tarball(方法📦);
  • 将镜像推送到注册表(方法🐳🐋🐙);以及
  • 保存缓存(方法📦🐳🐋)。

根据我的经验,

  • 构建时间主要随着项目复杂性而增加,即构建的 CPU 时间。
  • 开销时间主要随着缓存内容的大小而增加,即序列化和传输内容的 IO 时间。

对于这个实验,我们希望有一个足够大的图像,以便构建时间和开销在我们的测量中变得明显。

这导致这个 Dockerfile 生成了一个有点臃肿的图像🤪:

FROM node:12
RUN yarn create react-app my-react-app
RUN cd my-react-app && yarn build
RUN npm install -g @vue/cli && (yes | vue create my-vue-app --default)
RUN cd my-vue-app && yarn build
RUN mkdir -p my-tests && cd my-tests && yarn add playwright
Enter fullscreen mode Exit fullscreen mode

方法论🔬

我创建了一个 GitHub Actions 工作流来运行所有方法。我运行了 20 次。

  • 10次​​,每次修改Dockerfile,都会使缓存失效(actions/cache)。这是缓存MISS的场景。
  • 执行 10 次,每次都保持 Dockerfile 不变。这就是缓存命中的场景。

之后,将对每种方法和场景的结果进行平均。

但也有一些例外:

  • 基线没有缓存。因此所有 20 次构建都算作缓存 MISS。
  • 方法🐙使用外部注册表,因此镜像会逐层缓存和失效。由于我设置此实验的方式,所有 20 次构建最终都命中了缓存。我懒得再进行 10 次构建,所以我假设缓存未命中的情况与无缓存 + 首次推送镜像所需的时间相同。

结果📊

所有构建运行完成后,我使用GitHub Actions API检索每个步骤的时间。我将结果输入 Google Sheets,运行了一些数据透视表,并汇总了结果。

🎖 摊牌

图表

绿色条表示缓存命中 (HIT) 场景下所花费的时间。红色条表示缓存未命中 (MISS) 场景下所花费的额外时间。

🤷‍♂️ 基线 🤷‍♀️

图表

您可以看到,仅使用不带任何缓存的 BuildKit 就能带来一些性能提升。节省了整整一分钟。

📦 tarball

图表

缓存大小为846 MB。事实证明,这docker save非常慢。因此,将该 tarball 上传到缓存中也需要很长时间。

🐳 本地注册表

图表

缓存大小为855 MB。事实证明,docker push在 Docker 内部,对在 localhost 中运行的文件系统支持的注册表执行 比 更快。与相比docker save也是如此docker pulldocker load

🐋 本地注册表,带有 BuildKit

替代文本

缓存大小为855 MB。运行时,BuildKit 会从本地注册表中提取镜像,因此与方法🐳docker build相比,无需单独的步骤docker pull

🐙 GitHub 软件包注册表

替代文本

虽然我们无法利用 BuildKit,但使用外部注册表意味着可以逐层缓存构建。在我尝试过的所有方法中,这种方法效果最好。🏆

但是,您还会在存储库的已发布包中获得一个“build-cache”包。😂

一个更接近现实世界的例子

为了测试潜在的收益,我尝试在DEV Community 的仓库docker-compose build上运行。在没有任何缓存的情况下,构建 Web 镜像花费了9 分 5 秒。使用 GitHub Package Registry 作为缓存后,构建镜像的时间减少到了37 秒

结论

您可以在这里找到实现每种方法的代码:

谈到构建性能改进,有很多文章,每篇文章都使用不同的示例推荐不同的方法。很难看出每种方法之间有何优劣。

在研究这个问题的过程中,我还注意到尝试其他不同的方法可以发现更多新颖的方法。例如,如果我没有尝试过 Approach 🐳(本地 Docker 仓库),我就不会想到“哦!GitHub 包仓库真厉害!我能用它完全跳过 Action 的缓存吗?试试看!”,然后我就想到了 Approach 🐙。

所以,我想说,当我想用​​ Docker 设置 CI 工作流程时,我希望有这样的文章。我希望我们能有更多方法和工具的对决。

感谢阅读!

文章来源:https://dev.to/dtinth/caching-docker-builds-in-github-actions-which-approach-is-the-fastest-a-research-18ei
PREV
为什么你需要给 Firefox 一个机会
NEXT
不要担心成为一名程序员需要多长时间!