阅读时间:15 分钟
Node.js 无疑是 js 开发者的福音,因为它既可用于 web 端开发,当作构建工具,也可以用于服务端,搭建 web 服务器。
但使用 Node.js(尤其是 npm)时也会碰到一些麻烦的事情,比如:
针对以上问题,聪明的开发者们想出了各种解决方案
只可惜这些解决方案都是有缺陷的,对于 windows 系统的开发者来说更是如此:
既然这些问题无法使用传统的工具解决,那么不妨切换一下角度使用新的工具:Docker。
Docker 的使用场景通常是多应用、多服务器的生产环境或持续集成中的测试环境,用于开发环境的案例比较少,能看到的案例也仅仅是启动容器、构建镜像,实际应用、深入探索者寥寥。
出现这种情抛开学习成本不考虑,一方面可能就现在的开发方式而言,分工越来越细,主张各种 "分离",开发者通常只需要在某种单一环境下,而人们了解 Docker 大多有以上先入为主的观念,所以未曾想到用于开发环境;另一方面可能是实际操作中碰到了一些暂时无法解决的问题。
瑕不掩瑜,将 Docker 用于 Node.js 的开发环境不仅能解决开头提出的几个问题,而且对于开发完成之后与测试人员、运维人员进行快速对接和实施部署也是非常有帮助的。
Docker 利用虚拟化技术,利用镜像创建称之为容器的虚拟运行环境来运行我们的程序。
所以对于切换 nodejs 版本的问题,只需要利用不同版本的 nodejs 镜像来创建容器即可轻松实现。
Docker 本身在 Linux、Mac、Windows 三大操作系统都能安装(win10 以下版本需要使用 docker toolbox),所以 Docker 容器是跨平台的,镜像一致即容器一致。
调试起来也非常方便,利用 Node.js 自带的 inspector 功能,配合 visual studio code 的 remote debuger 可轻松实现断点、重启等操作。
要减少 npm install 的时间,在不修改 npm 本身的情况下,路径只有一条:缓存 node_modules 下的模块。
一种缓存方式是创建一个容器做代理服务器(或私有仓库),将 npm 的 registry 地址指向该代理服务器进行模块安装。
代理服务器从远程仓库下载模块并返回给请求安装的容器,同时该缓存模块。下次请求该模块中直接从缓存中读取。
这种方式的麻烦之处不是搭建代理服务器(或私有仓库),而是效率不高,一方面还是需要发送 http(s) 请求来下载解压模块;另一方面对于开发者来说还是每个代码仓库一个 node_modules,其中可能相当一部分是重复的;更为严重的是,这些模块和当前系统还不一致(容器中安装的是 linux 系统使用的模块,而开发者安装的系统可能是 Windows 或 Mac)。
所以比较好的方式应该是在开发者本地进行缓存,建立一个模块池,不同的项目都可以从这个模块池中找到自己想要的模块,如果找不到就往池里面新增模块。这样一方面可以增加模块的最大利用率,避免因不同项目存储多份相同的模块,从而节省了大量的空间。更重要的是,对于已安装的模块,npm 不会再发送请求下载模块,从而节省了大量的时间。这些节省的空间和时间优势,会随着项目数量和规模的增长而变得更加明显。
然而这要实现起来却并非上述那般容易,就连 node.js 官方的文档也只有关于如何将应用部署到 Docker 容器中的介绍。
《Dockerizing a Node.js web app》
《Docker and Node.js Best Practices》
下面就来详细介绍具体操作
虽然 Node.js 在前后端开发使用场景作用差别很大,前端通常用来运行构建工具,如 gulp、webpack 等,后端则可以直接执行 js 代码启动服务器。
不过目录结构大体相同,所以可以放在一起讨论。下面是个简单的项目结构示例,代表了项目种的几类文件和目录。
我们现在有个 Node.js 项目,在 C:\Repo\project 路径中
- |
- - src //源文件目录
- - node_modules // node模块
- - package.json // node模块管理工具
- - dist // 被压缩、合并、编译生成的目标代码目录
- ...
在这个项目结构中,
、
- src
是开发中可能需要修改的目录和文件,
- package.json
是需要被缓存的目录,
- node_modules
是需要用来部署、执行的目录。
- dist
用 Docker 容器管理项目在此便会出现分歧。
一部分开发者可能会如前面提到的文章一样,通过编写 Dockerfile 来构建镜像,将 node_modules 放入镜像中,然后挂载源码路径,这样以后搭建开发环境可以直接通过镜像创建容器来实现,不过实现的问题是:
每次配置文件改动、模块依赖变化都需要重新 build,即使不 build 直接在容器中用 npm 安装,但是由于 package.json 是文件,无法挂载,所以导致又需要手动同步配置文件,这些都是相当麻烦而且容易出错的。
另一部分开发者可能会想到直接挂载整个项目,如
- docker run -itd -v C:\Repo\project:/project --name project node:7-alpine
但是这样并不能缓存 node_modules 目录,只是在容器中执行 node 进程而已。
那把 node_modules 再单独挂载一次不就行了?很遗憾,Docker 并不支持这种嵌套挂载宿主机目录的方式~
但这个思路已经很接近我们想要的结果了,所以我们要用另外的方式挂载 node_modules 来进行缓存,比如说把它和另一个容器进行挂载。
先创建一个容器用来缓存 node_modules,并暴露 /project/node_modules 作为挂载路径
- docker run -it -v /project/node_modules --name node_modules alpine
然后创建容器时通过 –volumes-from 实现与缓存容器共享 /project/node_modules。
- docker run -itd -v C:\Repo\project:/project --volumes-from node_modules --name project node:7-alpine
再 project 容器中安装一个 lodash 模块试试是否成功。
- docker exec -it project npm i lodash
创建一个临时容器看看是否安装成功。
- docker exec -it --rm --volumes-from node_modules -w /project node:7-alpine ls node_modules
成功显示 lodash,打开宿主机上的文件夹,node_modules 下空空如也,证明模块已安装到容器中,且可以重复使用。
把数据保存在容器中并不是一种值得推荐的做法,抛开 Docker 守护进程和容器本身的稳定性不说,容器也存在一定被误删的可能性。
而这种共享卷的方式有个更麻烦的问题是所有想利用这个缓存卷的容器目录结构都必须是 /project/node_modules,这样的限制就显得很不友好了。
另外用来缓存卷的容器基本上算是浪费了,起不到什么实质性的作用。
顺着挂载卷这条线索继续往下找,便可以发现更好的解决方法:创建一个 Docker volume 用来共享容器间的数据。
- docker volume create node_modules7
首先的好处便是这个 volume 可以叫任意名字,也可以挂载到容器不同的路径下。这里之所以加上 "7" 是因为不同 npm 版本组织模块的方式会有些不同,这里通过对 node 版本号来进行标注,表示这些模块可用于 Node.js 7 版本。
这时候创建容器我们便可以用 node_modules7 这个 volume 进行挂载了
- docker run -itd -v C:\Repo\project:/project -v node_modules7:/project/node_modules --name project node:7-alpine
这次我们安装 underscore 测试一下
- docker exec -it project npm i underscore
依旧创建一个临时容器看看是否安装成功,不过我们挂载到容器的 / app/node_modules 目录下试试
- docker exec -it --rm -v node_modules7:/app/node_modules -w /project node:7-alpine ls node_modules
显示 underscore 目录,表示模块共享成功。
利用 Docker 容器搭建开发环境可能碰到最麻烦的问题便是无法实现自动刷新、编译,尤其是对于系统环境为 Windows 的用户,由于 NFS 存储卷底层的事件消息与 Docker 容器通信有些问题,导致 Node.js 模块的监听文件变化的功能失效。(据部分开发者反映 Mac 系统下可能也会出现这种问题)
所以主流常用的 Node.js 模块都会支持设置参数来开启以轮询模式,检测文件修。
其中包括前端目前常用的构建工具 gulp 就支持开启 poll 模式 。
- gulp.watch('xxx', {
- mode: 'poll', // 开启轮询模式
- interval: 500 // 轮询间隔
- }, function() {
- ...
- })
前端模块构建工具 webpack 也是提供了 poll 选项。
- watchOptions: {
- poll: 1000 // 开启轮询模式并设置轮询间隔
- }
虽然 Node.js 作为服务端不需要构建工具,但是在实际开发中,我们希望修改代码的时候服务器能自动重启,所以会使用 nodemon 这样的模块来帮我们运行代码。而 nodemon 本身也支持 "-L" 参数来进行轮询检查代码。例如:
- nodemon -L app.js
对于其他语言环境的开发者,相信对 Docker 容器的卷共享、网络通信等基本概念有所熟悉之后,结合实际开发情况,参照本文所述思路,应该也可以搭建理想的开发环境。
作者:亚里士朱德 博客: http://yalishizhude.github.io
更多文章,请微信搜索 "web 学习社" 或扫码关注:来源: http://www.tuicool.com/articles/2QVJVjY