Node.jsBestPractices-HowtoBecomeaBetterDeveloper2017年提到的几点时,我们Fundebug有同感:用ES6,用Promise,用LTS,用Docker……ES6、Promise、LTS大家肯定都知道,什么是Docker?我搜索了Node文档,没有找到任何踪迹!GitHub存储库:Fundebug/nodejs-docker什么是Docker?Docker是最流行的容器工具,没有之一。本文无意深入介绍Docker,但可以从几个简单的角度来理解。从进程的角度理解Docker在Linux中,所有的进程组成了一棵树。可以使用pstree命令查看:pstreeinit─┬─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-brinit进程是所有进程的根(root),其PID为1Docker将不同应用的进程进行了隔离,这些隔离的进程就是容器。隔离是基于两种Linux内核机制实现的,Namespace和Cgroups。Namespace可以从UTD、IPC、PID、Mount、User和Network的角度对进程进行隔离。比如不同的进程会有不同的PID空间,这样容器中的进程就看不到宿主机上的进程,也看不到其他容器中的进程。这类似于Node.js中模块化隔离变量命名空间的思想。通过Cgroups,可以限制进程对CPU、内存等资源的使用。简单的说,我们可以通过Cgroups指定容器只能使用1G内存。从进程的角度来理解Docker,每个Docker容器都是一个孤立的进程及其子进程。从上面pstree的输出可以区分出两个容器:mongodb和redis。从文件来看,Docker基于Namespace和Cgroups的容器工具已经存在,比如Linux-VServer、OpenVZ、LXC等。然而,真正引爆容器技术的却是后来者Docker。为什么?个人认为是Docker镜像和Dockerfile的原因。在Linux中,一切皆文件,一个进程的运行离不开各种文件。运行一个简单的Node.js程序,传统的方法是手动安装各种依赖,然后运行;而Docker将所有的依赖(包括操作系统、Node、NPM模块和源代码)打包成一个Docker镜像,然后基于这个镜像运行容器。Docker镜像可以通过Docker存储库与其他人共享,这样他们只需要下载镜像即可运行程序。想象一下,当我们需要在另一台主机(比如生产服务器,新同事的机器)上运行一个Node.js应用时,只需要下载对应的Docker镜像,是不是很方便?Docker镜像可以通过文本文件定义,即Dockerfile。我们来看一个简单的例子(由于不可抗力,这个Dockerfile可能会构建失败,仅供参考):#BasedonUbuntuFROMubuntu#InstallNode.jsandNPMRUNapt-getupdate&&apt-get-yinstallnodejsnpm#安装NPM模块:ExpressRUNnpminstallexpress#添加源代码ADDapp.js/其中FROM、RUN、ADD为Dockerfile命令。结合注释,Dockerfile的意思就很直白了。基于这个Dockerfile,使用dockerbuild命令构建对应的Docker镜像。基于这个Docker镜像,你可以运行Docker容器来执行app.js:varexpress=require("express");varapp=express();app.get("/",function(req,res){res.send("HelloFundebug!\n");});app.listen(3000);Dockerfile一方面编码Docker镜像,另一方面编码安装依赖的过程,所以我们可以像使用git一样像管理源码一样管理Dockerfile的版本。为什么要使用Docker?当你的系统变得越来越复杂时,你就会发现Docker的价值。刚开始从应用架构的角度理解Docker,只需要写一个Node.js即可,你介绍了Memcached缓存;终于有一天,你决定前后端分离,这样可以提高开发效率;当用户越来越多时,你不得不使用Nginx作为反向代理;顺便说一句,随着功能越来越多,你拥有的越多,你的应用程序就会有越多的依赖......总之,你的应用程序架构只会变得越来越复杂。不同组件的安装、配置、运行步骤不一样,所以你不得不写一个很长的文档给新来的同事,就是为了让他搭建一个开发环境。使用Docker,你可以为不同的组件一个一个地编写Dockerfile,分别构建镜像,然后在每个容器中运行。这样做统一了复杂的架构,将所有组件的安装和运行步骤统一为几个简单的命令:构建Docker镜像:dockerbuild上传Docker镜像:dockerpush下载Docker镜像:dockerpullRunaDocker容器:从应用程序部署的角度理解Docker中的dockerrun通常,您将拥有开发、测试和生产服务器,并且对于某些应用程序,还需要构建。不同步骤的依赖会略有不同,在不同的服务器上执行。在不同的服务器上手动安装依赖非常麻烦。比如,当你需要给一个Node.js应用添加一个新的npm模块,或者升级Node.js时,你是否需要重复很多次操作?友情提示,手动输入命令极易出错,有些错误甚至会导致致命的后果(参考最近的Gitlab误删数据库和AWSS3故障)。如果使用Docker,开发、构建、测试、生产都会在Docker容器中执行,需要针对不同的步骤编写不同的Dockerfile。当依赖关系发生变化时,只需要对Dockerfile稍作修改。结合构建工具Jenkins,可以实现整个部署过程的自动化。另一方面,Dockerfile对Docker镜像的描述非常准确,可以保证强一致性。比如操作系统的版本,Node.js的版本,NPM模块的版本等。这意味着在本地开发环境中运行成功的镜像在构建、测试和生产中都没有问题环境。另外,不同的Docker容器依赖不同的Docker镜像,这样它们就不会相互干扰。例如,两个Node.js应用程序可以使用不同版本的Node.js。从集群管理的角度来说,当Docker架构的规模越来越大的时候,引入集群是很有必要的。这意味着服务器从一台服务器变成了多台服务器,同一个应用程序需要运行多个备份来分担负载。当然,你可以手动划分集群的功能:Nginx服务器、Node.js服务器、MySQL服务器、测试服务器、生产服务器……这样做的好处是简单粗暴;也可以说是有钱,因为资源闲置会很严重。还有一点,每增加一个新的节点,都要花费大量的时间进行安装和配置,实际上是一种低效的重复劳动。下载Docker镜像后,Docker容器可以运行在集群的任意节点上。一方面,各个组件可以共享主机,互不干扰;另一方面,无需在集群节点上安装和配置任何组件。至于整个Docker集群的管理,业界有很多成熟的方案,比如Mesos、Kubernetes、DockerSwarm。这些集群系统提供调度、服务发现、负载均衡等功能,使整个集群成为一个整体。如何用Docker编写Dockerfile?正确的Dockerfile如下:#UseDaoCloud的Ubuntu镜像FROMdaocloud.io/library/ubuntu:14.04#SetimageauthorMAINTAINERFundebug
