GitHub Action“gha”Docker 缓存比重新创建映像慢得多

GitHub Action `gha` Docker cache much slower than recreating the image

提问人:Yuval 提问时间:11/16/2023 最后编辑:Yuval 更新时间:11/16/2023 访问量:21

问:

我设置了一个 GitHub 操作来在我的 Docker 映像上构建和运行测试。 在构建 Docker 映像时,正在下载一个相当重的文件(来自 Huggingface 的 6Gb 模型)。(顺便说一句 - 我可能会将其降低到 2Gb,因为出于愚蠢的原因,权重被下载了三次。 我以为我可以通过使用缓存来加快速度。缓存可以工作,但速度要慢得多。gha

这是我设置的要点

非缓存 GitHub 操作

      - name: Build Image
        shell: bash
        run: |
          docker buildx build -t ${IMAGE_TAG} -f ./Dockerfile .

耗时 3 分 23 秒

缓存 GitHub 操作

      - name: Build Image
        uses: docker/build-push-action@v5
        with:
          push: false  # do not push to remote registry
          load: true  # keep in local Docker registry
          context: .
          file: ./Dockerfile
          tags: ${{env.IMAGE_TAG}}
          cache-from: type=gha
          cache-to: type=gha,mode=max

需要 7 分 58 秒(使用新设置执行首次提交时的初始构建时间为 12 分 53 秒)。

从 huggingface 下载 6Gb 大约需要 30 秒。从 GitHub 下载 4Gb 映像本身需要 279 秒。

这是 GitHub 的问题吗?有什么办法可以绕过它吗?

这可能与这个问题有关。

编辑:显然我不是唯一一个在Docker的GitHub上遭受这个问题的人

docker 性能 缓存 github-actions docker-layer

评论


答:

1赞 VonC 11/21/2023 #1

从您提到的问题来看,您似乎需要等待 docker buildx 版本来解决此问题。

检查您是否使用的是 buildx v0.12.0,这可能会有所帮助。

与此同时(等待新的 buildx 版本),您需要避免使用该选项,因为它会导致速度显着变慢。
如果 GitHub Container Registry 提供更好的性能,请考虑将其用于某些操作。
--load


您能否建议在不使用默认驱动程序的情况下实现任何其他方法?docker-container--load

您可以考虑使用本地注册表进行缓存:该方法涉及在一个作业中将构建的映像推送到本地注册表,并在后续作业中从那里拉取它。这比使用 GitHub 的缓存系统更快,并避免了 .--load

+------------------------+     +---------------------------+     +------------------------+
|  Start Local Registry  |     | Build and Push to Local   |     | Pull from Local        |
|                        |     | Registry                  |     | Registry in Subsequent |
|  docker run registry   |     |  docker build & push      |     | Job                    |
|         │              |     |         │                 |     |         │              |
|         ▼              |     |         ▼                 |     |         ▼              |
|  Local Docker Registry |     |  localhost:5000/my-image  |     | docker pull            |
+------------------------+     +---------------------------+     +------------------------+

在您的第一个作业中,启动本地 Docker 注册表容器。

- name: Start Local Registry
    run: docker run -d -p 5000:5000 --name registry registry:2

然后,您将构建 Docker 映像并将其推送到本地注册表:

- name: Build and Push to Local Registry
    run: |
    docker build -t localhost:5000/my-image .
    docker push localhost:5000/my-image

在后续作业中,从本地注册表拉取映像。

- name: Pull from Local Registry
    run: docker pull localhost:5000/my-image

另一种方法:

直接保存和加载图像。您可以将 Docker 映像另存为 tarball,并使用 GitHub 的缓存缓存 tarball,而不是使用该选项。在后续作业中,您可以从缓存中恢复 tarball 并将其加载到 Docker 中。该方法可能比使用驱动程序更有效。--loaddocker-container

+-----------------------+     +-----------------------+     +--------------------------+
|  Build and Save Image |     | Cache the Tarball     |     | Load Image in Subsequent |
|                       |     |                       |     | Job                      |
|  docker build & save  |     |  actions/cache        |     |  docker load             |
|         │             |     |         │             |     |         │                |
|         ▼             |     |         ▼             |     |         ▼                |
|  my-image.tar         |     |  Cache my-image.tar   |     |  Restore & Load Image    |
+-----------------------+     +-----------------------+     +--------------------------+

构建 Docker 映像并将其另存为 tarball。

- name: Build and Save Image
    run: |
    docker build -t my-image .
    docker save my-image > my-image.tar

使用 GitHub Actions 的缓存来存储 tarball。

- uses: actions/cache@v2
    with:
    path: my-image.tar
    key: ${{ runner.os }}-my-image-${{ hashFiles('**/Dockerfile') }}

从缓存中恢复 tarball 并将其加载到 Docker 中。

- name: Load Image from Tarball
    run: |
    if [ -f my-image.tar ]; then
        docker load < my-image.tar
    fi

似乎这也保存了一个 TAR 文件并加载它,这正是它的作用。直接使用 Github 缓存很有趣,但无论如何您都会构建映像。--load

诚然,这两种方法本质上都涉及保存和加载 TAR 文件,这在概念上类似于该选项在 Docker 中的作用。但是,主要区别在于 TAR 文件的管理方式和位置:--load

  • 本地注册表方法涉及将映像推送到在 GitHub Actions 环境中运行的本地 Docker 注册表或从中拉取映像。它通过直接与本地注册表交互来避免一些相关的 I/O 开销。--load

  • 直接 TAR 文件缓存使用 GitHub 自己的缓存机制来存储和检索 TAR 文件。虽然它仍然涉及构建映像并将其保存为 TAR 文件,但与使用 .--load

在这两种情况下,目标都是绕过与驱动程序和 相关的一些性能瓶颈,但它们仍然从根本上依赖于保存和加载 Docker 映像的类似机制。docker-container--load

评论

0赞 Yuval 11/21/2023
我需要使用缓存,并在后续的 Github 作业步骤中使用此图像。您能否建议在不使用默认驱动程序的情况下实现任何其他方法?P.S. 我在提到的问题中建议使用 GHCR 解决方案,但这可能会产生额外费用。docker-container--load
0赞 VonC 11/21/2023
@Yuval我已经编辑了答案以解决您的评论。
0赞 Yuval 11/26/2023
你试过这个吗?它是否提高了性能?似乎这也保存了一个 TAR 文件并加载它,这正是它的作用。直接使用 Github 缓存很有趣,但无论如何您都会构建映像。--load
0赞 VonC 11/26/2023
@Yuval 否:这些是供您测试的建议。
0赞 VonC 11/26/2023
@Yuval我已经编辑了答案,以解释这些方法背后的原因。
0赞 Mohamd Imran 11/27/2023 #2

在使用 Docker 缓存的 GitHub Actions 中遇到的速度变慢可能是由于几个因素造成的。如果缓存大小超出 GitHub Actions 的限制,可能会减慢缓存检索过程。确保缓存大小在建议的范围内。缓存命中率低,缓存经常失效或与预期层不匹配,可能会导致构建速度变慢,因为 Docker 必须获取更多数据。定期清理 Docker 映像中的旧层或不必要的层,以减小缓存大小并缩短检索时间。尝试并行化生成步骤,看看这是否能缩短整体生成时间。GitHub Actions 支持并行作业。或者尝试不同的缓存策略,例如热缓存方法,即定期按计划的方式更新缓存。

实现缓存的另一种方法:

如果要使用缓存并在后续 GitHub 作业步骤之间共享 Docker 映像,而不依赖于默认驱动程序,可以考虑使用 GitHub Container Registry 来存储和检索 Docker 映像。您可以构建并推送到 GitHub 容器注册表:docker-container--load

第一生成 Docker 映像。使用版本或唯一标识符标记映像。将这些映像推送到 GitHub Container Registry。

例如:

- name: Build and Push Image
  run: |
    docker build -t ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }} .
    echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
    docker push ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}

构建并推送后,在后续步骤中使用缓存的镜像,直接从 GitHub Container Registry 使用缓存的 Docker 镜像,而不是依赖本地缓存。

例如:

- name: Use Cached Image
  run: |
    docker pull ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
    # Continue with subsequent steps using the pulled image

这样,您就可以利用 GitHub Container Registry 跨作业存储和检索 Docker 映像,从而确保一致性和可重现性。希望我已经回答了您的问题,并且 Clearfield 是解决缓存问题的好方法