在Node.js 我们可以通过以下代码捕获 uncaughtException 错误:
捕获 uncaughtException 后,Node.js 的进程就不会退出,但是当 Node.js 抛出uncaughtException 异常时就会丢失当前环境的堆栈,导致 Node.js 不能正常进行内存回收。
也就是说,每一次、 uncaughtException 都有可能导致内存泄露。既然如此,退而求其次,我们可以在满足前两个条件的情况下退出进程以便重启服务。
当然还可以利用 domain 模块做更细致的异常处理,这里就不做介绍了。
3. 如何编写 Dockerfile
3.1 基础镜像选择
我们先选用 Node.js 官方推荐的 node:argon 官方 LTS 版本最新镜像,镜像大小为 656.9 MB (解压后大小,下文提到的镜像大小没有特殊说明的均指解压后的大小)
我们事先写好了两个文件 package.json , app.js :
下面开始编写 Dockerfile,由于直接从 Dockerhub 拉取镜像速度较慢,我们选用时速云的docker官方镜像docker_library/node(https://hub.tenxcloud.com/repos/docker_library/node),这些官方镜像都是与 Dockerhub 实时同步的:
执行以下命令进行构建:
docker build -t zhangpc/docker_web_app:argon .
最终得到的镜像大小是 660.3 MB ,体积略大,Docker 容器的优势是轻量和可移植,所以承载它的操作系统即基础镜像也应该迎合这个特性,于是我想到了 Alpine Linux ,一个面向安全的,轻量的 Linux 发行版,基于 musllibc 和 busybox 。
下面我们使用 alpine:edge 作为基础镜像,镜像大小为 4.799 MB :
执行以下命令进行构建:
docker build -t zhangpc/docker_web_app:alpine .
最终得到的镜像大小是 31.51 MB ,足足缩小了20倍,运行两个镜像均测试通过。
3.2 还有优化的空间吗?
首先,大小上还是可以优化的,我们知道 Dockerfile 的每条指令都会将结果提交为新的镜像,下一条指令将会基于上一步指令的镜像的基础上构建。
所以如果我们要想清除构建过程中产生的缓存,就得保证产生缓存的命令和清除缓存的命令在同一条 Dockerfile 指令中,因此修改 Dockerfile 如下:
执行以下命令进行构建:
docker build -t zhangpc/docker_web_app:alpine .
最终得到的镜像大小是 21.47 MB ,缩小了10M。
其次,我们发现在构建过程中有一些依赖是基本不变的,例如安装 Node.js 以及项目依赖,我们可以把这些不变的依赖集成在基础镜像中,这样可以大幅提升构建速度,基本上是秒级构建。
当然也可以把这些基本不变的指令集中在 Dockerfile 的前面部分,并保持前面部分不变,这样就可以利用缓存提升构建速度。
最后,如果使用了 Express 框架,在构建生产环境镜像时可以设置 NODE_ENV 环境变量为 production ,可以大幅提升应用的性能,还有其他诸多好处,下面会有介绍。
小结
我们构建的三个镜像大小对比见上图,镜像的大小越小,发布的时候越快捷,而且可以提高安全性,因为更少的代码和程序在容器中意味着更小的攻击面。
使用 node:argon 作为基础镜像构建出的镜像(tag 为 argon)压缩后的大小大概为 254 MB ,也不是很大。
如果对 Alpine Linux 心存顾虑的童鞋可以选用 Node.js 官方推荐的 node:argon 作为基础镜像构建微服务。
4.微服务部署及 devops 集成
部署微服务时有一个原则:一个容器中只放一个服务,可以使用stack 编排把各个微服务组合成一个完整的应用: