删除node modules_Node.js docker 优化实践
最近项目上 k8s,用 jenkins 做 CI,跑得比较顺了,开始考虑优化。主要有两个问题:docker build 耗时长。我们把 docker build 单独放在一个 stage, 所以很容易从 Jenkins Stage View 看出这个 stage 最耗时构建后的 image 很大,超过 1G接下来谈谈我们是怎么优化构建速度想要优化 docker build,我们就需要知道构建过程每
最近项目上 k8s,用 jenkins 做 CI,跑得比较顺了,开始考虑优化。
主要有两个问题:
docker build
耗时长。
我们把docker build
单独放在一个stage
, 所以很容易从 Jenkins Stage View 看出这个stage
最耗时- 构建后的
image
很大,超过 1G
接下来谈谈我们是怎么优化
构建速度
想要优化 docker build
,我们就需要知道构建过程每一个步骤的耗时。经过 Google,很容易找到了方法:
计算 dockerfile 每一条 instruction 的耗时
# 1. 开启这个实验性 flag
export DOCKER_BUILDKIT=1
# 2. 添加参数 --progress plain 获得更加直观的输出结果
docker build -t YOUR_IMAGE_NAME:YOUR_TAG -f YOUR_DOCKERFILE --progress plain .
从输出结果我们发现最耗时的命令是 npm install
,不出意外
有什么办法可以优化?我们知道,复用 node_modules
缓存可大大减少 npm install
时间。我们可以从两个方向做到这一点。
Solution 1:基于历史最新的业务镜像构建
比如你的镜像名叫 example
,dockerfile
可能涨这样:
FROM node
我们可以已有的 example
镜像(因为它已经包含 node_modules
)作为基础镜像:
FROM example
那第一次构建时,example
并不存在怎么办?我们需要一点点脚本来做判断:
YOUR_IMAGE_NAME_TAG=example
# 如果 demo 存在,用 demo 做基础镜像,否则用 node
base=$(DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $YOUR_IMAGE_NAME_TAG > /dev/null 2>&1 && echo $YOUR_IMAGE_NAME_TAG || echo "node")
# 作为参数传给 docker build
docker build --build-arg base=$base ...
对应的 Dockerfile
:
# 给个默认值,坐下兼容
ARG base=node
FROM $base
这种方式可以适用于任何语言,不限于 Node.js
。但是两个点需要注意:
- 需要考虑什么时候更新
:latest
tag。
我们并不希望基础镜像是一个不稳定的版本 - 基础镜像本身不
clean
。
其实我们复用了一切,包含构建过程中产生的文件,不仅仅是node_modules
目录
Solution 2:利用 build cache
官方的 dockerfile 最佳实践将缓存和指令之间的相互作用讲得很清楚了,简单理解就是:
Dockerfile
每一条指令会产生一个新的layer
- 如果当前指令将要产生的
layer
已经在 cache 里,那就直接复用缓存,而不是重复创建layer
,指令本身也不会再次执行 - 如何判断是否在 cache 里?
特殊指令ADD
和COPY
,docker 会对文件系统发生的变更(增删改)做一次checksum
,然后去缓存中匹配这个checksum
其余指令,就是简单的比较一下指令本身有没变化,如指令RUN your_command
就是比较your_command
部分有没有变化(就是这么简单) - 如果某个指令没有命中缓存,那么后续指令也不会读取缓存,每条指令均会创建新的
layer
根据这些规则,我们可以对 Dockerfile
进行优化,最大化利用 build cache
。
比如我某个项目一开始的 Dockerfile
长这样:
FROM node:10.15.3
WORKDIR /data/server
COPY . .
# Configure apt and install packages
RUN apt-get update
# install vim
&& apt-get install -y vim
# clean up
&& apt-get autoremove -y
&& apt-get clean -y
&& rm -rf /var/lib/apt/lists/*
RUN npm install && npm run build
CMD ["./deploy/entrypoint.sh"]
因为 COPY . .
放在最前面,只要整个项目任意文件发生变更,后续的所有指令都会重新执行,几乎没有利用 build cache
。怎么改呢?
注意看代码中的注释:
FROM node:10.15.3
# 1. 将全局安装的依赖,置于最前面
# Configure apt and install packages
RUN apt-get update
# install vim
&& apt-get install -y vim
# clean up
&& apt-get autoremove -y
&& apt-get clean -y
&& rm -rf /var/lib/apt/lists/*
WORKDIR /data/server
# 2. 只有部分文件如 package.json 发生变更的时候,才重新执行 npm install
COPY package.json package-lock.json .
RUN npm install
# 复制剩余文件
COPY . .
RUN npm run build
CMD ["./deploy/entrypoint.sh"]
利用 build cache
,我们完全跳过了 npm install
,耗时接近 0!
这个方法我们可以用来优化任何耗时比较长的命令:
- 先
COPY
该命令的依赖文件,而不是所有文件 - 执行该命令
镜像大小
我们注意到,一个简单的 Node.js 的镜像大小超过 1G,出乎意料
找出是哪些部分最占空间
我们可以用 du -sh
来找出每个文件夹占用的空间大小:
# 忽略一些 du 的错误
docker run -it YOUR_IMAGE du -sh /* 2>&1 | grep -v "cannot access"
我们可以看到最占空间的是(忽略体积较小的目录):
279M /data # 项目目录 /data/server,见前面的 dockerfile
920M /usr # OS目录
Step 1:寻找更轻量的 OS 镜像
很显然,OS
占了我们 75%
以上的空间,我们需要优先减少这一块。再一次 Google,发现 alpine
是目前被广泛采用、为容器定制的轻量 OS(也有缺陷,见 reddit 讨论)
因此,我们的基础镜像改为 node:lts-alpine
FROM node:lts-alpine
alpine
不自带bash
,需要手动安装RUN apk add --no-cache bash
,不会增加多少空间
Step 2:减少 node_modules 大小
对于 /data
,我们在执行一遍 du -sh /data/server/*
,可以看出主要是 node_modules
吃硬盘:
16K ./__tests__
4.0K ./commitlint.config.js
8.0K ./jest.config.js
196K ./lib
277M ./node_modules
4.0K ./nodemon.json
336K ./package-lock.json
4.0K ./package.json
120K ./src
4.0K ./tsconfig.json
npm install
会安装 dependencies
和 devDependencies
,但是运行时我们并不需要 devDependencies
,因此,我们可以再执行一次 npm i --only=production
移除 devDependencies
:
# 复制剩余文件
COPY . .
RUN npm run build
# cleanup devDependencies
RUN npm prune --production
经过上述两个优化,
# 117.7M /data/
238.3M /data
111.7M /usr
容器大小也从 1.2G
减少为 421M
,镜像大小只有原来的 1/3!
以上。
UPDATE 2019.10.08:有评论表示优化后的体积很大,这个是和项目有关的,你不可能要求一个只包含静态资源的nginx
项目和一个Node.js
项目体积一样,每个Node.js
的依赖项也不一样,容器体积大小也不是越小越好,有时候你会愿意牺牲一些空间安装一些辅助工具,所以, 关键在于你接受什么样的 tradeoff。本文知识点很简单,结论也是稀疏平常,我写下来只是想沉淀其中的方法论。
经过我再一次对比,我发现确实还有一个吃硬盘的目录/root/.npm/
,比如上述项目,该目录占到74M
=。=
更多推荐
所有评论(0)