您好,登錄后才能下訂單哦!
Node.js Best Practices - How to Become a Better Developer in 2017提到的幾點,我們Fundebug深有同感:
想必大家都知道ES6,Promise以及LTS,那Docker是啥玩意啊?翻遍Node文檔也沒見蹤跡啊!
GitHub倉庫: Fundebug/nodejs-docker
Docker是最流行的的容器工具,沒有之一。本文并不打算深入介紹Docker,不過可以從幾個簡單的角度來理解Docker。
在Linux中,所有的進(jìn)程構(gòu)成了一棵樹??梢允褂胮stree命令進(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。
基于Namespace與Cgroups的容器工具其實早已存在,例如Linux-VServer,OpenVZ,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,RUN與ADD為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)行版本管理。
當(dāng)你的系統(tǒng)越來越復(fù)雜的時候,你會發(fā)現(xiàn)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)一為幾個簡單的命令:
通常,你會有開發(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。
架構(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è)界有很多成熟的解決方案,例如Mesos,Kubernetes與Docker Swarm。這些集群系統(tǒng)提供了調(diào)度,服務(wù)發(fā)現(xiàn),負(fù)載均衡等功能,讓整個集群變成一個整體。
正確的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"]
有幾點值得注意的地方:
更重要的一點是,package.json需要單獨添加。Docker在構(gòu)建鏡像的時候,是一層一層構(gòu)建的,僅當(dāng)這一層有變化時,重新構(gòu)建對應(yīng)的層。如果package.json和源代碼一起添加到鏡像,則每次修改源碼都需要重新安裝npm模塊,這樣木有必要。所以,正確的順序是: 添加package.json;安裝npm模塊;添加源代碼。
使用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 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必然會有很多挑戰(zhàn),需要熟悉Docker才能應(yīng)對自如。想必這是一個艱難的決定。如果從長遠(yuǎn)的角度來看,Docker正在成為應(yīng)用開發(fā),部署,發(fā)布的標(biāo)準(zhǔn)技術(shù),也許我們不得不用開放的心態(tài)對待它。
作為Node.js開發(fā)者,真正理解Docker可能需要一些時間,但是它可以給你帶來很多便利。
Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java實時BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計處理了7億+錯誤事件,得到了Google、360、金山軟件、百姓網(wǎng)等眾多知名用戶的認(rèn)可。歡迎免費試用!
轉(zhuǎn)載時請注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/03/27/nodejs-docker/
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。