分阶段构建如何缓存第三方依赖

技术

非分阶段构建场景下,使用容器进行构建时,我们可以将容器中的缓存目录挂载到构建主机上,执行构建任务;然后将产物拷贝到运行镜像,制作应用镜像。但是在分阶段构建时,构建镜像和运行镜像在同一个 Dockerfile 中,这给优化第三方依赖的缓存带来了难度。

  1. 创建一个 Vue 实例项目

  • 安装 Vue CLI

        
          
npm install -g @vue/cli --force  

      
  • 初始化示例项目

        
          
vue create hello-world  

      

使用默认配置,创建示例项目: hello-world

  • 运行项目

此时,项目已经包含全部依赖,可以直接运行项目:


        
          
npm run serve  

      
  • 删除依赖

依赖包通常不会提交到代码仓库,为了更好模拟构建情形,这里删除依赖,进行构建


        
          
rm -rf node_modules  

      
  • 项目中添加 Dockerfile 文件

进入项目目录:


        
          
cd hello-world  

      

编辑并保存 Dockerfile 文件:


        
          
vim Dockerfile  
  
FROM node:lts-alpine as builder  
WORKDIR /  
COPY package.json /  
RUN npm install  
  
COPY . .  
RUN npm run build  
  
FROM nginx:alpine  
COPY --from=builder /dist/ /usr/share/nginx/html/  
EXPOSE 80  

      
  • 构建镜像

执行命令:


        
          
docker build --no-cache -t shaowenchen/hello-world:v1 -f Dockerfile .  
  
[+] Building 139.2s (13/13) FINISHED  
 => [internal] load build definition from Dockerfile                                                                                                                        2.6s  
 => => transferring dockerfile: 228B                                                                                                                                        0.2s  
 => [internal] load .dockerignore                                                                                                                                           3.4s  
 => => transferring context: 2B                                                                                                                                             0.0s  
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                             4.2s  
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                          4.3s  
 => CACHED [builder 1/6] FROM docker.io/library/node:lts-alpine@sha256:2c6c59cf4d34d4f937ddfcf33bab9d8bbad8658d1b9de7b97622566a52167f2b                                     0.0s  
 => [internal] load build context                                                                                                                                           1.8s  
 => => transferring context: 5.03kB                                                                                                                                         0.4s  
 => CACHED [stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:da9c94bec1da829ebd52431a84502ec471c8e548ffb2cedbf36260fd9bd1d4d3                                        0.0s  
 => [builder 2/6] COPY package.json /                                                                                                                                       5.3s  
 => [builder 3/6] RUN npm install                                                                                                                                          93.1s  
 => [builder 4/6] COPY . .                                                                                                                                                  5.9s  
 => [builder 5/6] RUN npm run build                                                                                                                                        13.6s  
 => [stage-1 2/2] COPY --from=builder /dist/ /usr/share/nginx/html/                                                                                                         4.0s  
 => exporting to image                                                                                                                                                      4.0s  
 => => exporting layers                                                                                                                                                     2.3s  
 => => writing image sha256:dc0f72b655eb95235b51d8fb30c430c3c1803c2d538d9948941f3e7afd23ab56                                                                                0.2s  
 => => naming to docker.io/shaowenchen/hello-world:v1                                                                                                                       0.2s  

      
  • 测试镜像

执行命令,创建容器:


        
          
docker run --rm -it -p 80:80 shaowenchen/hello-world:v1  

      
  • 访问服务

在本地打开: http://localhost, 可以看到页面

picture.image

  1. 利用 Buildkit 挂载缓存优化

这种方式的思路是,将第三方包单独存储在一个缓存镜像中,当构建应用镜像时,将缓存镜像中的文件挂载到构建环境中。

2.1 开启 Buildkit

Buildkit 默认是关闭的。有两种方式打开 Buildkit:

  • 第一种,在 /etc/docker/daemon.json 中增加 buildkit 配置,{ "features": { "buildkit": true }} 默认开启 buildkit 特性。
  • 第二种,每次执行 docker 命令时,加上环境变量 DOCKER_BUILDKIT=1

2.2 使用 Bind 的方式挂载缓存

  • 准备缓存镜像的 Dockerfile

创建 Dockerfile 文件:


        
          
vim Dockerfile-Cache  
  
FROM node:lts-alpine as builder  
WORKDIR /  
COPY . .  
RUN npm install  
RUN npm run build  

      

这里有一个小细节就是,需要 npm run build编译第三方包。仅仅缓存第三方包,是不能获得很好的加速效果的。同时,预编译能减少 CPU 和内存的消耗。

  • 编译包含第三方包的缓存镜像

        
          
docker build --no-cache -t shaowenchen/hello-world:cache -f Dockerfile-Cache .  
  
[+] Building 111.9s (9/9) FINISHED  
 => [internal] load build definition from Dockerfile-Cache                                                                                                                  1.8s  
 => => transferring dockerfile: 132B                                                                                                                                        0.0s  
 => [internal] load .dockerignore                                                                                                                                           2.9s  
 => => transferring context: 2B                                                                                                                                             0.0s  
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                          4.2s  
 => [internal] load build context                                                                                                                                           1.7s  
 => => transferring context: 4.57kB                                                                                                                                         0.2s  
 => CACHED [1/5] FROM docker.io/library/node:lts-alpine@sha256:2c6c59cf4d34d4f937ddfcf33bab9d8bbad8658d1b9de7b97622566a52167f2b                                             0.0s  
 => [2/5] COPY . .                                                                                                                                                          3.6s  
 => [3/5] RUN npm install                                                                                                                                                  69.2s  
 => [4/5] RUN npm run build                                                                                                                                                14.5s  
 => exporting to image                                                                                                                                                     13.9s  
 => => exporting layers                                                                                                                                                    13.0s  
 => => writing image sha256:e6ba7406f5d0c33d446ecc9a3c8e35fa593176ec9dedd899d39a1c00a14a5179                                                                                0.2s  
 => => naming to docker.io/shaowenchen/hello-world:cache                                                                                                                    0.2s  

      
  • 准备应用的构建 Dockerfile 文件

        
          
vim Dockerfile-Bind  
  
FROM node:lts-alpine as builder  
WORKDIR /  
COPY . .  
RUN --mount=type=bind,from=shaowenchen/hello-world:cache,source=/node_modules,target=/node_modules \  
--mount=type=bind,from=shaowenchen/hello-world:cache,source=/root/.npm,target=/root/.npm npm install  
RUN --mount=type=bind,from=shaowenchen/hello-world:cache,source=/node_modules,target=/node_modules \  
--mount=type=bind,from=shaowenchen/hello-world:cache,source=/root/.npm,target=/root/.npm npm run build  
  
FROM nginx:alpine  
COPY --from=builder /dist/ /usr/share/nginx/html/  
EXPOSE 80  

      

在每个使用缓存的命令前面都需要加 --mount

  • 编译应用镜像镜像

        
          
docker build --no-cache -t shaowenchen/hello-world:v1-bind -f Dockerfile-Bind .  
  
[+] Building 55.3s (13/13) FINISHED  
 => [internal] load build definition from Dockerfile-Bind                                                                                                                   2.5s  
 => => transferring dockerfile: 42B                                                                                                                                         0.0s  
 => [internal] load .dockerignore                                                                                                                                           3.4s  
 => => transferring context: 2B                                                                                                                                             0.0s  
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                             4.0s  
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                          3.8s  
 => [internal] load build context                                                                                                                                           2.4s  
 => => transferring context: 4.47kB                                                                                                                                         0.2s  
 => CACHED FROM docker.io/shaowenchen/hello-world:cache                                                                                                                     0.3s  
 => CACHED [stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:da9c94bec1da829ebd52431a84502ec471c8e548ffb2cedbf36260fd9bd1d4d3                                        0.0s  
 => CACHED [builder 1/5] FROM docker.io/library/node:lts-alpine@sha256:2c6c59cf4d34d4f937ddfcf33bab9d8bbad8658d1b9de7b97622566a52167f2b                                     0.0s  
 => [builder 2/5] COPY . .                                                                                                                                                  4.2s  
 => [builder 3/5] RUN --mount=type=bind,from=shaowenchen/hello-world:cache,source=/node_modules,target=/node_modules --mount=type=bind,from=shaowenchen/hello-world:cache  16.8s  
 => [builder 4/5] RUN --mount=type=bind,from=shaowenchen/hello-world:cache,source=/node_modules,target=/node_modules --mount=type=bind,from=shaowenchen/hello-world:cache  13.2s  
 => [stage-1 2/2] COPY --from=builder /dist/ /usr/share/nginx/html/                                                                                                         3.7s  
 => exporting to image                                                                                                                                                      4.8s  
 => => exporting layers                                                                                                                                                     3.1s  
 => => writing image sha256:de18663c5752a41cd61c23fb2cbbc1ac9c4c79cf5fdbe15ca16e806d0ce18d9d                                                                                0.2s  
 => => naming to docker.io/shaowenchen/hello-world:v1-bind                                                                                                                  0.1s  

      

可以看到,加缓存之后,执行 install 和 build 总时长从 100 多秒降到了不到 30 秒。

  1. 利用 S3 存储缓存优化

3.1 快速部署一个 minio

参考文件: Jenkins 中的构建产物与缓存, https://www.chenshaowen.com/blog/artifacts-and-cache-in-jenkins.html

3.2 配置秘钥

在 hello-world 目录下创建凭证文件 .s3cfg


        
          
host_base = 1.1.1.1:9000  
host_bucket = 1.1.1.1:9000  
use_https = False  
  
access_key =  minio  
secret_key = minio123  
  
signature_v2 = False  

      

3.3 改造 Dockerfile 适配 S3 缓存

这里主要的工作点在:

  1. 安装 s3cmd
  2. 获取并解压缓存,忽略错误(第一次为空)
  3. ... 安装依赖,进行构建
  4. 压缩并上传缓存

        
          
vim Dockerfile-S3  
  
FROM node:lts-alpine as builder  
  
ARG BUCKETNAME  
ENV BUCKETNAME=$BUCKETNAME  
  
RUN apk add python3 && ln -sf python3 /usr/bin/python && apk add py3-pip  
RUN wget https://sourceforge.net/projects/s3tools/files/s3cmd/2.2.0/s3cmd-2.2.0.tar.gz \  
    && mkdir -p /usr/local/s3cmd && tar -zxf s3cmd-2.2.0.tar.gz -C /usr/local/s3cmd \  
    && ln -s /usr/local/s3cmd/s3cmd-2.2.0/s3cmd /usr/bin/s3cmd && pip3 install python-dateutil  
WORKDIR /  
  
# Get Cache  
COPY .s3cfg /root/  
RUN s3cmd get s3://$BUCKETNAME/node_modules.tar.gz && tar xf node_modules.tar.gz || exit 0  
RUN s3cmd get s3://$BUCKETNAME/npm.tar.gz && tar xf npm.tar.gz || exit 0  
COPY . .  
RUN npm install  
RUN npm run build  
  
# Uploda Cache  
RUN s3cmd del s3://$BUCKETNAME/node_modules.tar.gz || exit 0  
RUN s3cmd del s3://$BUCKETNAME/npm.tar.gz || exit 0  
RUN tar cvfz node_modules.tar.gz node_modules  
RUN tar cvfz npm.tar.gz ~/.npm  
RUN s3cmd put node_modules.tar.gz s3://$BUCKETNAME/  
RUN s3cmd put npm.tar.gz s3://$BUCKETNAME/  
  
FROM nginx:alpine  
COPY --from=builder /dist/ /usr/share/nginx/html/  
EXPOSE 80  

      
  • 首次使用 S3 缓存构建应用镜像

构建之前,需要提前创建一个名为 hello-world 的 Bucket。


        
          
docker build --no-cache --build-arg BUCKETNAME="hello-world" -t shaowenchen/hello-world:v1-s3 -f Dockerfile-S3 .  
  
[+] Building 244.7s (23/23) FINISHED  
 => [internal] load build definition from Dockerfile-S3                                                                                                                     1.7s  
 => => transferring dockerfile: 40B                                                                                                                                         0.1s  
 => [internal] load .dockerignore                                                                                                                                           2.6s  
 => => transferring context: 2B                                                                                                                                             0.1s  
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                             2.6s  
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                          0.0s  
 => CACHED [builder  1/16] FROM docker.io/library/node:lts-alpine                                                                                                           0.0s  
 => [internal] load build context                                                                                                                                           2.2s  
 => => transferring context: 4.53kB                                                                                                                                         0.1s  
 => CACHED [stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:da9c94bec1da829ebd52431a84502ec471c8e548ffb2cedbf36260fd9bd1d4d3                                        0.0s  
 => [builder  2/16] RUN apk add python3 && ln -sf python3 /usr/bin/python && apk add py3-pip                                                                               32.3s  
 => [builder  3/16] RUN wget https://sourceforge.net/projects/s3tools/files/s3cmd/2.2.0/s3cmd-2.2.0.tar.gz     && mkdir -p /usr/local/s3cmd && tar -zxf s3cmd-2.2.0.tar.g  12.8s  
 => [builder  4/16] COPY .s3cfg /root/                                                                                                                                      5.8s  
 => [builder  5/16] RUN s3cmd get s3://hello-world/node_modules.tar.gz && tar xf node_modules.tar.gz || exit 0                                                              6.7s  
 => [builder  6/16] RUN s3cmd get s3://hello-world/npm.tar.gz && tar xf npm.tar.gz || exit 0                                                                                7.3s  
 => [builder  7/16] COPY . .                                                                                                                                                5.7s  
 => [builder  8/16] RUN npm install                                                                                                                                        71.3s  
 => [builder  9/16] RUN npm run build                                                                                                                                      14.4s  
 => [builder 10/16] RUN s3cmd del s3://hello-world/node_modules.tar.gz || exit 0                                                                                            7.5s  
 => [builder 11/16] RUN s3cmd del s3://hello-world/npm.tar.gz || exit 0                                                                                                     6.9s  
 => [builder 12/16] RUN tar cvfz node_modules.tar.gz node_modules                                                                                                          11.3s  
 => [builder 13/16] RUN tar cvfz npm.tar.gz ~/.npm                                                                                                                          9.4s  
 => [builder 14/16] RUN s3cmd put node_modules.tar.gz s3://hello-world/                                                                                                    14.8s  
 => [builder 15/16] RUN s3cmd put npm.tar.gz s3://hello-world/                                                                                                             15.9s  
 => [stage-1 2/2] COPY --from=builder /dist/ /usr/share/nginx/html/                                                                                                         4.5s  
 => exporting to image                                                                                                                                                      3.9s  
 => => exporting layers                                                                                                                                                     2.5s  
 => => writing image sha256:dceead698b2c5f3980bf17f246078fe967dda2d9b009c30d9fdb0c60263146e5                                                                                0.1s  
 => => naming to docker.io/shaowenchen/hello-world:v1-s3                                                                                                                    0.2s  

      

在 Minio 的 UI 端可以看到相关的缓存文件:

picture.image

  • 再次使用 S3 缓存构建应用镜像

        
          
 docker build --no-cache --build-arg BUCKETNAME="hello-world" -t shaowenchen/hello-world:v1-s3 -f Dockerfile-S3 .  
[+] Building 213.8s (23/23) FINISHED  
 => [internal] load build definition from Dockerfile-S3                                                                                                                     2.0s  
 => => transferring dockerfile: 40B                                                                                                                                         0.0s  
 => [internal] load .dockerignore                                                                                                                                           2.7s  
 => => transferring context: 2B                                                                                                                                             0.0s  
 => [internal] load metadata for docker.io/library/nginx:alpine                                                                                                             4.6s  
 => [internal] load metadata for docker.io/library/node:lts-alpine                                                                                                          0.0s  
 => CACHED [builder  1/16] FROM docker.io/library/node:lts-alpine                                                                                                           0.0s  
 => CACHED [stage-1 1/2] FROM docker.io/library/nginx:alpine@sha256:da9c94bec1da829ebd52431a84502ec471c8e548ffb2cedbf36260fd9bd1d4d3                                        0.0s  
 => [internal] load build context                                                                                                                                           1.9s  
 => => transferring context: 4.53kB                                                                                                                                         0.1s  
 => [builder  2/16] RUN apk add python3 && ln -sf python3 /usr/bin/python && apk add py3-pip                                                                               30.9s  
 => [builder  3/16] RUN wget https://sourceforge.net/projects/s3tools/files/s3cmd/2.2.0/s3cmd-2.2.0.tar.gz     && mkdir -p /usr/local/s3cmd && tar -zxf s3cmd-2.2.0.tar.g  13.2s  
 => [builder  4/16] COPY .s3cfg /root/                                                                                                                                      5.5s  
 => [builder  5/16] RUN s3cmd get s3://hello-world/node_modules.tar.gz && tar xf node_modules.tar.gz || exit 0                                                             16.7s  
 => [builder  6/16] RUN s3cmd get s3://hello-world/npm.tar.gz && tar xf npm.tar.gz || exit 0                                                                               15.3s  
 => [builder  7/16] COPY . .                                                                                                                                                4.7s  
 => [builder  8/16] RUN npm install                                                                                                                                        18.4s  
 => [builder  9/16] RUN npm run build                                                                                                                                      13.6s  
 => [builder 10/16] RUN s3cmd del s3://hello-world/node_modules.tar.gz || exit 0                                                                                            7.4s  
 => [builder 11/16] RUN s3cmd del s3://hello-world/npm.tar.gz || exit 0                                                                                                     7.9s  
 => [builder 12/16] RUN tar cvfz node_modules.tar.gz node_modules                                                                                                          10.8s  
 => [builder 13/16] RUN tar cvfz npm.tar.gz ~/.npm                                                                                                                         10.0s  
 => [builder 14/16] RUN s3cmd put node_modules.tar.gz s3://hello-world/                                                                                                    17.9s  
 => [builder 15/16] RUN s3cmd put npm.tar.gz s3://hello-world/                                                                                                             16.3s  
 => [stage-1 2/2] COPY --from=builder /dist/ /usr/share/nginx/html/                                                                                                         5.0s  
 => exporting to image                                                                                                                                                      3.8s  
 => => exporting layers                                                                                                                                                     2.5s  
 => => writing image sha256:a9c46eef6073b3ef8e6c4cd33cc1ed11c94dcebdb0883c89283883d9434de331                                                                                0.2s  
 => => naming to docker.io/shaowenchen/hello-world:v1-s3                                                                                                                    0.2s  

      

可以看到,install 和 build 命令大约需要 80 秒,但是 S3 缓存相关的操作占用了大约 50 秒。

其中的 80 秒还可以优化的地方是,构建环境和 S3 服务之间网络限速为 1.2 MB/S 导致拉取和推送占用时间过长,就有较大优化空间。我认为在 30 秒以内,比较合理。

  1. 总结

缓存加速是 CI 产品的一个难点。用户使用的方式各不相同,我们能做的是针对用户的场景提供解决方案,而不能强制改变用户的使用习惯。在我之前开发的 CI 产品中,主要是将主机上的缓存挂载到构建环境中加速,无法适用分阶段构建的场景。这里主要提供了两个方案:

第一种,开启 Buildkit 特性,将第三方依赖包存储在缓存镜像。缓存镜像可以根据策略,定时进行更新。构建镜像时,挂载缓存镜像中的第三方包。

第二种,使用 S3 存储第三方依赖包,在构建时,使用 s3cmd 命令管理缓存。

以上两种方式,都不算很好。主要的原因是,它们都需要对 Dockerfile 进行修改,对业务的入侵较大。

  1. 参考

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
火山引擎大规模机器学习平台架构设计与应用实践
围绕数据加速、模型分布式训练框架建设、大规模异构集群调度、模型开发过程标准化等AI工程化实践,全面分享如何以开发者的极致体验为核心,进行机器学习平台的设计与实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论