Action 中 Docker 镜像构建缓存问题折腾记

背景

我的项目之前托管在 Github 上面

Dockerfile 是我自己写的,用了最新的 --mount 语法,缓存效果非常好,都不需要再专门加一层用来缓存依赖,直接跟本地跑一样快。还用了 Spring 分层插件,镜像的增量也只会有新增的部分。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
FROM maven:3.9-eclipse-temurin-17-alpine AS build

WORKDIR /app

COPY pom.xml ./pom.xml

COPY src ./src

RUN --mount=type=cache,target=/root/.m2 \
    mvn package  -am -DskipTests

RUN --mount=type=cache,target=/root/.m2 \
    mkdir -p /layers && \
    cp /app/target/$(mvn help:evaluate -Dexpression=project.artifactId -q -DforceStdout)-$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout).jar  /layers/target.jar && \
    cd /layers && \
    java -Djarmode=layertools -jar /layers/target.jar extract

FROM eclipse-temurin:17-jre AS runtime

WORKDIR /app

COPY --from=build /layers/dependencies/ .
COPY --from=build /layers/snapshot-dependencies/ .
COPY --from=build /layers/spring-boot-loader/ .
COPY --from=build /layers/application/ .

EXPOSE 8080

# 执行命令
ENTRYPOINT [ "java", "org.springframework.boot.loader.launch.JarLauncher" ]

构建 docker 镜像用的 Github 官方给的 workflow,一整套下来,真的很漂亮

image.png

后来,因为一些原因,仓库迁移到自建的 Gitea 上来了,入乡随俗,也就用上了 Gitea Action,各种问题便接踵而至

问题

镜像构建缓慢

问题的表面原因显而易见,每一次都去下载了完整的 Maven 依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

Progress (5): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 95/862 kB | 850/855 kB
Progress (5): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 95/862 kB | 855 kB    
                                                                       
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/httpcomponents/core5/httpcore5/5.2.4/httpcore5-5.2.4.jar (855 kB at 663 kB/s)
#10 252.7 Downloading from central: https://repo.maven.apache.org/maven2/org/apache/httpcomponents/core5/httpcore5-h2/5.2.4/httpcore5-h2-5.2.4.jar
#10 252.7 Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 95/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 95/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 95/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 112/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 112/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.4/1.9 MB | 128/862 kB
Progress (4): 0.7/1.6 MB | 328/459 kB | 0.5/1.9 MB | 128/862 kB

这玩意下载的又慢,又多,还都是单线程下载,日志还拉个几千行,很难不让人血压升高

镜像无法重用

就是,每一次构建镜像,好像都需要从 Dockerhub 上面重新拉去基础镜像

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#7 [build 2/5] WORKDIR /app
#7 sha256:64f0f61b31342293811c4e3ccb537233fcfe562ca06fad9d8d812301a43cf66a 93B / 93B 0.0s done
#7 sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 32B / 32B 0.1s done
#7 sha256:a6bac109506e0e8259f57335eb13f11e48c16a2031d39442b3be200c45ba239a 156B / 156B 0.1s done
#7 sha256:2152833668d7f665efae9f5901512719f4b0269a34ec568bd055db5ece013406 858B / 858B 0.1s done
#7 sha256:538a095b96fac90e1a596046e0c0d4d75aa1451f8f278cdfbe5148c9ea35b5d7 2.11kB / 2.11kB done
#7 sha256:96e3299f059b933428266eb1e665628370ebb22006295cae5f33b8294545ba3d 160B / 160B 0.0s done
#7 sha256:25f66cc26682c59c201a46f3a75c16ecf546862e732efbbdccae66e8aa736c71 4.24MB / 4.24MB 0.1s done
#7 sha256:a531e50593c7123d3ba2c82b61f41d8c5a32b3d4cd17045cf97b5bf64a71ecfe 9.17MB / 9.17MB 0.2s
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 23.07MB / 144.39MB 0.2s
#7 sha256:591c34627c847892ee87160ccb0488eb4484039dd04d400e9e86561f15459495 14.03MB / 14.03MB 0.2s
#7 sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 3.62MB / 3.62MB 0.2s
#7 sha256:a531e50593c7123d3ba2c82b61f41d8c5a32b3d4cd17045cf97b5bf64a71ecfe 9.17MB / 9.17MB 0.3s done
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 50.33MB / 144.39MB 0.3s
#7 sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 3.62MB / 3.62MB 0.3s done
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 105.91MB / 144.39MB 0.6s
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 132.12MB / 144.39MB 0.8s
#7 sha256:591c34627c847892ee87160ccb0488eb4484039dd04d400e9e86561f15459495 14.03MB / 14.03MB 0.6s done
#7 extracting sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 144.39MB / 144.39MB 0.9s
#7 extracting sha256:43c4264eed91be63b206e17d93e75256a6097070ce643c5e8f0379998b44f170 0.1s done
#7 extracting sha256:591c34627c847892ee87160ccb0488eb4484039dd04d400e9e86561f15459495
#7 sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 144.39MB / 144.39MB 3.2s done
#7 extracting sha256:591c34627c847892ee87160ccb0488eb4484039dd04d400e9e86561f15459495 2.5s done
#7 extracting sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5
#7 extracting sha256:68a797ec6e8389f8017685a5168b0bcd0ebec6f42e3bfb325a3f260d56afc8b5 1.9s done
#7 DONE 5.7s

分析

显而易见,是缓存的问题,原先在Github(其实也有问题,只不过网速快,感觉不到)本地跑的好好的,怎么到自己平台上面跑就这么多问题。

前者很明显是我的 —mount 缓存没有生效,于是开始查文档

Docker buildx 参数分析

找到 这篇文档,再看到我的 workflow

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
      - name: Build and push Docker image
        id: build-and-push
        uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
        with:
          context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

注意到cache-from: type=gha cache-to: type=gha,mode=max这两个参数,好像是和 action 的 cache server 有关的,可能我是 Gitea 所以不支持吧,所以尝试换了其他的参数 inlinelocal 没有啥意义,registry 试了用 Gitea Registry,不行(回看时,觉得可能是 这个参数的问题

Gitea Action Cache Server 配置

既然改配置不行,那就去看看能不能给 Gitea Action 配一个 cache server

然后是这一篇 博客,讲到了加上这个 env 就有缓存功能

1
2
3
4
5
jobs:
  build:
    env:
      RUNNER_TOOL_CACHE: /toolcache
...

不过可惜,这个是 clone action script 和其他的缓存,不是我要的 cache server

最后,仔细看,发现博客引用了这个 链接

Configuring cache when starting a Runner using docker image

哦哦哦,原来我的 act_runner 在 docker 里面跑的,要端口映射并且加配置,workflow 才能找到 cache server

于是,爽快的配置好,然后,问题依旧。。。

检索 issue

这个时候就感觉,可能是 —mount 的问题吧,因为前面的镜像都提示有缓存的,但就是每一次到了mvn package的时候,就去下载依赖了。

其实,有一个解决方法,就是用原来的方式,把依赖作为一个层放进去,但是我就是不想那样做✊

我相信,这个问题,不只有我一个人遇到,于是去检索 issue

找到了 这个

似乎是官方不是很好解决,但是下面有人指路到 另一个项目buildkit-cache-dance

看了看,好像就是为了解决 —mount的缓存而写的。简单来说,就是手动的把—mount的 cache 保存到主机,然后再用action/cache 缓存到 cache server

检查 Gitea Action cache server

又是爽快的配置好,又是问题依旧

找来其他人仓库的 workflow 仔细对比了,确信写的没问题,便怀疑是 cache server 的问题

简单来说,检查了,没问题

检查 Workflow

这个时候,我才上 GitHub 原来的仓库去看,发现其实原来的仓库也有相似的问题

所以,问题就只有可能出现在 GitHub 给的 workflow 上了

我对着屏幕,睁大眼睛,一行一行的看

视线最终停留在 docker/setup-buildx-action 这个 action 上面

真相大白

好了,不卖关子了

我们知道,现代化的 Docker 是有很多个模块组成的,容器的构建部分主要是由 buildx 负责的。我们平常使用的docker build 命令,实际上是用的 docker 自带的 buildx 进行构建镜像操作的。但 buildx 其实可以独立工作,独立完成拉取基础镜像,构建,推送等工作。

docker/setup-buildx-action 这个 action,就是创建一个独立的 buildx,并且把当前的 docker cli 的构建引擎设置为新创建的

因此,容器的构建工作都会在一个独立的容器里面完成

1
2
3
4
5
6
7
root@ubuntu-42:~/gitea# docker ps
CONTAINER ID   IMAGE                               COMMAND                  NAMES
8f5ab8cd820a   moby/buildkit:buildx-stable-1       "buildkitd --allow-i…"   buildx_buildkit_builder-e4308f9d-5be1-4724-aebf-5a42bd36ecf50
e73ffb9ff9ee   gitea/runner-images:ubuntu-latest   "/bin/sleep 10800"       GITEA-ACTIONS-TASK-160_WORKFLOW-Java-CI-with-Maven_JOB-build
48b9f06f0cc3   moby/buildkit:buildx-stable-1       "buildkitd --allow-i…"   buildx_buildkit_builder-4d03a86d-a95d-4021-8775-97153798d54f0
208ebdade2df   gitea/act_runner:nightly            "/sbin/tini -- /opt/…"   gitea-runner-1
fd636dc949b0   moby/buildkit:buildx-stable-1       "buildkitd --allow-i…"   buildx_buildkit_builder-f0901d6f-19eb-4ec3-af8a-4b6d4f08e3240

容器构建都在独立容器里面了,缓存自然就不好搞了。这也解释了上面所说的,每一次构建都要重新拉取镜像的问题

虽然 issue1 issue2 里面有提到,buildx 的数据实际上是在一个命名的卷里面的,没有那么容易丢失。但是管理起来对我来说还是太复杂了

解决方案

docker/setup-buildx-action 删掉

对,就是把这个 action 删掉,让容器使用本机的 docker 去构建,这样所有缓存就都留在同一个地方,最后效果非常好,即使需要重新编译,但是整个 workflow 也可以在半分钟内完成

image.png

总结

折腾了一晚上。

暴露出来很多的问题。一开始参照就没有选好,GitHub 上也有我遇到的问题,只不过我都选择性忽视了,然后在 gitea 上硬去解决。还有那个缓存的,action/cache这个 action,在整 buildkit-cache-dance 的时候硬整上去了,原理都不懂,也折腾了好久。

不过,这也就是学习的过程吧,从一个点再扩展到其他点,最后形成网。

折腾的不是很爽,最后还是靠降级实现目的的,不够优雅。不过也能用就行。

在 k8s 上跑 runner 集群这事,以后再说吧。

Licensed under CC BY-NC-SA 4.0