专栏名称: 奇舞精选
《奇舞精选》是由奇舞团维护的前端技术公众号。除周五外,每天向大家推荐一篇前端相关技术文章,每周五向大家推送汇总周刊内容。
目录
相关文章推荐
51好读  ›  专栏  ›  奇舞精选

现学现用之Docker镜像构建速度优化

奇舞精选  · 公众号  ·  · 2024-12-05 18:00

正文

本文作者系360奇舞团前端开发工程师

背景

在最近临时支持的项目中,发现项目的构建流程耗时比较长,严重的影响了开发的进度。参照文档要发测试环境的时候,发现10分钟过去了还没有发布完成。项目是通过 Docker 来构建镜像部署的,所以想看看有没有什么方案,可以对 Docker 镜像构建进行优化。

现状

Dockerfile 是长这样子的:

Dockfile 文件分析

以下主要分析 Dockerfile 构建过程中主要执行的操作

一、基础镜像选择

首先定义了一个基础镜像 FROM node:20.18.1-alpine AS base ,这里选择了基于 Alpine 系统的 Node.js 版本 20.18.1 作为基础镜像。

二、依赖安装阶段(deps)

  1. 基于 base 镜像创建了 deps 镜像。

  2. 执行 RUN apk add --no - cache libc6 - compat ,这是在 Alpine 系统下安装 libc6 - compat 库, --no - cache 表示不使用缓存。

  3. package.json yarn.lock* package - lock.json* pnpm - lock.yaml* 复制到当前工作目录 (/app)

  4. 根据不同的 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)

  1. 基于 base 镜像创建 builder 镜像。

  2. deps 镜像复制 /app/node_modules 到当前工作目录下的 node_modules

  3. 复制当前目录 (.) 下的所有文件到 /app

  4. 执行 yarn build:test ,可能是使用 Yarn 构建测试版本的项目。

四、运行阶段(runner)

  1. 基于 base 镜像创建 runner 镜像。

  2. 设置环境变量 NODE_ENV为production ,表示生产环境。

  3. builder 镜像复制 /app/public 到当前工作目录下的 public

  4. builder 镜像复制 /app/.next/standalone 到当前工作目录下

  5. builder 镜像复制 /app/.app/.next/static 到当前工作目录下的 .next/static

构建镜像

通过运行 Docker build 的命令,我们可以看着在构建镜像的过程中,主要做了什么操作,每个操作耗时分别是多少:

可以看出, Docker 镜像打包过程总共花费了 614.6s ,主要耗时集中在以下几个操作上:

  1. [internal] load metadata for docker.io/library/node:20.18.1-alpine : 4.4s

  2. => [internal] load build context : 23.8s

  3. => transferring context: 712.33MB : 23.8s

  4. => [deps 1/4] RUN apk add --no-cache libc6-compat : 3.0s

  5. => [deps 4/4] RUN if [ -f yarn.lock ]; then yarn --frozen-lockfile; elif [ -f package-lock.json ]; then npm ci; eli : 258.1s

  6. => [builder 2/4] COPY --from=deps /app/node_modules ./node_modules : 35.9s

  7. => [builder 3/4] COPY . . : 4.7s

  8. => [builder 4/4] RUN yarn build:test : 234.2s

优化

之前没有太多的 Docker 镜像打包经验,都是直接 build 写好的 Dockerfile 或者是基于开源的Dockerfile进行定制化开发(复制其他项目的拿过来改一下😊),所以搜了一下看看都有哪些优化的方案。单纯从 Docker 镜像打包来看,可以从以下几个方向入手:

  1. 使用更小的基础镜像

  2. 多阶段构建 (Multi-stage Builds)

  3. 利用缓存加速构建

  4. 减少镜像层数

  5. 使用 .dockerignore 文件

  6. 分层打包 (Layered Packaging)

  7. 静态二进制文件和“临时”基础映像

因为当前的项目已经做了1、2、6,所以我们还可以从4、5、7这三个方面考虑。

优化一:减少文件复制的时间

  1. 通过添加 .dockerignore 文件,过滤掉一些非必要的文件,来减少文件复制的时间。

  2. 我们看到之前的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 下载的时间。

  1. [internal] load metadata for docker.io/library/node:20.18.1-alpine : 4.4s => 0.7s

  2. => [internal] load build context : 23.8s => 11.3s

  3. => transferring context: 712.33MB : 23.8s => 9.9s

  4. => [deps 1/4] RUN apk add --no-cache libc6-compat : 3.0s => 0s

总结

由于是临时支持了这个项目,在对项目改动不大的情况下去进行了一些尝试,而且仅针对 Docker 镜像自身构建的优化。通过这次实践来看,这个项目单纯从 Docker 方面来进行优化,效果相对来说还是不够的,减少了 70s 左右的时间。整个构建耗时的流程还是在安装依赖以及项目本身的构建上,如果要想显著的提高项目发布速度,还得从这两方面入手。







请到「今天看啥」查看全文