背景
在最近临时支持的项目中,发现项目的构建流程耗时比较长,严重的影响了开发的进度。参照文档要发测试环境的时候,发现10分钟过去了还没有发布完成。项目是通过
Docker
来构建镜像部署的,所以想看看有没有什么方案,可以对
Docker
镜像构建进行优化。
现状
Dockerfile
是长这样子的:
Dockfile
文件分析
以下主要分析
Dockerfile
构建过程中主要执行的操作
一、基础镜像选择
首先定义了一个基础镜像
FROM node:20.18.1-alpine AS base
,这里选择了基于
Alpine
系统的
Node.js
版本
20.18.1
作为基础镜像。
二、依赖安装阶段(deps)
-
基于
base
镜像创建了
deps
镜像。
-
执行
RUN apk add --no - cache libc6 - compat
,这是在
Alpine
系统下安装
libc6 - compat
库,
--no - cache
表示不使用缓存。
-
将
package.json
、
yarn.lock*
、
package - lock.json*
、
pnpm - lock.yaml*
复制到当前工作目录
(/app)
。
-
根据不同的
lock
文件类型进行依赖安装:如果存在
yarn.lock
文件,执行
yarn --frozen - lockfile
,这是使用
Yarn
安装依赖并且确保使用
lock
文件中的版本,以保证可重复性。如果存在
package - lock.json
文件,执行
npm ci
,这是使用
npm
安装依赖并且确保按照
package - lock.json
中的版本精确安装。如果存在
pnpm - lock.yaml
文件,先全局安装
pnpm(yarn global add pnpm)
,然后执行
pnpm i --frozen - lockfile
,同样是按照
lock
文件安装依赖。如果没有找到任何
lock
文件,则输出
Lockfile not found.
并以错误码
1
退出。
三、构建阶段(builder)
-
基于
base
镜像创建
builder
镜像。
-
从
deps
镜像复制
/app/node_modules
到当前工作目录下的
node_modules
。
-
复制当前目录
(.)
下的所有文件到
/app
。
-
执行
yarn build:test
,可能是使用
Yarn
构建测试版本的项目。
四、运行阶段(runner)
-
基于
base
镜像创建
runner
镜像。
-
设置环境变量
NODE_ENV为production
,表示生产环境。
-
从
builder
镜像复制
/app/public
到当前工作目录下的
public
。
-
从
builder
镜像复制
/app/.next/standalone
到当前工作目录下
-
从
builder
镜像复制
/app/.app/.next/static
到当前工作目录下的
.next/static
构建镜像
通过运行
Docker build
的命令,我们可以看着在构建镜像的过程中,主要做了什么操作,每个操作耗时分别是多少:
可以看出,
Docker
镜像打包过程总共花费了
614.6s
,主要耗时集中在以下几个操作上:
-
[internal] load metadata for docker.io/library/node:20.18.1-alpine
: 4.4s
-
=> [internal] load build context
: 23.8s
-
=> transferring context: 712.33MB
: 23.8s
-
=> [deps 1/4] RUN apk add --no-cache libc6-compat
: 3.0s
-
=> [deps 4/4] RUN if [ -f yarn.lock ]; then yarn --frozen-lockfile; elif [ -f package-lock.json ]; then npm ci; eli
: 258.1s
-
=> [builder 2/4] COPY --from=deps /app/node_modules ./node_modules
: 35.9s
-
=> [builder 3/4] COPY . .
: 4.7s
-
=> [builder 4/4] RUN yarn build:test
: 234.2s
优化
之前没有太多的
Docker
镜像打包经验,都是直接
build
写好的
Dockerfile
或者是基于开源的Dockerfile进行定制化开发(复制其他项目的拿过来改一下😊),所以搜了一下看看都有哪些优化的方案。单纯从
Docker
镜像打包来看,可以从以下几个方向入手:
-
使用更小的基础镜像
-
多阶段构建
(Multi-stage Builds)
-
利用缓存加速构建
-
减少镜像层数
-
使用
.dockerignore
文件
-
分层打包
(Layered Packaging)
-
静态二进制文件和“临时”基础映像
因为当前的项目已经做了1、2、6,所以我们还可以从4、5、7这三个方面考虑。
优化一:减少文件复制的时间
-
通过添加
.dockerignore
文件,过滤掉一些非必要的文件,来减少文件复制的时间。
-
我们看到之前的Dockerfile里,有一个复制
node_modules
的操作,花费了
35.9s
,可以想办法把这个去掉。
基于以上两点,对项目文件以及
Dockerfile
进行修改.
一、添加.dockerignore文件
内容如下:
node_modules
.next
src/.DS_Store
.vscode
.husky
二、Dockerfile调整
三、构建看效果
我们可以看到,
=> [builder 3/4] COPY . .
从
4.7s
降到了
1.8s
,
=> [builder 2/4] COPY --from=deps /app/node_modules ./node_modules
这一步的耗时已经没有了。
优化二:重新构建一个新的镜像作为基础镜像
通过观察
Dockerfile
,我们发现以
node:20.18.1-alpine
镜像为基础镜像进行构建的时候,还需要安装
libc6-compat
,受网络波动的影响
libc6-compat
有时候下载比较慢。那我们可以把在
node:20.18.1-alpine
环境下,下载好
libc6-compat
单独打包成一个镜像,上传到公司内部的镜像仓库中,直接使用这个新镜像来作为基础镜像就可以了。如何构建镜像上传到公司内部镜像仓库,大家可以去看看
Docker
的教程就可以了,这里就不展开了。
一、Dockerfile调整
二、构建效果
我们可以看到通过构建一个新的镜像上传到内部镜像仓库中使用,不仅镜像下载速度变快了,还可以节省下了
=> [deps 1/4] RUN apk add --no-cache libc6-compat
下载的时间。
-
[internal] load metadata for docker.io/library/node:20.18.1-alpine
: 4.4s => 0.7s
-
=> [internal] load build context
: 23.8s => 11.3s
-
=> transferring context: 712.33MB
: 23.8s => 9.9s
-
=> [deps 1/4] RUN apk add --no-cache libc6-compat
: 3.0s => 0s
总结
由于是临时支持了这个项目,在对项目改动不大的情况下去进行了一些尝试,而且仅针对
Docker
镜像自身构建的优化。通过这次实践来看,这个项目单纯从
Docker
方面来进行优化,效果相对来说还是不够的,减少了
70s
左右的时间。整个构建耗时的流程还是在安装依赖以及项目本身的构建上,如果要想显著的提高项目发布速度,还得从这两方面入手。