當Node.js遇見Docker

AlvinGraynd 7年前發布 | 95K 次閱讀 Docker Node.js Node.js擴展

  • 使用ES6

  • 使用Promise

  • 使用LTS

  • 使用 Docker

  • ...

想必大家都知道ES6,Promise以及LTS,那 Docker 是啥玩意啊?翻遍 Node文檔 也沒見蹤跡啊!

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 /</code></pre>

其中, FROMRUNADD 為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);</code></pre>

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

通常,你會有 開發測試生產 服務器,對于某些應用,還會需要進行 構建 。不同步驟的依賴會有一些不同,并且在不同的服務器上執行。如果手動地在不同的服務器上安裝依賴,是件很麻煩的事情。比如說,當你需要為Node.js應用添加一個新的npm模塊,或者升級一下Node.js,是不是得重復操作很多次?友情提示一下,手動敲命令是極易出錯的,有些失誤會導致致命的后果(參考最近Gitlab誤刪數據庫與AWS的S3故障)。

如果使用Docker的話, 開發構建測試生產 將全部在Docker容器中執行,你需要為不同步驟編寫不同的Dockerfile。當依賴變化時,僅需要稍微修改Dockerfile即可。結合構建工具 Jenkins ,就可以將整個部署流程自動化。

另一方面,Dockerfile將Docker鏡像描述得非常精準,能夠保證很強的一致性。比如,操作系統的版本,Node.js的版本,NPM模塊的版本等。這就意味著,在本地開發環境運行成功的鏡像,在 構建測試生產 環境中也沒有問題。還有,不同的Docker容器是依賴于不同的Docker鏡像,這樣他們互不干擾。比如,兩個Node.js應用可以分別使用不同版本的Node.js。

從集群管理角度理解Docker

架構規模越來越大的時候,你有必要引入集群了。這就意味著,服務器由1臺變成了多臺,同一個應用需要運行多個備份來分擔負載。當然,你可以手動對集群的功能進行劃分: Nginx服務器,Node.js服務器,MySQL服務器,測試服務器,生產服務器...這樣做的好處是簡單粗暴;也可以說財大氣粗,因為資源閑置會非常嚴重。還有一點,每次新增節點的時候,你就不得不花大量時間進行安裝與配置,這其實是一種低效的重復勞動。

下載Docker鏡像之后,Docker容器可以運行在集群的任何一個節點。一方面,各個組件可以共享主機,且互不干擾;另一方面,也不需要在集群的節點上安裝和配置任何組件。至于整個Docker集群的管理,業界有很多成熟的解決方案,例如 Mesos , Kubernetes 與 Docker Swarm 。這些集群系統提供了 調度服務發現負載均衡 等功能,讓整個集群變成一個整體。

如何用Docker?

編寫Dockerfile

正確的 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"]</code></pre>

有幾點值得注意的地方:

  • 使用國內 DaoCloud 的Docker倉庫,阿里云的ubuntu鏡像以及淘寶的npm鏡像,否則會出事情的;

  • 將時區設為Asia/Shanghai,否則日志的時間會不大對勁;

  • 使用.dockerignore忽略不需要添加到Docker鏡像的文件和目錄,其語法與.gitigore一致;

更重要的一點是, package.json需要單獨添加 。Docker在構建鏡像的時候,是一層一層構建的,僅當這一層有變化時,重新構建對應的層。如果package.json和源代碼一起添加到鏡像,則每次修改源碼都需要重新安裝npm模塊,這樣木有必要。所以,正確的順序是: 添加package.json;安裝npm模塊;添加源代碼。

構建Docker鏡像

使用 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容器

使用 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才能應對自如。 想必這是一個艱難的決定 。如果從長遠的角度來看,Docker正在成為應用開發,部署,發布的標準技術,也許我們不得不用開放的心態對待它。

作為Node.js開發者,真正理解Docker可能需要一些時間,但是它可以給你帶來很多便利。

參考鏈接

 

 

來自:https://segmentfault.com/a/1190000008945039

 

 本文由用戶 AlvinGraynd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!