当前栏目
使用Docker Compose改善Node.js的开发
在过去的几年中,Docker和Node.js都变得非常流行。对于开发人员来说利用这些新技术来改善自己的开发体验很有必要,而且在此过程中还可以学习新技术。遵循"Coding到老,学习到老,折腾到老"的宗旨,本文我们将介绍将如何结合Node.js与Docker来改善开发人员体验,包括使用docker build和利用Docker Compose来实现无缝的本地前端开发环境。
概述
本文中,我们以Express.js为一个示例展开,需要实现了解一丁点Node.js和npm的基础知识。还要了解Express.js框架的基础知识。
对Docker也要有一定的概念和要会基础操作(不会也没关系,很容易)。
![使用Docker Compose改善Node.js的开发](https://s6.51cto.com/oss/202101/14/4500c5643d6c6673f9d414e6efea879e.png)
最后本文全程使用Linux(Mac) shell终端命令行。
创建Express.js项目
为了要生成示范应用程序,需要使用Express应用程序生成器。需要运行以下npx命令行:
- npx express-generator --view=pug --git <app-name>
Express生成器将生成Express应用。--view=pub选项表示使用pug视图引擎。--git表示用来给项目添加一个git .gitignore文件。
生成效果如下:
![使用Docker Compose改善Node.js的开发](https://s3.51cto.com/oss/202101/14/4500c0969d22cff2f4fba1d66e107cc0.png)
测试Express应用
要测试该应用程序,需要运行npm install安装所有必需的npm模块。然后,运行以下命令以启动应用程序:
- DEBUG=nodejs-docker-express:* npm start
如果没有异常,应该会到一条类似的消息。
- nodejs-docker-express:server Listening on port 3000
上面的命令非常简单:它运行一个环境变量DEBUG=nodejs-docker-express,用来表示服务器进行详细的调试。
对Windows系统,使用的参数要修改为:
- set DEBUG=nodejs-docker-express:* & npm start
现在打开浏览器,在地址栏并输入localhost:3000并访问:
![使用Docker Compose改善Node.js的开发](https://s2.51cto.com/oss/202101/14/782212439e7943ca9ac047ff8c9efc22.png)
这样示例的Express.js应用就已经在运行OK了。是不是非常简单?有此基本的"Hello,World!"为基础,我们进一步深入。
Docker多阶段构建
容器化应用程序有很多好处:首先,无论运行平台是什么,其行为都相同。借助Docker容器,应用程序可以轻松部署到各个公有容器云(比如AWS Fargate,Google Cloud Run),自建的K8S集群中,甚至本地docker上。
容器化,基础是Dockerfile。Dockerfile是构建Docker镜像的基础。用Dockerfile编译生成的镜像运行时,就称之为容器。
![使用Docker Compose改善Node.js的开发](https://s6.51cto.com/oss/202101/14/589ea500a4724fae2ec3acdef198fc3e.png)
如图示,整个过程非常简单:从Dockerfile构建Docker镜像。运行镜像,得到运行时容器。
Dockerfile
Dockerfile有一些类似命令行的语句:
- FROM node:14-alpine as base
- WORKDIR /src
- COPY package*.json /
- EXPOSE 3000
- FROM base as production
- ENV NODE_ENV=production
- RUN npm ci
- COPY . /
- CMD ["node", "bin/www"]
- FROM base as dev
- ENV NODE_ENV=development
- RUN npm install -g nodemon && npm install
- COPY . /
- CMD ["nodemon", "bin/www"]
通过Docker镜像的分层继承,创建了一个精简的production镜像和一个功能更丰富,以开发为重点的dev镜像。
在Dockerfile中,使用了多阶段构建,整个过程分为三个阶段:base,production和dev。production和dev依赖于base,base为node:14-alpine的基础镜像,该基础镜像需要从DockerHub获取,这是一个官方Alpine基础OS的Node.js官方镜像,主镜像为345MB,Node.js镜像大概不到40M。
- WORKDIR /src
- COPY package*.json /
- EXPOSE 3000
WORKDIR语句设置了Docker运行的工作目录,其后的命令都在该工作目录运行。COPY语句,复制package*.json(package.json和package-lock.json)容器中。
EXPOSE语句,设置Node.js Express Web服务器的监听端口。上述步骤对于开发和生产阶段都是通用的。
现在我们来看看生产目标阶段是如何构建的。
production
在生产阶段,继续从基础阶段开始的工作,FROM语句指示Docker从base开始。ENV语句设置Docker将环境变量NODE_ENV为production。
- FROM base as production
- ENV NODE_ENV=production
- RUN npm ci
- COPY . /
- CMD ["node", "bin/www"]
变量ENV设置为production可以使性能提高三倍,并且提供一些其他优化,比如缓存视图。npm install命令只会安装主要依赖项,忽略开发依赖项。这些设置非常适合生产环境。
接着使用RUN语句运行npm ci而非npm install。npm ci适用于持续集成和部署。和npm install相比,会绕过某些面向用户的功能。当然,npm ci需要一个package-lock.json文件才能工作。
之后,还是使用COPY语句将代码复制到工作目录。
最后使用CMD语句,运行Node应用服务器和/srcbin/www
dev
我们利用了多阶段构建,并在开发阶段添加开发所需的组件:
- FROM base as dev
- ENV NODE_ENV=development
- RUN npm install -g nodemon && npm install
- COPY . /
- CMD ["nodemon", "bin/www"]
大体上和生产极端类似,差异为NODE_ENV环境变量设置为development。
接着,用RUN语句安装nodemon。每当文件更改时,nodemon都会重新启动服务器,从而开发体验更加流畅。同时执行npm install,该命令会递归安装dev依赖项。例如,如果要使用Jest测试应用程序,那将是开发依赖项之一。
请注意,这两个命令通过&&放在一起,创建更少的Docker层,于构建缓存非常有用。这是撰写Dockerfile时候常用的一个技巧。
和生产阶段相同,将代码复制到容器。但是,用nodemon取代了Node服务器,这样在每次文件/src更改时会重新启动它。
.dockerignore
和git的.gitignore一样,docker也使用.dockerignore来忽略不想放入Docker镜像的文件。通过忽略无关的文件更改,它有助于使Docker镜像保持身材,而且能使构建缓存更高效。本示例中.dockerignore
- .git
- node_modules
非常简单,告诉Docker不COPY.git文件夹和node_modules从主机复制到Docker容器。
使用Docker Compose
到目前为止,我们创建一个使用运行Node.js Express应用程序Docker所需的大部分功能。为了更便捷,我们还建议用Docker Compose,这样可以更轻松地使用单个或多个容器运行应用程序。这样也无需要记住很长的命令来构建或运行容器。只需通过:
- docker-compose build
- docker-compose up
但是docker-compose使用yml的配置文件和dockerfile略有不同:
![使用Docker Compose改善Node.js的开发](https://s2.51cto.com/oss/202101/14/e6a2066058230b9a366b8111cc32aec6.png)
上述,我们指定Docker Compose的版本,在本例中为3.8,对应Docker引擎19.0.3支持的最新版本。这样可以支持多阶段Docker构建。
接着,指定正在使用的服务。在本教程中,只有一个名为web的服务,具有context为当前目录的构建以及一个重要的构建参数target设置为dev。这告诉Docker在dev阶段构建Docker映像。
之后,通过volumes制定 Docker卷。它指示Docker从Docker容器上的./和主机本地/src目录复制和同步更改。当我们在主机中更改文件时,这将很有用,并且文件也将立即反映到容器中。
command语句运行npm run start:dev,start:dev执行内容定义在package.json,内容为:
- "start:dev": "nodemon ./bin/www"
表示使用nodemon启动Web服务器。在开发环境中,可以在每次保存文件时重新启动服务器。
接下来,用ports语句设置docker端口映射主机的3000端口与容器3000端口。在构建容器时,公开了端口3000, Web服务器就会在3000上运行。
最后,设置了两个环境变量。首先,将其NODE_ENV设置为development,因为这样可以看到详细的Debug信息,也没有任何视图缓存。然后,将debug设置为*,让Web服务器打印出所有内容的详细调试消息。
测试应用程序
前面,设置了弄好了基础构建配置文件,接着构建Docker镜像。使用BuildKit优化Docker构建。启用BuildKit可以更快地构建Docker镜像,运行以下命令:
- COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build
该命令告诉Compose在BuildKit上构建Docker镜像。它应该在一段时间内运行并构建Docker镜像,如下所示:
![使用Docker Compose改善Node.js的开发](https://s3.51cto.com/oss/202101/14/670b07a1ceafd63ec1f42aa1d9696c1f.png)
Docker镜像大约在14秒内构建完成,使用BuildKit可以更快。运行该镜像:
docker-compose up
![使用Docker Compose改善Node.js的开发](https://s3.51cto.com/oss/202101/14/4b34d81969a13abb536d893d308ad912.png)
然后浏览器访问localhost:3000:
![使用Docker Compose改善Node.js的开发](https://s4.51cto.com/oss/202101/14/f6658dde16c7eddfe3a31a0befab3cd2.png)
这样我们,自配置的应用程序在Docker上已经完美运行。我们来改改源文件,看看效果。
我们修改下源码将" Welcome to Express"更改为" Welcome to Express with Docker"来测试。在源文件目录/src下,找到routes/index.jsline文件,修改语句为:
- res.render('index', { title: 'Express with Docker' });
保存文件,然后可以看到Web服务器已经重新启动,表示Docker卷和nodemon可以都可以正常工作。
![使用Docker Compose改善Node.js的开发](https://s6.51cto.com/oss/202101/14/c96ecb05f3de52c4dec235d0772a041f.png)
F5刷新浏览器,内容已经修改:
![使用Docker Compose改善Node.js的开发](https://s2.51cto.com/oss/202101/14/782212439e7943ca9ac047ff8c9efc22.png)
总结
本文中我们利用Docker和Docker Compose构建了一个简单的Nodejs的开发和运行环境。Node.js和docker的配合很好。通过使用docker-compose,开发体验更加流畅。当然这这是一个很简单的开始,对于更复杂的应用(比如需要访问数据库)才是Docker Compose的用武之地,他可以同时启动和管理多个容器,比如给开发环境增加Mongo或MySQL添加为应用程序的数据源,只需很轻松地增加一个docker-compose配置的服务语句就可以搞定整个环境。
相关文章
- 建造具有巨大处理能力的超导量子计算机的秘密:光纤
- 一日一技:如何捅穿Cloud Flare的5秒盾
- 全球那些知名组织是如何做软件测试的?
- 前端开发指南:如何利用PHP Cake框架构建应用
- 数据结构线性结构篇—链表
- 如何将 DevTools的堆栈追踪速度提高10倍
- Vue3值得注意的新特性之——触发组件选项
- 基于React与Vue后,移动开源项目Weex如何定义未来
- Vue3值得注意的新特性之——teleport
- 提高React界面性能的十个小技巧
- 搞定 ParseInt() 的怪异行为
- 前端百题斩之Js中6种变量声明方式
- Switch竟然会报空指针异常,学到了!
- JavaScript 如何压缩目录并上传?
- 没有UI团队怎么办?分享6款能为独立网页开发者提效的免费工具
- JavaScript 原始值与包装对象
- 一篇文章带你了解JavaScript错误处理
- CI校验不通过,竟然被自己坑死了
- Js 实现 Bind 的这五层,你在第几层?
- 这需求快让我崩溃了,不过幸亏我懂装饰器模式