ihahoo / docker-node-boilerplate

docker下开发和部署node.js应用基本脚手架, 整合了Redis和Postgres数据库容器。 ( Babel, ES6, async/await, ESLint, Dockerfile, docker-compose, redis, postgresql/mysql... )

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

docker-node-boilerplate

docker下开发和部署node.js应用基本脚手架, 整合了Redis和Postgres数据库容器。 ( Babel, ES6, async/await, ESLint, Dockerfile, docker-compose, redis, postgresql/mysql... )

docker容器技术作为新技术方式,可以方便的部署应用,实现了从开发、测试到生产的环境一致性。开发人员应该学习和使用docker来方便开发,提高效率,并且为最终的生产负责。作为全栈工程师,或者后端开发工程师需要学习和使用docker去搭建坏境。本例是node.js下开发后端的脚手架,其它语言的环境原理类似。脚手架中整合了Redis和Postgres数据库,这些都可以通过docker-compose的配置文件,灵活的配置成自己需要的环境,比如mysql等。如果你要开发一套云产品或微服务产品,使用docker容器去部署、发布、升级,将方便很多。

流程

1.开发环境

在开发时,可以将数据库等环境通过docker容器运行。以往的开发环境是在开发机上安装数据库等服务环境,不同的项目通过不同的数据库表分开,而用docker容器之后,每个项目都可以单独的使用容器运行一套独自的环境。使用容器技术的好处就是可以统一开发环境,比如多人开发,每个人的开发环境和版本不同,可能导致一些问题,另外的好处就是可以统一从开发到生产的环境。

1.1 安装

首先需要安装dockerdocker-componse环境,本机即使没有node.js环境也没关系。然后克隆本项目:

$ git clone https://github.com/ihahoo/docker-node-boilerplate.git

1.2 初始化

$ docker-compose -f docker-compose.dev.yml run --rm web_dev npm install

此命令通过 docker-compose.dev.yml 配置文档调用 Dockerfile-dev 建立了 xxxx_web_dev 的镜像,xxxx 根据你项目的文件夹显示不同的名称,可以使用 docker images 命令查看生成的镜像。

开发环境使用的官方node镜像构建,没有使用alpine版,因为开发环境要用到pm2,而我测试使用alpine的node镜像建立的容器使用pm2会报错。另外开发环境没有使用精简版也方便调试。开发环境使用pm2是为了方便修改了文档之后,可以自动更新服务。

通过此命令,会调用npm install安装node.js依赖,安装在项目根文件夹下的node_modules中。而node_modules是作为数据卷挂到建立的开发环境镜像中,这样在开发镜像和本地都不用反复安装npm依赖了。

其实上面的命令会产生两步操作,一是建立一个开发环境的node镜像,二是通过run --rm web_dev npm install,在容器中运行npm install,为什么分这两步?因为开发环境是把当前项目根文件夹整体作为数据卷挂到容器中,如果直接在docker-compose配置文档或者Dockerfile-dev中运行npm install,你会发现全部运行完,却没有node_modules文件夹,是因为最后在数据卷挂载的时候,会用当前文件夹去替换掉容器中的根文件夹,而一开始文件夹中是不含node_modules的,虽然在容器里安装并建立了node_modules文件夹,但是随后用当前根目录挂载数据卷到容器中,就会覆盖掉容器里的根目录的内容,node_modules就消失了。所以,初始化阶段,分了两步。

如果本机安装了Node.js环境

如果你的开发机本机有Node.js的环境,可以跳过上面的方式,直接运行以下命令即可(不推荐):

$ npm install

这样也会初始化安装依赖,安装到根目录node_modules中。

为什么不推荐这种方式呢?因为你首先要保证本机的node和npm版本和镜像中的一致,否则可能因为开发环境的版本不一致,导致团队开发或者发布产生问题。最好是通过调用容器中的node来初始化安装,另外在以后的开发中如果要安装node模块最好也到容器中去运行npm install ..., 其实即使本机没有安装node,通过容器中的node环境,也完全可以完成开发和调试。当然这不是必须的。

注意:初始化之后,不用每次都要初始化,以后只要用docker-componse up启动就可以了,除非修改了Dockerfile-dev初始化构建文件,需要重新建立镜像

1.3 启动开发环境

$ docker-compose up

通过此命令,就启动了本机的开发环境。运行docker ps,可以看到开启了3个容器,包括node容器,redis容器,postgres数据库容器。这个命令调用了docker-compose.yml配置文件,建立了3个容器,并建立了3者的内部网络,你可以看作有3台服务器,通过内部网络连接到一起,可以通信,我们连接到了这个网络中去开发。这就是Docker容器的强大之处,而且它并不像VMware那种虚拟机需要占用本机很多资源。

注:这个过程会自动从docker官方Hub下载相关镜像。从国内直接下载很慢,建议配置docker镜像加速器,比如阿里镜像加速器,注册账户后,会配置独立的加速器地址使用。

连接数据库
数据库客户端连接:

本机数据库客户端可以通过localhost的5432端口访问postgres数据库,通过6379端口连接redis数据库。这里使用了postgres数据库,您也可以换成mysql等数据库,原理是一样的,可以通过修改docker-compose.yml来配置。通过数据库客户端连接,可以建立数据库,建表等开发操作。当然,你也可以直接进入相应数据库的容器中操作。

通过node.js程序连接:

在容器中node.js连接数据库,通过docker-compose.yml中配置的services名称作为hostname连接,比如这里db代表postgres的hostname, redis代表redis的hostname。例:在node程序中连接redis配置项如下

{
  host: 'redis',
  port: 6379,
  password: 'xxxxxxxx'
}

至此,你可以使用开发环境开发了。本机不用安装postgres,不用安装redis,甚至不用安装node,都用docker容器,是不是很酷。

进入node容器
$ docker exec -it xxxx_web_dev /bin/bash

可以通过以上命令进入到node的容器内部,xxxx_web_dev是名称,根据你的名称输入,可以通过docker ps查看到名称。进入容器后你可以通过npm install安装一些node模块。

注意:如果在容器里要使用npm install -g全局的方式安装的话,请修改Dockerfile-dev文件,将需要全局安装的模块在生成镜像的时候就安装进去,否则在容器中安装全局模块,重启容器后,就不见了。修改Dockerfile-dev后,请重新构建镜像。比如先docker-compose downdocker-compose up --build通过--build参数,重新构建镜像。

1.4 停止开发环境

$ docker-compose stop

docker-compose stop停止后,不会删除容器,通过docker ps -a可以看到容器还在。

1.5 终止开发环境

$ docker-compose down

docker-compose down会删除容器和建立的网络,通过docker ps -a可以看到容器不在了。

1.6 卸载开发环境

$ docker-compose down

这样就先删除了容器和网络,然后

$ docker images

列出建立的开发镜像,通过命令删除镜像即可

$ docker rmi somename_web_dev

然后您可以重新初始化,建立开发环境。

而不论卸载或删除,都不会影响你开发环境的文件和数据文件,docker容器相当于一个环境运行机。

1.7 npm脚本

本例中,一些写在package.json中的脚本如下:

$ npm run build

构建发布文件。将src/下的代码,通过Babel转码和压缩后生成到lib/文件夹下,最终生产环境通过lib/下发布。

$ npm run dev

直接调用src/下的启动文件,启动测试服务,不过修改了代码后,不会自动重启,需要手动重启才能看到变化。

$ npm run dev:start

相当于pm2 start process.dev.json,使用pm2,可以自动监视文件变化,当修改了代码保存后会自动重启服务,达到实时更新。 注意:在docker容器中需要使用pm2-dev start process.dev.json来启动。

$ npm run dev:stop

相当于pm2 stop process.dev.json, 停止pm2

$ npm run dev:kill

相当于pm2 kill,终止pm2启动的所有服务

$ npm run clean

删除通过npm run build建立的lib/文件夹。

$ npm run lint

代码错误检查

$ npm start

启动lib/下的生产环境。(首先需要npm run build构建)

2.部署到生产环境

2.1 构建生成环境的应用文件

$ npm run build

通过docker exec -it xxxxx /bin/bash到node容器中运行以上命令,可以将要发布到生产环境的应用文件,构建到lib/下。当然,如果你本机有安装node环境,也可以直接在本机中运行以上命令。

2.2 生成生产环境镜像

$ docker build -t hahoo/node-hello:0.0.1 .

这个命令是通过Dockerfile建立镜像,使用的是node的alpine版,生成的镜像比较小,生产环境这里不使用pm2。注意,别忘记命令最后的那个.点。-t hahoo/node-hello的意思,就是给镜像加tag,名称是hahoo/node-hello,版本是0.0.1,由于本例子,是发布到Docker官方Hub中,而实际项目一般是上传到私有仓库中。私有仓库,可以通过官方的registry方便的建立。

您可以参考我的 nginx + docker-gen + letsencrypt 下部署Docker私有仓库registry

比如你的仓库地址是 registry.domain.com,那么镜像名称可以是:registry.domain.com/yourname:0.0.1 之类的

然后将这个镜像打上latest标签:

docker tag registry.domain.com/yourname:0.0.1 registry.domain.com/yourname:latest

2.3 测试镜像

$ docker-compose -f docker-compose.prod.yml up

上传镜像前,先在本地测试。以上命令通过docker-compose建立了node和数据库的容器,挂载了数据卷,将容器设置为总是自动重启。

注意:请修改docker-compose.prod.yml中的配置,以适合自己的需要

生产环境将config/data/作为数据卷挂载到容器中,这里是配置文件和数据库文件,以后升级镜像即可,而数据是永久的,否则数据在容器中,重启,数据就消失了。而生产环境,我们把lib/和通过npm install --only=production安装的node_modules封装到了镜像中。开发环境的文件和模块,不进入到生产环境中。另外在生产环境,数据库的端口不对外映射,只需要node.js容器内部网络访问。

2.4 推送镜像

$ docker login registry.domain.com
$ docker push registry.domain.com/yourname:0.0.1
$ docker push registry.domain.com/yourname:latest

registry.domain.com是你的私有仓库。通过docker push将镜像上传到私有仓库。另外,也可以不用仓库的方式传送,比如docker保存和导出命令,然后在生产服务器上导入镜像。建议使用私有仓库,方便管理和版本控制及回滚。

2.5 生产服务器部署

第一次部署
  1. 在生产服务器中建立一个项目文件夹,然后,将docker-compose.prod.yml上传到文件夹中,重命名为docker-compose.yml
  2. config/data/上传到新建的项目文件夹中。如果有其它需要永久存储的文件夹及文件,也上传到文件夹中,并配置docker-compose.yml挂载数据卷。
  3. 在服务器中新建立的这个文件夹下运行docker-compose up,当然要保证生产服务器也能访问你的Docker私有仓库。
  4. 不出意外,服务就可以正常访问了。
升级部署

进入到应用项目文件夹

$ docker-compose down
$ docker-compose pull
$ docker-compose up

当然,可以通过负载均衡,启动多个相同服务的容器,不停服务的同时平滑升级,本例中暂没展开。

注意:如果服务器通过nginx反向代理共享80或443端口虚机配置,需要修改docker-compose.yml网络设置。由于这3个服务建立了独有的虚拟网络环境,需要配置nginx可以访问到node.js容器的网络,而node.js到数据库的通信只需要在独有的内部网络环境中。

具体Docker的部署,请查看我的 docker-server-deploy, 在服务器上通过Docker部署nginx自发现,并且可以自动配置和升级letsencrypt https证书等。

应用项目文件夹下有docker-compose.yml文件和数据卷,这里的数据卷是config/data/,数据卷也可以放到专门存储数据的地方,比如NAS或者Docker的某个Datecenter之类的。

Node.js版本及开发环境

node.js 版本>= 6, node版本8也快出了,不要用6以下的版本开发了。 强烈建议使用 ES6, ES7, ES8等新特征开发。node6已经支持了大部分ES6新特性,不支持的用babel转码。.babelrc文件配置Babel设置。

  • src/: 源代码的组织和书写请在这个文件夹下。
  • eslint: 通过eslint检查js es6的语法错误和一致性习惯,这里用到了eslint-config-airbnb-base,也就是用了airbnb总结的代码书写习惯,这样对书写代码的一致性有了一个规范,特别是多人开发的时候,如果没有一个规范,那么每个人按照自己的习惯去做,那么代码会很难读懂和维护。可以在代码编辑器中,加入eslint插件,这样在开发的过程中,就可以时实时提示错误。如果对某些eslint规则不爽,可以通过修改.eslintrc配置去设置规则。eslint的一个好处,就是可以自定义规则。可以通过npm run lint来运行代码检查。
  • .editorconfig: 请将代码编辑器配置支持EditConfig的插件。编辑器通过读取这个配置文件之后,会按照一致的方式来书写代码,比如Tab键代表几个空格等,这样在多人开发,不同的开发环境,书写的代码是一致的。可以通过修改.editorconfig来进行一致性设置。
  • .tern-project: 是本人使用vim编辑器的YouCompleteMe插件的配置文件。如果不用vim可以不用管它或者删除。

自动化及持续集成

可以使用gitlabjenkins配置版本控制和自动化测试,构建和部署。而gitlabjenkins同样可以通过docker方便的搭建。

本例的镜像

在本例中通过Dockerfile,生成了hahoo/node-hello的测试镜像,存储在Docker官方的Hub仓库https://hub.docker.com/r/hahoo/node-hello/。在Docker的hub仓库中,设置了github的自动化构建,当提交到github中,会自动按照配置构建镜像。您可以用本例的镜像进行测试。

拉取镜像

docker pull hahoo/node-hello

或直接生成容器

docker run -p 8080:8080 -d --name node-hello hahoo/node-hello

常见问题

用docker部署服务,可行吗?

答: 当然,以往的部署是在服务器中建立环境,比如数据库等,然后部署上去。这相当于服务和服务器耦合了,如果一个主机有多个服务,不同的数据库,就要建立很多环境,如果涉及不同的版本,将更难维护,特别是在服务器迁移的时候,还要去服务器安装配置等,特别是本地开发的服务,发到服务器上发现跑不起来,而服务器维护人员无法独自排查原因,而又不允许开发人员直接到生产服务器上操作排查。而通过容器技术,实现了应用服务和服务器的解耦,可以实现快速的部署和迁移,可以实现从开发到生产的环境一致性,也方便集群和扩展。对于云服务和微服务,很适合容器。而且现在已经不用担心这个能不能用到生产的问题,因为各大互联网公司已经在使用Docker在跑服务了。

服务器用什么操作系统?

答: 用支持docker的linux内核版本即可,比如CentOS 7。当然,也可以使用CoreOS,只跑容器的Linux OS,而且CoreOS特别容易实现集群和负载等。

服务器全用Docker部署吗?

答: 只要ssh远程连接,其它服务,都可以用Docker容器实现,比如nginx等等。

一个服务器跑多个容器,性能?

答: Docker这种容器服务,不像VMware这种虚拟技术,docker可以动态的分配资源,可以在一个服务器中跑很多容器,当然也要看某一个容器服务的负载情况,更多请去搜索Docker技术的说明。

为什么使用docker-componse

答: docker-componse 可以将一个应用项目涉及的服务容器整合在一起,通过一个配置文件即可启动和部署,很方便,相当于docker的批量操作。

为什么要联合其它容器,通过一个linux镜像,将node,redis,postgre安装进去不行吗?

答: 可以这样,这样的确可以更方便的发布和部署。这样的弊端,就是要一直要维护这个镜像,比如postgre版本升级了,就要去镜像中更新,而且如果操作不擅,将会让整个镜像越来越大,越来越臃肿,所以不建议这种方式。而分开的方式,各自的服务镜像由官方维护,只要替换镜像版本即可实现升级。gitlab的官方镜像,就采用了全部封装到一个镜像里的方式发布,对于这种复杂配置的服务,这的确省事很多,只要run,就可以建立一个像github一样的服务,在以往,在服务器中安装一套gitlab,要经过很多步骤和设置。不过我还是建议最好不要都建立到一个镜像里。

配置文件里npm config set registry https://registry.npm.taobao.org什么意思

答: 使用taobao的npm镜像,在国内安装node模块时,快很多。如果服务器在国外,可以不用这个。

拉取官方docker镜像的时候很慢或者连接出错怎么办?

答: 请使用国内的docker镜像加速器。比如阿里云加速器。https://cr.console.aliyun.com/#/accelerator, 具体配置请自行搜索一下。

为什么生产环境,把代码都放到了镜像里

答: 开发环境,可以把开发目录作数据卷全部挂载到容器里,这样方便开发调试。生产环境,最好是把代码和npm安装的模块都放到镜像里,只把其它永久存储的数据作为数据卷挂载。通过升级镜像,来升级服务。推荐这样。当然,你也可以把代码文件作为数据卷挂载到容器上,这样每次修改文件即可更新,不过可能很多操作还是要到容器里面操作,比如npm安装。通过封装到镜像的方式升级,还可以通过版本记录变化,可以方便的回滚到以往的版本。

开发环境,一定要用容器里的node来调试吗?

答: 可以不用。本地安装了node环境,可以直接开发调试,这个脚手架就考虑了这个问题,所以架构上可以脱离docker容器开发,就像以前没用docker容器一样的目录结构。不过数据库等最好还是用容器吧,这不是方便很多?不用安装数据库到本机了。不过还是推荐使用容器的方式,起码要用容器调试,这样在发布到生产前解决遇到的问题。

为什么使用alpine版本镜像

答: alpine是个只有几M的linux版,构建的镜像比较小。常见应用的官方镜像基本都发布了alpine版。如果要自己不得不定义一些服务镜像,也建议使用alpine作为基础镜像去搭建。

怎么进入alpine镜像建立的容器里

答: alpine没用使用bash,而是使用ash,所以进入alpine镜像,类似于这样:docker exec -it xxxxxx /bin/ashash不是bash

我的容器要开ssh端口给远程访问维护吗?

答:当然你可以这么做,但是最好不要这样。只把它作为服务发布的一种容器对待。当然一些场景,是需要ssh登录服务器操作的,那么不是不可以。

postgres的数据库密码是?

答: 在docker-compose.ymldocker-compose.prod.ymldb部分,通过环境变量POSTGRES_PASSWORD设置密码,初始设置的密码是SgQ6Vhc3u015xOL09se9,请设置成您自己的。

redis的密码是?

答: 在config/redis/redis.conf中的requirepass中设置,初始设置的密码是CCq2Si39hdgY6ajP5vHL,请设置成您自己的。

About

docker下开发和部署node.js应用基本脚手架, 整合了Redis和Postgres数据库容器。 ( Babel, ES6, async/await, ESLint, Dockerfile, docker-compose, redis, postgresql/mysql... )

License:MIT License


Languages

Language:JavaScript 100.0%