Node.js Best Practices - How to Become a Better Developer in 2017提到的幾點,我們Fundebug深有同感:
- 使用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 /
複製程式碼
其中,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
通常,你會有開發,測試和生產伺服器,對於某些應用,還會需要進行構建。不同步驟的依賴會有一些不同,並且在不同的伺服器上執行。如果手動地在不同的伺服器上安裝依賴,是件很麻煩的事情。比如說,當你需要為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"]
複製程式碼
有幾點值得注意的地方:
- 使用國內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可能需要一些時間,但是它可以給你帶來很多便利。歡迎加入我們Fundebug的Node.js技術交流群,老司機帶你玩轉酷炫的Docker技術。
參考連結
- DOCKER基礎技術:LINUX NAMESPACE(上)
- DOCKER基礎技術:LINUX NAMESPACE(下)
- DOCKER基礎技術:LINUX CGROUP
- 從GITLAB誤刪除資料庫想到的
- AWS 的 S3 故障回顧和思考
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了7億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!
版權宣告
轉載時請註明作者Fundebug以及本文地址:
blog.fundebug.com/2017/03/27/…