Node.js Best Practices - How to Become a Better Developer in 2017提到的幾點,咱們Fundebug深有同感:html
想必你們都知道ES6,Promise以及LTS,那Docker是啥玩意啊?翻遍Node文檔也沒見蹤影啊!node
Docker是最流行的的容器工具,沒有之一。本文並不打算深刻介紹Docker,不過能夠從幾個簡單的角度來理解Docker。linux
在Linux中,全部的進程構成了一棵樹。可使用pstree命令進行查看:git
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。github
Docker將不一樣應用的進程隔離了起來,這些被隔離的進程就是一個個容器。隔離是基於兩個Linux內核機制實現的,Namesapce和Cgroups。redis
Namespace能夠從UTD、IPC、PID、Mount,User和Network的角度隔離進程。好比,不一樣的進程將擁有不一樣PID空間,這樣容器中的進程將看不到主機上的進程,也看不到其餘容器中的進程。這與Node.js中模塊化以隔離變量的命名空間的思想是殊途同歸的。mongodb
經過Cgroups,能夠限制進程對CPU,內存等資源的使用。簡單地說,咱們能夠經過Cgroups指定容器只能使用1G內存。docker
從進程角度理解Docker,那每個Docker容器就是被隔離的進程及其子進程。上文pstree的輸出中能夠分辨出2個容器: mongodb和redis。shell
基於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的價值。
剛開始,你只須要寫一個Node.js程序,掛載一個靜態網站;而後,你作了一個用戶帳號系統,這時須要數據庫了,好比說MySQL; 後來,爲了提高性能,你引入了Memcached緩存;終於有一天,你決定把先後端分離,這樣能夠提升開發效率;當用戶愈來愈多,你又不得不使用Nginx作反向代理; 對了,隨着功能愈來愈多,你的應用依賴也會愈來愈多...總之,你的應用架構只會愈來愈複雜。不一樣的組件的安裝,配置與運行步驟各不相同,因而你不得不寫一個很長的文檔給新同事,只爲了讓他搭建一個開發環境。
使用Docker的話,你能夠爲不一樣的組件逐一編寫Dockerfile,分別構建鏡像,而後運行在各個容器中。這樣作,將複雜的架構統一了,全部組件的安裝和運行步驟統一爲幾個簡單的命令:
一般,你會有開發,測試和生產服務器,對於某些應用,還會須要進行構建。不一樣步驟的依賴會有一些不一樣,而且在不一樣的服務器上執行。若是手動地在不一樣的服務器上安裝依賴,是件很麻煩的事情。好比說,當你須要爲Node.js應用添加一個新的npm模塊,或者升級一下Node.js,是否是得重複操做不少次?友情提示一下,手動敲命令是極易出錯的,有些失誤會致使致命的後果(參考最近Gitlab誤刪數據庫與AWS的S3故障)。
若是使用Docker的話,開發、構建、測試、生產將所有在Docker容器中執行,你須要爲不一樣步驟編寫不一樣的Dockerfile。當依賴變化時,僅須要稍微修改Dockerfile便可。結合構建工具Jenkins,就能夠將整個部署流程自動化。
另外一方面,Dockerfile將Docker鏡像描述得很是精準,可以保證很強的一致性。好比,操做系統的版本,Node.js的版本,NPM模塊的版本等。這就意味着,在本地開發環境運行成功的鏡像,在構建、測試、生產環境中也沒有問題。還有,不一樣的Docker容器是依賴於不一樣的Docker鏡像,這樣他們互不干擾。好比,兩個Node.js應用能夠分別使用不一樣版本的Node.js。
架構規模愈來愈大的時候,你有必要引入集羣了。這就意味着,服務器由1臺變成了多臺,同一個應用須要運行多個備份來分擔負載。固然,你能夠手動對集羣的功能進行劃分: Nginx服務器,Node.js服務器,MySQL服務器,測試服務器,生產服務器...這樣作的好處是簡單粗暴;也能夠說財大氣粗,由於資源閒置會很是嚴重。還有一點,每次新增節點的時候,你就不得不花大量時間進行安裝與配置,這實際上是一種低效的重複勞動。
下載Docker鏡像以後,Docker容器能夠運行在集羣的任何一個節點。一方面,各個組件能夠共享主機,且互不干擾;另外一方面,也不須要在集羣的節點上安裝和配置任何組件。至於整個Docker集羣的管理,業界有不少成熟的解決方案,例如Mesos,Kubernetes與Docker Swarm。這些集羣系統提供了調度,服務發現,負載均衡等功能,讓整個集羣變成一個總體。
正確的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"]
複製代碼
有幾點值得注意的地方:
更重要的一點是,package.json須要單獨添加。Docker在構建鏡像的時候,是一層一層構建的,僅當這一層有變化時,從新構建對應的層。若是package.json和源代碼一塊兒添加到鏡像,則每次修改源碼都須要從新安裝npm模塊,這樣木有必要。因此,正確的順序是: 添加package.json;安裝npm模塊;添加源代碼。
使用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 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正在成爲應用開發,部署,發佈的標準技術,也許咱們不得不用開放的心態對待它。
做爲Node.js開發者,真正理解Docker可能須要一些時間,可是它能夠給你帶來不少便利。歡迎加入咱們Fundebug的Node.js技術交流羣,老司機帶你玩轉酷炫的Docker技術。
Fundebug專一於JavaScript、微信小程序、微信小遊戲、支付寶小程序、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了7億+錯誤事件,獲得了Google、360、金山軟件、百姓網等衆多知名用戶的承認。歡迎免費試用!
轉載時請註明做者Fundebug以及本文地址:
blog.fundebug.com/2017/03/27/…