手记

当Node.js遇见Docker


Node.js Best Practices - How to Become a Better Developer in 2017提到的几点,我们Fundebug深有同感:

使用ES6

使用Promise

使用LTS

使用Docker

...

想必大家都知道ES6,Promise以及LTS,那Docker是啥玩意啊?翻遍Node文档也没见踪迹啊!

当Node.js遇见Docker

GitHub仓库: Fundebug/nodejs-docker

什么是Docker?

Docker是最流行的的容器工具,没有之一。本文并不打算深入介绍Docker,不过可以从几个简单的角度来理解Docker。

从进程的角度理解Docker

在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。

从文件的角度理解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构建大概会失败,仅作为参考):

# 基于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?

当你的系统越来越复杂的时候,你会发现Docker的价值。

从应用架构角度理解Docker

刚开始,你只需要写一个Node.js程序,挂载一个静态网站;然后,你做了一个用户账号系统,这时需要数据库了,比如说MySQL; 后来,为了提升性能,你引入了Memcached缓存;终于有一天,你决定把前后端分离,这样可以提高开发效率;当用户越来越多,你又不得不使用Nginx做反向代理; 对了,随着功能越来越多,你的应用依赖也会越来越多...总之,你的应用架构只会越来越复杂。不同的组件的安装,配置与运行步骤各不相同,于是你不得不写一个很长的文档给新同事,只为了让他搭建一个开发环境。

使用Docker的话,你可以为不同的组件逐一编写Dockerfile,分别构建镜像,然后运行在各个容器中。这样做,将复杂的架构统一了,所有组件的安装和运行步骤统一为几个简单的命令:

构建Docker镜像: docker build

上传Docker镜像: docker push

下载Docker镜像: docker pull

运行Docker容器: docker run

从应用部署角度理解Docker

通常,你会有开发,测试和生产服务器,对于某些应用,还会需要进行构建。不同步骤的依赖会有一些不同,并且在不同的服务器上执行。如果手动地在不同的服务器上安装依赖,是件很麻烦的事情。


0人推荐
随时随地看视频
慕课网APP