當(dāng)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文檔也沒見蹤跡啊!

Paste_Image.png

GitHub倉庫: Fundebug/nodejs-docker

什么是Docker?

Docker是最流行的的容器工具,沒有之一。本文并不打算深入介紹Docker练链,不過可以從幾個簡單的角度來理解Docker先壕。

從進(jìn)程的角度理解Docker

在Linux中瘩扼,所有的進(jìn)程構(gòu)成了一棵樹±牛可以使用pstree命令進(jìn)行查看:

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進(jìn)程為所有進(jìn)程的根(root),其PID為1谆棺。

Docker將不同應(yīng)用的進(jìn)程隔離了起來栽燕,這些被隔離的進(jìn)程就是一個個容器。隔離是基于兩個Linux內(nèi)核機(jī)制實現(xiàn)的改淑,Namesapce和Cgroups碍岔。

Namespace可以從UTD、IPC朵夏、PID蔼啦、Mount,User和Network的角度隔離進(jìn)程仰猖。比如捏肢,不同的進(jìn)程將擁有不同PID空間掠河,這樣容器中的進(jìn)程將看不到主機(jī)上的進(jìn)程,也看不到其他容器中的進(jìn)程猛计。這與Node.js中模塊化以隔離變量的命名空間的思想是異曲同工的唠摹。

通過Cgroups,可以限制進(jìn)程對CPU奉瘤,內(nèi)存等資源的使用勾拉。簡單地說,我們可以通過Cgroups指定容器只能使用1G內(nèi)存盗温。

從進(jìn)程角度理解Docker藕赞,那每一個Docker容器就是被隔離的進(jìn)程及其子進(jìn)程。上文pstree的輸出中可以分辨出2個容器: mongodb和redis卖局。

從文件的角度理解Docker

基于Namespace與Cgroups的容器工具其實早已存在斧蜕,例如Linux-VServerOpenVZ砚偶,LXC批销。然而,真正引爆容器技術(shù)的卻是后來者Docker染坯。為什么呢均芽?個人覺得是因為Docker鏡像以及Dockerfile

在Linux中单鹿,一切皆文件掀宋,進(jìn)程的運行離不開各種各樣的文件。跑一個簡單的Node.js程序仲锄,傳統(tǒng)的做法是手動安裝各種依賴然后運行劲妙;而Docker則是將所有依賴(包括操作系統(tǒng),Node儒喊,NPM模塊镣奋,源代碼)打包到一個Docker鏡像中,然后基于這個鏡像運行容器澄惊。

Docker鏡像可以通過Docker倉庫共享給其他人唆途,這樣他們只需要下載鏡像即可運行程序富雅。想象一下掸驱,當(dāng)我們需要在另一臺主機(jī)(比如生產(chǎn)服務(wù)器,新同事的機(jī)器)上運行一個Node.js應(yīng)用没佑,僅僅需要下載對應(yīng)的Docker鏡像就可以了毕贼,是不是很方便呢?

Docker鏡像可以通過文本文件蛤奢,即Dockerfile進(jìn)行定義鬼癣。不妨看一個簡單的例子(由于不可抗力陶贼,這個Dockerfile構(gòu)建大概會失敗,僅作為參考):

# 基于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拜秧,RUNADD為Dockerfile命令。結(jié)合注釋章郁,該Dockerfile的含義非常直白枉氮。基于這個Dockerfile暖庄,使用docker build命令就可以構(gòu)建對應(yīng)的Docker鏡像聊替。基于這個Docker鏡像培廓,就可以運行Docker容器來執(zhí)行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進(jìn)行版本管理肩钠。

為啥用Docker?

當(dāng)你的系統(tǒng)越來越復(fù)雜的時候泣港,你會發(fā)現(xiàn)Docker的價值。

從應(yīng)用架構(gòu)角度理解Docker

剛開始价匠,你只需要寫一個Node.js程序爷速,掛載一個靜態(tài)網(wǎng)站;然后霞怀,你做了一個用戶賬號系統(tǒng)惫东,這時需要數(shù)據(jù)庫了,比如說MySQL; 后來毙石,為了提升性能廉沮,你引入了Memcached緩存;終于有一天徐矩,你決定把前后端分離滞时,這樣可以提高開發(fā)效率;當(dāng)用戶越來越多滤灯,你又不得不使用Nginx做反向代理; 對了坪稽,隨著功能越來越多,你的應(yīng)用依賴也會越來越多...總之鳞骤,你的應(yīng)用架構(gòu)只會越來越復(fù)雜窒百。不同的組件的安裝,配置與運行步驟各不相同豫尽,于是你不得不寫一個很長的文檔給新同事篙梢,只為了讓他搭建一個開發(fā)環(huán)境

使用Docker的話美旧,你可以為不同的組件逐一編寫Dockerfile渤滞,分別構(gòu)建鏡像贬墩,然后運行在各個容器中。這樣做妄呕,將復(fù)雜的架構(gòu)統(tǒng)一了陶舞,所有組件的安裝和運行步驟統(tǒng)一為幾個簡單的命令:

  • 構(gòu)建Docker鏡像: docker build
  • 上傳Docker鏡像: docker push
  • 下載Docker鏡像: docker pull
  • 運行Docker容器: docker run
從應(yīng)用部署角度理解Docker

通常,你會有開發(fā)绪励,測試生產(chǎn)服務(wù)器吊说,對于某些應(yīng)用,還會需要進(jìn)行構(gòu)建优炬。不同步驟的依賴會有一些不同颁井,并且在不同的服務(wù)器上執(zhí)行。如果手動地在不同的服務(wù)器上安裝依賴蠢护,是件很麻煩的事情雅宾。比如說,當(dāng)你需要為Node.js應(yīng)用添加一個新的npm模塊葵硕,或者升級一下Node.js眉抬,是不是得重復(fù)操作很多次?友情提示一下懈凹,手動敲命令是極易出錯的蜀变,有些失誤會導(dǎo)致致命的后果(參考最近Gitlab誤刪數(shù)據(jù)庫與AWS的S3故障)。

如果使用Docker的話介评,開發(fā)库北、構(gòu)建測試们陆、生產(chǎn)將全部在Docker容器中執(zhí)行寒瓦,你需要為不同步驟編寫不同的Dockerfile。當(dāng)依賴變化時坪仇,僅需要稍微修改Dockerfile即可杂腰。結(jié)合構(gòu)建工具Jenkins,就可以將整個部署流程自動化椅文。

另一方面喂很,Dockerfile將Docker鏡像描述得非常精準(zhǔn),能夠保證很強(qiáng)的一致性皆刺。比如少辣,操作系統(tǒng)的版本,Node.js的版本芹橡,NPM模塊的版本等毒坛。這就意味著,在本地開發(fā)環(huán)境運行成功的鏡像林说,在構(gòu)建煎殷、測試生產(chǎn)環(huán)境中也沒有問題腿箩。還有豪直,不同的Docker容器是依賴于不同的Docker鏡像,這樣他們互不干擾珠移。比如弓乙,兩個Node.js應(yīng)用可以分別使用不同版本的Node.js。

從集群管理角度理解Docker

架構(gòu)規(guī)模越來越大的時候钧惧,你有必要引入集群了暇韧。這就意味著,服務(wù)器由1臺變成了多臺浓瞪,同一個應(yīng)用需要運行多個備份來分擔(dān)負(fù)載懈玻。當(dāng)然,你可以手動對集群的功能進(jìn)行劃分: Nginx服務(wù)器乾颁,Node.js服務(wù)器涂乌,MySQL服務(wù)器,測試服務(wù)器英岭,生產(chǎn)服務(wù)器...這樣做的好處是簡單粗暴湾盒;也可以說財大氣粗,因為資源閑置會非常嚴(yán)重诅妹。還有一點罚勾,每次新增節(jié)點的時候,你就不得不花大量時間進(jìn)行安裝與配置吭狡,這其實是一種低效的重復(fù)勞動荧库。

下載Docker鏡像之后,Docker容器可以運行在集群的任何一個節(jié)點赵刑。一方面分衫,各個組件可以共享主機(jī),且互不干擾般此;另一方面蚪战,也不需要在集群的節(jié)點上安裝和配置任何組件。至于整個Docker集群的管理铐懊,業(yè)界有很多成熟的解決方案邀桑,例如MesosKubernetesDocker Swarm科乎。這些集群系統(tǒng)提供了調(diào)度壁畸,服務(wù)發(fā)現(xiàn)負(fù)載均衡等功能,讓整個集群變成一個整體捏萍。

如何用Docker?

編寫Dockerfile

正確的Dockerfile是這樣的:

# 使用DaoCloud的Ubuntu鏡像
FROM daocloud.io/library/ubuntu:14.04

# 設(shè)置鏡像作者
MAINTAINER Fundebug <help@fundebug.com>

# 設(shè)置時區(qū)
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"]

有幾點值得注意的地方:

  • 使用國內(nèi)DaoCloud的Docker倉庫太抓,阿里云的ubuntu鏡像以及淘寶的npm鏡像,否則會出事情的;
  • 將時區(qū)設(shè)為Asia/Shanghai令杈,否則日志的時間會不大對勁;
  • 使用.dockerignore忽略不需要添加到Docker鏡像的文件和目錄走敌,其語法與.gitigore一致;

更重要的一點是,package.json需要單獨添加逗噩。Docker在構(gòu)建鏡像的時候掉丽,是一層一層構(gòu)建的,僅當(dāng)這一層有變化時异雁,重新構(gòu)建對應(yīng)的層捶障。如果package.json和源代碼一起添加到鏡像,則每次修改源碼都需要重新安裝npm模塊纲刀,這樣木有必要项炼。所以,正確的順序是: 添加package.json柑蛇;安裝npm模塊芥挣;添加源代碼。

構(gòu)建Docker鏡像

使用docker build命令構(gòu)建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鏡像的基礎(chǔ)上增加了80MB左右蹋砚。

運行Docker容器

使用docker run命令運行Docker容器

sudo docker run -d --net=host --name=hello-fundebug fundebug/nodejs

其中,-d選項表示容器在后臺運行摄杂;--net選項指定容器的網(wǎng)絡(luò)模式坝咐,host表示與主機(jī)共享網(wǎng)絡(luò);--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命令在容器內(nèi)執(zhí)行ps命令查看容器內(nèi)的進(jìn)程

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

可知,容器內(nèi)的1號進(jìn)程即為node進(jìn)程node /app/app.js柑船。在Linux中帽撑,PID為1進(jìn)程按說是唯一的,即init進(jìn)程鞍时。但是亏拉,容器使用了內(nèi)核的Namespace機(jī)制扣蜻,為容器創(chuàng)建了獨立的PID空間,因此容器中也有1號進(jìn)程及塘。

測試

使用curl命令訪問:

curl localhost:3000
Hello Fundebug!

是否用Docker莽使?

一方面,使用Docker能夠帶來很大益處磷蛹;另一方面吮旅,引入Docker必然會有很多挑戰(zhàn)溪烤,需要熟悉Docker才能應(yīng)對自如味咳。想必這是一個艱難的決定。如果從長遠(yuǎn)的角度來看檬嘀,Docker正在成為應(yīng)用開發(fā)槽驶,部署,發(fā)布的標(biāo)準(zhǔn)技術(shù)鸳兽,也許我們不得不用開放的心態(tài)對待它掂铐。

作為Node.js開發(fā)者,真正理解Docker可能需要一些時間揍异,但是它可以給你帶來很多便利全陨。歡迎加入我們FundebugNode.js技術(shù)交流群,老司機(jī)帶你玩轉(zhuǎn)酷炫的Docker技術(shù)衷掷。

Paste_Image.png

參考鏈接

版權(quán)聲明:
轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/03/27/nodejs-docker/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辱姨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子戚嗅,更是在濱河造成了極大的恐慌雨涛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件懦胞,死亡現(xiàn)場離奇詭異替久,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躏尉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蚯根,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胀糜,你說我怎么就攤上這事颅拦。” “怎么了僚纷?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵矩距,是天一觀的道長。 經(jīng)常有香客問我怖竭,道長锥债,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哮肚,結(jié)果婚禮上登夫,老公的妹妹穿的比我還像新娘。我一直安慰自己允趟,他們只是感情好恼策,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著潮剪,像睡著了一般涣楷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上抗碰,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天狮斗,我揣著相機(jī)與錄音,去河邊找鬼弧蝇。 笑死碳褒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的看疗。 我是一名探鬼主播沙峻,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼两芳!你這毒婦竟也來了摔寨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盗扇,失蹤者是張志新(化名)和其女友劉穎祷肯,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗隶,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡佑笋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斑鼻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒋纬。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坚弱,靈堂內(nèi)的尸體忽然破棺而出蜀备,到底是詐尸還是另有隱情,我是刑警寧澤荒叶,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布碾阁,位于F島的核電站,受9級特大地震影響些楣,放射性物質(zhì)發(fā)生泄漏脂凶。R本人自食惡果不足惜宪睹,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蚕钦。 院中可真熱鬧亭病,春花似錦、人聲如沸嘶居。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽邮屁。三九已至整袁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間樱报,已是汗流浹背葬项。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工泞当, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留迹蛤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓襟士,卻偏偏與公主長得像盗飒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子陋桂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容