分级构建
的特性来解决这一问题 。
首先,我们可以在完整版镜像下进行依赖安装,并给该任务设立一个别名(此处为build
) 。
# 安装完整依赖并构建产物FROM node:14 AS buildWORKDIR /appCOPY package*.json /app/RUN ["npm", "install"]COPY . /app/RUN npm run build
之后我们可以启用另一个镜像任务来运行生产环境,生产的基础镜像就可以换成 alpine 版本了 。其中编译完成后的源码可以通过--from
参数获取到处于build
任务中的文件,移动到此任务内 。
FROM node:14-alpine AS releaseWORKDIR /releaseCOPY package*.json /RUN ["npm", "install", "--registry=http://r.tnpm.oa.com", "--production"]# 移入依赖与源码COPY public /release/publicCOPY --from=build /app/dist /release/dist# 启动服务EXPOSE 8000CMD ["node", "./dist/index.js"]
Docker 镜像的生成规则是,生成镜像的结果仅以最后一个镜像任务为准 。因此前面的任务并不会占用最终镜像的体积,从而完美解决这一问题 。
当然 , 随着项目越来越复杂,在运行时仍可能会遇到工具库报错,如果曝出问题的工具库所需依赖不多,我们可以自行补充所需的依赖 , 这样的镜像体积仍然能保持较小的水平 。
其中最常见的问题就是对node-gyp
与node-sass
库的引用 。由于这个库是用来将其他语言编写的模块转译为 node 模块,因此,我们需要手动增加g++ make python
这三个依赖 。
# 安装生产环境依赖(为兼容 node-gyp 所需环境需要对 alpine 进行改造)FROM node:14-alpine AS dependenciesRUN apk add --no-cache python make g++COPY package*.json /RUN ["npm", "install", "--registry=http://r.tnpm.oa.com", "--production"]RUN apk del .gyp
详情可见:https://github.com/nodejs/docker-node/issues/282合理规划 Docker Layer构建速度优化我们知道,Docker 使用 Layer 概念来创建与组织镜像,Dockerfile 的每条指令都会产生一个新的文件层,每层都包含执行命令前后的状态之间镜像的文件系统更改 , 文件层越多,镜像体积就越大 。而 Docker 使用缓存方式实现了构建速度的提升 。若 Dockerfile 中某层的语句及依赖未更改,则该层重建时可以直接复用本地缓存 。
如下所示,如果 log 中出现
Using cache
字样时,说明缓存生效了,该层将不会执行运算,直接拿原缓存作为该层的输出结果 。Step 2/3 : npm install ---> Using cache ---> efvbf79sd1eb
通过研究 Docker 缓存算法,发现在 Docker 构建过程中 , 如果某层无法应用缓存,则依赖此步的后续层都不能从缓存加载 。例如下面这个例子:COPY . .RUN npm install
此时如果我们更改了仓库的任意一个文件,此时因为npm install
层的上层依赖变更了,哪怕依赖没有进行任何变动,缓存也不会被复用 。因此,若想尽可能的利用上
npm install
层缓存 , 我们可以把 Dockerfile 改成这样:COPY package*.json .RUN npm installCOPY src .
这样在仅变更源码时,node_modules
的依赖缓存仍然能被利用上了 。由此,我们得到了优化原则:
- 最小化处理变更文件,仅变更下一步所需的文件,以尽可能减少构建过程中的缓存失效 。
- 对于处理文件变更的 ADD 命令、COPY 命令 , 尽量延迟执行 。
- Docker 是以层为单位上传镜像仓库的,这样也能最大化的利用缓存的能力 。因此,执行结果很少变化的命令需要抽出来单独成层,如上面提到的
npm install
的例子里 , 也用到了这方面的思想 。
- 如果镜像层数越少,总上传体积就越小 。因此,在命令处于执行链尾部,即不会对其他层缓存产生影响的情况下,尽量合并命令,从而减少缓存体积 。例如,设置环境变量和清理无用文件的指令 , 它们的输出都是不会被使用的,因此可以将这些命令合并为一行 RUN 命令 。
RUN set ENV=prod && rm -rf ./trash
- Docker cache 的下载也是通过层缓存的方式,因此为了减少镜像的传输下载时间,我们最好使用固定的物理机器来进行构建 。例如在流水线中指定专用宿主机 , 能是的镜像的准备时间大大减少 。
推荐阅读
- 记一次 .NET 某企业OA后端服务 卡死分析
- 配置DNS域名解析服务
- CRESDA 陆地观测卫星数据服务订单ftp地址错误—已解决不能下载问题
- Docker | 数据持久化与数据共享
- Docker | Compose创建mysql容器
- 糟了,线上服务出现OOM了
- Docker 部署Kibana
- Docker | 容器数据卷详解
- 使用docker-compose安装Prometheus
- Docker | 部署nginx服务