在 GitHub Actions 上使用多 Job 并行构建,提升 Multi-Arch 镜像制作速度
在土豆大佬的文章 「使用 GitHub Actions 构建 Multi-arch Docker image」 中我们知道,如果希望在 GitHub Actions 上构建一个 Multi-Arch 的镜像,其中关键的 Workflow 内容类似如下:
如果你不懂什么是 Multi-Arch 的话,你可以先看看上面的文章.
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push latest images
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm64, linux/amd64
push: true
tags: |
n0vad3v/bennythink:latest
这样我们就可以很方便地构建多架构镜像了,但是我们要知道一点:GitHub Actions 的机器是 Standard_DS2_v2,是一个 2 Core 7G 内存的小机器,而且甚至没法加钱换大机器,这样的话我们在构建一些比较鸡掰的包的时候就容易超时,比如…构建 CMake 可能需要 2hr…
再加上 ARM64 的部分是用 QEMU 模拟的,两个一起跑那真的是…等到跑了 6 个小时任务超时被 Kill 掉的时候…
只剩下滿腹的辛酸 無限的苦痛
为了解决这个问题(而且不花钱),我们就要充分利用 GitHub Actions 的 Jobs,我们知道,一个 Workflow 中的每个 Job 都是单独的机器在跑,所以这里的思路就从单机构建 Multi-Arch 镜像改为:
- 多个 Job 分别构建各自 Arch 的镜像,假设我们最终希望的镜像名称是
n0vad3v/bennythink:latest
,那么这里分别构建n0vad3v/bennythink-amd64:latest
和n0vad3v/bennythink-arm64:latest
- 等前构建任务完成之后利用一个单独的任务把这些镜像缝合在一起,使用
docker manifest amend
- Multi-Arch 镜像就出现了
Build Image
多个 Job 分别构建各自 Arch 的镜像
这一步非常简单,只需要多写几个 Job 就好了,类似这样:
build-arm64-image:
name: Build ARM64 Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push latest images
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm64
push: true
tags: |
n0vad3v/bennythink-arm64:latest
build-amd64-image:
name: Build AMD64 Image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWD }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Build and push latest images
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64
push: true
tags: |
n0vad3v/bennythink-amd64:latest
缝合镜像
上面两个 Job 完成后,我们应该已经得到并 push 了 n0vad3v/bennythink-amd64:latest
和 n0vad3v/bennythink-arm64:latest
两个镜像,接下来使用一个任务把这两个镜像合并到一起,并使用 needs
保证只会在前面构建的任务完成后才会运行,示例如下:
combine-two-images:
runs-on: ubuntu-latest
needs:
- build-arm64-image
- build-amd64-image
steps:
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWD }}
- name: Combine two images
run: |
docker manifest create n0vad3v/bennythink:latest --amend n0vad3v/bennythink-amd64:latest --amend n0vad3v/bennythink-arm64:latest
docker manifest push n0vad3v/bennythink:latest
然后就成了.
缓存
由于通过 QEMU 模拟的构建镜像非常的慢,所以一定要用好缓存机制(比如使用 actions/cache@v3
),不过一定要看清楚官方文档,了解清楚 key
和 restore-keys
这类参数的用法,不要直接复制(不然就会像我一样两个 Job 写成了一个 key
然后 AMD64 的缓存总是把 ARM64 的缓存给覆盖掉了)。
用 QEMU 模拟 ARM64 真的是慢到吃屎都赶不上热乎的
后记
不过即使是这样你也没法成功地在 6hr 限制内用 GitHub Actions 的官方 Runner 编译完成 ClickHouse,但是对于一些没有那么重的任务来说,这样可以提升不少速度,目前我已经在如下仓库上实践这个策略:
顺便可以感受一下 QEMU 模拟的 ARM64 和 AMD64 原生之间的构建速度差距有多大:
最终我自己的需求还是通过前两天开源的 knatnetwork/github-runner 并搭配 DOKS 和 Eks 创建了一堆 AMD64 和 ARM64 的机器注册 Self-hosted Runner 在对应平台原生构建了,后续有机会我会写另一篇文章分享。