在 GitHub Actions 中缓存 Docker 构建:哪种方法最快?🤔 一项研究。
摘要:在本文中,我尝试了 6 种不同的方法在 GitHub Actions 中缓存 Docker 构建,以加快构建过程,并比较了它们的结果。在对每种方法各尝试 10 次后,结果表明,使用 GitHub Packages 的 Docker 镜像仓库作为构建缓存(而非 GitHub Actions 的内置缓存)可获得最高的性能提升。
⚠️警告:本文自 2020 年 4 月发布以来未更新。此处概述的方法可能已过时。以下是有关此问题的更多更新观点:
2021-07-29 Docker 官方
build-push-action
现在支持 GitHub Cache API,其中缓存直接保存到 GitHub Actions 缓存中,跳过基于本地文件系统的缓存。2021-03-21 Andy Barnov,Kirill Kuznetsov。“使用 Docker 层缓存在 GitHub Actions 上构建镜像”,Evil Martians。
背景和动机💬
厌倦了等待 GitHub Actions 上的构建。😖
与 Jenkins 等自托管运行器不同,大多数云托管构建运行器都是无状态的,每次运行都为我们提供一个原始的环境。我们无法保留之前运行的文件;任何需要持久化的内容都必须外部化。
GitHub Actions 内置了缓存功能,可以帮助实现这一点。但是创建缓存的方法有很多(我首先想到的docker save
也是docker load
有很多)。性能提升是否会抵消保存和加载缓存带来的开销?除了使用 GitHub Action 的内置缓存之外,还有其他方法吗?这正是本研究的目的所在。
考虑的方法💡
为了进行这个实验,我首先想出了一些关于如何缓存构建的 Docker 镜像的想法。我总共尝试了 8 种方法。
- 没有缓存🤷♂️
这是基线。
- 无缓存 (BuildKit)🤷♀️
这是另一个基线,但使用了BuildKit。
- 使用
docker save
和📦docker load
actions/cache
创建镜像后,我们可以使用docker save
将镜像导出到 tarball 并使用 进行缓存actions/cache
。在后续运行时,我们可以使用docker load
从缓存的 tarball 中导入镜像。然后,我们可以使用--cache-from
标志来构建镜像。
请注意,此方法与 BuildKit 不兼容,因为它仅支持位于注册表上的外部缓存(不支持拉取/导入的图像)。
actions/cache
使用🤯缓存 /var/lib/docker
也许我们不需要做所有的导入导出工作,只需缓存整个/var/lib/docker
目录即可!
actions/cache
使用带有🐳的本地注册表
我们可以在本地主机上运行一个基于文件系统的 Docker 镜像仓库,将构建好的镜像推送到该仓库,并使用 进行缓存actions/cache
。后续运行时,我们可以从本地主机镜像仓库拉取最新的镜像,并使用 arg 参数使用拉取到的镜像--cache-from
。
- 使用 (BuildKit) 的本地注册表
actions/cache
🐋
与上面相同,但使用 BuildKit。
设置上有一个细微的区别:使用 BuildKit 我们无需运行docker pull
。BuildKit已经预设缓存镜像位于 registry 中。虽然这看起来像是一个限制,但它也带来了性能提升:如果缓存不适用,BuildKit 将跳过镜像拉取。
- 使用 GitHub Packages 的 Docker 注册表作为缓存🐙
GitHub 已经提供了一个包注册表,所以我们可能根本不需要运行我们自己的文件系统支持的注册表!
- 使用 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
方法论🔬
我创建了一个 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 pull
docker 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