Node.js Best Practices - How to Become a Better Developer in 2017提到的几点,咱们Fundebug深有同感:html
使用ES6node
使用Promiselinux
使用LTSgit
使用Dockergithub
...redis
想必你们都知道ES6,Promise以及LTS,那Docker是啥玩意啊?翻遍Node文档也没见踪影啊!mongodb
GitHub仓库: Fundebug/nodejs-dockerdocker
Docker是最流行的的容器工具,没有之一。本文并不打算深刻介绍Docker,不过能够从几个简单的角度来理解Docker。shell
在Linux中,全部的进程构成了一棵树。可使用pstree命令进行查看:数据库
pstree init─┬─VBoxService───7*[{VBoxService}] ├─acpid ├─atd ├─cron ├─dbus-daemon ├─dhclient ├─dockerd─┬─docker-containe─┬─docker-containe─┬─redis-server───2*[{redis-server}] │ │ │ └─8*[{docker-containe}] │ │ ├─docker-containe─┬─mongod───16*[{mongod}] │ │ │ └─8*[{docker-containe}] │ │ └─11*[{docker-containe}] │ └─13*[{dockerd}] ├─6*[getty] ├─influxd───9*[{influxd}] ├─irqbalance ├─puppet───{puppet} ├─rpc.idmapd ├─rpc.statd ├─rpcbind ├─rsyslogd───3*[{rsyslogd}] ├─ruby───{ruby} ├─sshd─┬─sshd───sshd───zsh───pstree │ ├─sshd───sshd───zsh │ └─sshd───sshd───zsh───mongo───2*[{mongo}] ├─systemd-logind ├─systemd-udevd ├─upstart-file-br ├─upstart-socket- └─upstart-udev-br
可知,init进程为全部进程的根(root),其PID为1。
Docker将不一样应用的进程隔离了起来,这些被隔离的进程就是一个个容器。隔离是基于两个Linux内核机制实现的,Namesapce和Cgroups。
Namespace能够从UTD、IPC、PID、Mount,User和Network的角度隔离进程。好比,不一样的进程将拥有不一样PID空间,这样容器中的进程将看不到主机上的进程,也看不到其余容器中的进程。这与Node.js中模块化以隔离变量的命名空间的思想是殊途同归的。
经过Cgroups,能够限制进程对CPU,内存等资源的使用。简单地说,咱们能够经过Cgroups指定容器只能使用1G内存。
从进程角度理解Docker,那每个Docker容器就是被隔离的进程及其子进程。上文pstree的输出中能够分辨出2个容器: mongodb和redis。
基于Namespace与Cgroups的容器工具其实早已存在,例如Linux-VServer,OpenVZ,LXC。然而,真正引爆容器技术的倒是后来者Docker。为何呢?我的以为是由于Docker镜像以及Dockerfile。
在Linux中,一切皆文件,进程的运行离不开各类各样的文件。跑一个简单的Node.js程序,传统的作法是手动安装各类依赖而后运行;而Docker则是将全部依赖(包括操做系统,Node,NPM模块,源代码)打包到一个Docker镜像中,而后基于这个镜像运行容器。
Docker镜像能够经过Docker仓库共享给其余人,这样他们只须要下载镜像便可运行程序。想象一下,当咱们须要在另外一台主机(好比生产服务器,新同事的机器)上运行一个Node.js应用,仅仅须要下载对应的Docker镜像就能够了,是否是很方便呢?
Docker镜像能够经过文本文件,即Dockerfile进行定义。不妨看一个简单的例子(因为不可抗力,这个Dockerfile构建大概会失败,仅做为参考):
# 基于Ubuntu FROM ubuntu # 安装Node.js与NPM RUN apt-get update && apt-get -y install nodejs npm # 安装NPM模块:Express RUN npm install express # 添加源代码 ADD app.js /
其中,FROM,RUN与ADD为Dockerfile命令。结合注释,该Dockerfile的含义很是直白。基于这个Dockerfile,使用docker build命令就能够构建对应的Docker镜像。基于这个Docker镜像,就能够运行Docker容器来执行app.js:
var express = require("express"); var app = express(); app.get("/", function(req, res) { res.send("Hello Fundebug!\n"); }); app.listen(3000);
Dockerfile其实是将Docker镜像代码化了,另外一方面也是将安装依赖的过程代码化了,因而咱们就能够像管理源码同样使用git对Dockerfile进行版本管理。
当你的系统愈来愈复杂的时候,你会发现Docker的价值。
刚开始,你只须要写一个Node.js程序,挂载一个静态网站;而后,你作了一个用户帐号系统,这时须要数据库了,好比说MySQL; 后来,为了提高性能,你引入了Memcached缓存;终于有一天,你决定把先后端分离,这样能够提升开发效率;当用户愈来愈多,你又不得不使用Nginx作反向代理; 对了,随着功能愈来愈多,你的应用依赖也会愈来愈多...总之,你的应用架构只会愈来愈复杂。不一样的组件的安装,配置与运行步骤各不相同,因而你不得不写一个很长的文档给新同事,只为了让他搭建一个开发环境。
使用Docker的话,你能够为不一样的组件逐一编写Dockerfile,分别构建镜像,而后运行在各个容器中。这样作,将复杂的架构统一了,全部组件的安装和运行步骤统一为几个简单的命令:
构建Docker镜像: docker build
上传Docker镜像: docker push
下载Docker镜像: docker pull
运行Docker容器: docker run
一般,你会有开发,测试和生产服务器,对于某些应用,还会须要进行构建。不一样步骤的依赖会有一些不一样,而且在不一样的服务器上执行。若是手动地在不一样的服务器上安装依赖,是件很麻烦的事情。好比说,当你须要为Node.js应用添加一个新的npm模块,或者升级一下Node.js,是否是得重复操做不少次?友情提示一下,手动敲命令是极易出错的,有些失误会致使致命的后果(参考最近Gitlab误删数据库与AWS的S3故障)。
若是使用Docker的话,开发、构建、测试、生产将所有在Docker容器中执行,你须要为不一样步骤编写不一样的Dockerfile。当依赖变化时,仅须要稍微修改Dockerfile便可。结合构建工具Jenkins,就能够将整个部署流程自动化。
另外一方面,Dockerfile将Docker镜像描述得很是精准,可以保证很强的一致性。好比,操做系统的版本,Node.js的版本,NPM模块的版本等。这就意味着,在本地开发环境运行成功的镜像,在构建、测试、生产环境中也没有问题。还有,不一样的Docker容器是依赖于不一样的Docker镜像,这样他们互不干扰。好比,两个Node.js应用能够分别使用不一样版本的Node.js。
架构规模愈来愈大的时候,你有必要引入集群了。这就意味着,服务器由1台变成了多台,同一个应用须要运行多个备份来分担负载。固然,你能够手动对集群的功能进行划分: Nginx服务器,Node.js服务器,MySQL服务器,测试服务器,生产服务器...这样作的好处是简单粗暴;也能够说财大气粗,由于资源闲置会很是严重。还有一点,每次新增节点的时候,你就不得不花大量时间进行安装与配置,这实际上是一种低效的重复劳动。
下载Docker镜像以后,Docker容器能够运行在集群的任何一个节点。一方面,各个组件能够共享主机,且互不干扰;另外一方面,也不须要在集群的节点上安装和配置任何组件。至于整个Docker集群的管理,业界有不少成熟的解决方案,例如Mesos,Kubernetes与Docker Swarm。这些集群系统提供了调度,服务发现,负载均衡等功能,让整个集群变成一个总体。
正确的Dockerfile是这样的:
# 使用DaoCloud的Ubuntu镜像 FROM daocloud.io/library/ubuntu:14.04 # 设置镜像做者 MAINTAINER Fundebug <help@fundebug.com> # 设置时区 RUN sudo sh -c "echo 'Asia/Shanghai' > /etc/timezone" && \ sudo dpkg-reconfigure -f noninteractive tzdata # 使用阿里云的Ubuntu镜像 RUN echo '\n\ deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\n\ deb http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\n\ deb http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\n\ deb http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\n\ deb http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse\n\ deb-src http://mirrors.aliyun.com/ubuntu/ trusty main restricted universe multiverse\n\ deb-src http://mirrors.aliyun.com/ubuntu/ trusty-security main restricted universe multiverse\n\ deb-src http://mirrors.aliyun.com/ubuntu/ trusty-updates main restricted universe multiverse\n\ deb-src http://mirrors.aliyun.com/ubuntu/ trusty-proposed main restricted universe multiverse\n\ deb-src http://mirrors.aliyun.com/ubuntu/ trusty-backports main restricted universe multiverse\n'\ > /etc/apt/sources.list # 安装node v6.10.1 RUN sudo apt-get update && sudo apt-get install -y wget # 使用淘宝镜像安装Node.js v6.10.1 RUN wget https://npm.taobao.org/mirrors/node/v6.10.1/node-v6.10.1-linux-x64.tar.gz && \ tar -C /usr/local --strip-components 1 -xzf node-v6.10.1-linux-x64.tar.gz && \ rm node-v6.10.1-linux-x64.tar.gz WORKDIR /app # 安装npm模块 ADD package.json /app/package.json # 使用淘宝的npm镜像 RUN npm install --production -d --registry=https://registry.npm.taobao.org # 添加源代码 ADD . /app # 运行app.js CMD ["node", "/app/app.js"]
有几点值得注意的地方:
使用国内DaoCloud的Docker仓库,阿里云的ubuntu镜像以及淘宝的npm镜像,不然会出事情的;
将时区设为Asia/Shanghai,不然日志的时间会不大对劲;
使用.dockerignore忽略不须要添加到Docker镜像的文件和目录,其语法与.gitigore一致;
更重要的一点是,package.json须要单独添加。Docker在构建镜像的时候,是一层一层构建的,仅当这一层有变化时,从新构建对应的层。若是package.json和源代码一块儿添加到镜像,则每次修改源码都须要从新安装npm模块,这样木有必要。因此,正确的顺序是: 添加package.json;安装npm模块;添加源代码。
使用docker build命令构建Docker镜像
sudo docker build -t fundebug/nodejs .
其中,-t选项用于指定镜像的名称。
使用docker images命令查看Docker镜像
sudo docker images REPOSITORY TAG IMAGE ID CREATED SIZE fundebug/nodejs latest 64530ce811a1 32 minutes ago 266.4 MB daocloud.io/library/ubuntu 14.04 b969ab9f929b 9 weeks ago 188 MB
可知,fundebug/nodejs镜像的大小为266.4MB,在ubuntu镜像的基础上增长了80MB左右。
使用docker run命令运行Docker容器
sudo docker run -d --net=host --name=hello-fundebug fundebug/nodejs
其中,-d选项表示容器在后台运行;--net选项指定容器的网络模式,host表示与主机共享网络;--name指定了容器的名称。
使用docker ps命令查看Docker容器
sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e8eb5473970c fundebug/nodejs "node /app/app.js" 37 minutes ago Up 37 minutes hello-
可知,COMMAND为"node /app/app.js",表示容器中运行的命令。这是咱们再Dockerfile中使用CMD指定的。不妨使用docker exec命令在容器内执行ps命令查看容器内的进程:
sudo docker exec hello-fundebug ps -f UID PID PPID C STIME TTY TIME CMD root 1 0 0 15:14 ? 00:00:00 node /app/app.js
可知,容器内的1号进程即为node进程node /app/app.js。在Linux中,PID为1进程按说是惟一的,即init进程。可是,容器使用了内核的Namespace机制,为容器建立了独立的PID空间,所以容器中也有1号进程。
使用curl命令访问:
curl localhost:3000 Hello Fundebug!
一方面,使用Docker可以带来很大益处;另外一方面,引入Docker必然会有不少挑战,须要熟悉Docker才能应对自如。想必这是一个艰难的决定。若是从长远的角度来看,Docker正在成为应用开发,部署,发布的标准技术,也许咱们不得不用开放的心态对待它。
做为Node.js开发者,真正理解Docker可能须要一些时间,可是它能够给你带来不少便利。欢迎加入咱们Fundebug的Node.js技术交流群,老司机带你玩转酷炫的Docker技术。
版权声明:
转载时请注明做者Fundebug以及本文地址:
https://blog.fundebug.com/2017/03/27/nodejs-docker/