node.js 应用一般用 pm2 或者 docker 来部署,如果你打算用 docker 部署的话,可能也会遇到如何选择 node 镜像的问题,官方提供了如下三类选择:
-
node:<version>:这是官方默认镜像,基于 debian 构建,可指定的版本有:- Debian 10(buster) — 当前的稳定版(stable)
- Debian 9(stretch) — 旧的稳定版(oldstable)
- Debian 8(jessie) — 更旧的稳定版(oldoldstable)
- Debian 7(wheezy) — 被淘汰的稳定版
这些镜像是基于 buildpack-deps 进行构建的,这里可以查看 Dockerfile,此类镜像的优点是安装的依赖很全,例如
curl、wget,缺点是体积过大。 -
node:<version>-slim:这是删除冗余依赖后的精简版本镜像,同样是基于 debian 构建,体积上比默认镜像小很多,删除了很多公共的软件包,只保留了最基本的 node 运行环境。 -
node:<version>-alpine:这个版本基于 alpine 镜像构建,比 debian 的 slim 版本还要小,可以说是最小的 node 镜像。虽然体积小,但是功能不少,普通的 node.js 应用都能跑起来,但是如果项目中用到了 c++ 扩展的话,就不要用这个了,因为 alpine 使用 musl 代替 glibc,一些 c/c++ 环境的软件可能不兼容。
接下来,基于 node.js 14 版本,分别下载上述三类镜像进行对比:
docker pull node:14-buster
docker pull node:14-buster-slim
docker pull node:14-alpine
镜像体积大小对比
运行 docker images | grep node:
node 14-buster 70c62b76e4cc 5 hours ago 912MB
node 14-buster-slim 9917d232c3dd 5 hours ago 181MB
node 14-alpine 9db54a688554 5 hours ago 117MB
可以看到默认镜像 node:14-buster 体积要 912MB ,实在太大了,相较而言 node:14-buster-slim 小很多,而 node:14-alpine 则更为轻巧。
容器内存占用对比
用上面的镜像分别启动容器:
docker run -d --name node-14-buster node:14-buster node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"
docker run -d --name node-14-buster-slim node:14-buster-slim node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"
docker run -d --name node-14-alpine node:14-alpine node -e "require('http').createServer((req, res) => res.end('Hello World')).listen(3030)"
运行 docker stats 查看运行时的内存占用
NAME CPU % MEM USAGE/LIMIT MEM % NET I/O BLOCK I/O PIDS
node-14-alpine 0.00% 4.809MiB/1.796GiB 0.26% 0B/0B 0B/0B 7
node-14-buster-slim 0.00% 4.238MiB/1.796GiB 0.23% 0B/0B 0B/0B 7
node-14-buster 0.00% 4.207MiB/1.796GiB 0.23% 0B/0B 4.88MB/0B 7
差别不大,反而是 alpine 占用内存稍稍高那么一点点,但都在可以接受的范围内。
如何选择?
站在 node.js 应用的角度,应该如何选择镜像呢?alpine 与 buster/buster-slim 最大的差异在于 C++ 插件,例如你的包里面用了 sharp 这个包对图片进行加工处理的话,alpine 镜像就不能用了,因为不兼容。其他情况下,如果你的应用和依赖是纯粹 node.js 编写的,不涉及到 C++ 插件,建议使用 alpine 镜像。
buster/buster-slim 镜像可以运行所有 node.js 项目,包括那些 C++ 依赖,但是有一个坑就是通过 npm start 启动的项目无法监听到 docker 的 SIGTERM 信号,如果进程没有相应 SIGTERM 事件,docker 默认等待 10s,然后就强制杀掉应用了,测试步骤如下:
-
首先在根目录创建一个
code文件夹,写入index.js:console.log('pid', process.pid) process.on('exit', (code) => { console.log('进程 exit 事件的代码: ', code) }) process.on('SIGTERM', (code) => { console.log('SIGTERM', code) process.exit(0) }) require('http').createServer((req, res) => res.end('Hello World')).listen(3030) -
然后创建 package.json :
{ "scripts": { "start": "node index.js" } } -
启动容器:
docker run -d --name node-14-buster -v /code:/code node:14-buster sh -c 'cd code && npm start' docker run -d --name node-14-buster-slim -v /code:/code node:14-buster sh -c 'cd code && npm start' docker run -d --name node-14-alpine -v /code:/code node:14-alpine sh -c 'cd code && npm start' -
重启容器
在重启 buster/buster-slim 镜像的时候,发现速度很慢,达到了 10s 的超时时间,那是因为没有响应 docker 传递的 SIGTERM 信号,可以通过下面的代码测试出来:
docker logs -f node-14-buster # 然后开新命令行 docker kill -s SIGTERM node-14-buster docker restart node-14-busterbuster 和 buster-slim 都无法接受 SIGTERM 信号,alpine 则可以。
自定义镜像
凑巧了,我们目前的项目既用到了 C++ 插件,又要监听 SIGTERM 事件,官方提供的三类镜像都不能用了,不过基于其他操作系统定制一个 node.js 镜像也很容易,例如基于 Centos7 系统创建 Dockerfile 如下:
FROM centos:7
RUN curl -L https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
RUN curl --silent --location https://rpm.nodesource.com/setup_14.x | bash -
RUN yum install -y nodejs yarn
WORKDIR /code
EXPOSE 80
CMD npm start
然后构建 node.js 镜像:
docker build -t node:14-centos7 .