忘了從哪年起,Docker就開始流行起來,根據(jù)一個(gè)兄弟的描述來說,Docker剛開始起源于一家快要倒閉的公司,因?yàn)榭煲归]就將自己的項(xiàng)目開源出來,結(jié)果受到了廣大開發(fā)者的關(guān)注,開始風(fēng)靡起來。介紹一個(gè)東西怎么用之前,我們先弄清楚兩點(diǎn),這個(gè)東西是個(gè)什么東西,這個(gè)東西對(duì)我們有什么用。
Docker是個(gè)什么東西
Docker有兩個(gè)基礎(chǔ)的概念,一個(gè)是容器,一個(gè)是鏡像,我們打個(gè)比方,容器就是盒子,鏡像就是模具或圖紙,應(yīng)用就是零件,這里泛指的應(yīng)用不應(yīng)該理解成一個(gè)完整的應(yīng)用,比如一臺(tái)服務(wù)器,可能可以分成數(shù)據(jù)庫(kù),應(yīng)用服務(wù)器,nginx應(yīng)用等等。盒子是用來裝零件的,也可以用來裝更小的盒子。鏡像可以用來生產(chǎn)零件,并將零件安裝到盒子里面變成一個(gè)可以用的道具。而道具就是我們最后完整的應(yīng)用。Docker就是我們從組裝應(yīng)用到發(fā)布應(yīng)用的一整套方案。
Docker對(duì)我們有什么用
Docker為我們一整套的構(gòu)建和發(fā)布應(yīng)用方案,它將一個(gè)應(yīng)用的發(fā)布分為兩步,一步是構(gòu)建,一步是運(yùn)行,那么對(duì)于一個(gè)構(gòu)建出來的應(yīng)用,它可以有多個(gè)運(yùn)行方案,我們可以在運(yùn)行的時(shí)候設(shè)置端口,環(huán)境等等,我們可以利用這個(gè)特性來很方便的實(shí)現(xiàn),多環(huán)境,多端口的部署,或者簡(jiǎn)單的實(shí)現(xiàn)node的負(fù)載均衡,多服務(wù)對(duì)于node來講是比較重要的,雖然有類似于pm2之類的模塊已經(jīng)實(shí)現(xiàn)了。其次,Docker在構(gòu)建應(yīng)用的時(shí)候,根據(jù)描述重新構(gòu)建應(yīng)用的環(huán)境,和宿主機(jī)的環(huán)境無關(guān),所以只要宿主機(jī)上安裝了docker,就不需要在乎宿主機(jī)的運(yùn)行環(huán)境,對(duì)于安裝了docker的機(jī)器而言,就是構(gòu)建一次,處處運(yùn)行。
Docker的運(yùn)作
環(huán)境的描述 ---- 構(gòu)建 ------> 鏡像 ------ 運(yùn)行 -----> 容器
解釋一下上面的圖,首先我們會(huì)對(duì)我們需要的環(huán)境進(jìn)行描述,Docker提供了一個(gè)Dockerfile來描述我們需要的環(huán)境,Dockerfile大致是這樣的:
FROM node:6.10.2
WORKDIR /usr/src/app
ADD package.json /usr/src/app
RUN cnpm install
ADD . /usr/src/app
RUN mkdir -p logs
CMD ["pm2-docker", "start", "pm2-cyd-docker.json"]
具體的語法就不介紹了,在官網(wǎng)可以查到,可以簡(jiǎn)單的看出,我們要做的就是把應(yīng)用需要的環(huán)境搭建好。當(dāng)我們?nèi)?gòu)建這個(gè)鏡像的時(shí)候,我們可以發(fā)現(xiàn)每當(dāng)我們執(zhí)行一條語句之后,docker都會(huì)為我們生成一個(gè)sh1值,這個(gè)值是用來標(biāo)志執(zhí)行到當(dāng)前生成的鏡像,docker的鏡像是基于棧的,這很符合我們搭建環(huán)境的習(xí)慣,比如我們?cè)谛枰粋€(gè)可以運(yùn)行node服務(wù)器的服務(wù)的時(shí)候,首先我們會(huì)需要一個(gè)簡(jiǎn)單的操作系統(tǒng),可能是centos或者ubuntu,之后我們需要curl之類的基礎(chǔ)命令行函數(shù)來獲取到node的包,之后我們可能才能安裝node環(huán)境,然后我們可以安裝一些基于node的模塊。docker做的是根據(jù)我們的這些構(gòu)建需求,一步一步去構(gòu)建,每一次構(gòu)建的時(shí)候都會(huì)為當(dāng)前的版本打上一個(gè)標(biāo)志,并緩存這一步構(gòu)建的鏡像。當(dāng)我們對(duì)項(xiàng)目進(jìn)行了更改,再次去構(gòu)建這個(gè)鏡像的時(shí)候,docker會(huì)根據(jù)每條命令當(dāng)前的鏡像和構(gòu)建的命令來考慮需不需要再次去執(zhí)行這次構(gòu)建,比如我們的鏡像總是基于ubuntu鏡像,但是每次的第一步都會(huì)去執(zhí)行安裝node環(huán)境的命令,docker就會(huì)跳過這一步,直接才能夠緩存中獲取到上一次執(zhí)行這個(gè)命令產(chǎn)生的鏡像。從這里我們看出,我們docker的構(gòu)建是可以從一個(gè)現(xiàn)成的鏡像開始構(gòu)建的,就如同例子里面FROM node:6.10.2,是從一個(gè)現(xiàn)成的鏡像開始構(gòu)建的。所以我們可以制作一些基礎(chǔ)的鏡像來防止一些重復(fù)的構(gòu)建,加快構(gòu)建速度。
解釋完了構(gòu)建,我們現(xiàn)在得到了鏡像,那么接下來解釋一下運(yùn)行。一個(gè)構(gòu)建好的鏡像,就是一個(gè)待運(yùn)行且安裝好所有環(huán)境的應(yīng)用,可以非常方便的將應(yīng)用運(yùn)行起來。所以運(yùn)行不是一個(gè)需要很多解釋的地方,但是運(yùn)行的設(shè)置,可以稍作解釋,來幫助我們更好的理解docker的優(yōu)勢(shì)。首先,docker的在運(yùn)行應(yīng)用的時(shí)候可以映射docker容器內(nèi)外的端口,這讓我們?cè)谶\(yùn)行的時(shí)候可以隨意制定端口,當(dāng)然了,這也沒有什么了不起的,畢竟幾乎現(xiàn)在的服務(wù)器應(yīng)用在啟動(dòng)的時(shí)候都可以通過命令設(shè)置端口,不過docker現(xiàn)在不用管內(nèi)部具體是什么類型的應(yīng)用,也不用管這個(gè)應(yīng)用具體設(shè)置端口的方式,就可以設(shè)置端口的出口了。docker可以設(shè)置當(dāng)前的環(huán)境變量,這和我們直接啟動(dòng)應(yīng)用一樣,就不多說了。docker提供了可共享的資源的映射,docker在運(yùn)行的時(shí)候可以指定一些資源對(duì)于宿主機(jī)的映射,這讓我們可以在運(yùn)行時(shí),將一些緩存文件或者日志文件在宿主機(jī)上讀寫,可以加快服務(wù)啟動(dòng)的速度,也可以靈活的修改處理應(yīng)用產(chǎn)生或者使用的文件。
除此之外docker還提供了一系列容器內(nèi)應(yīng)用設(shè)置的修改,啟動(dòng),停止,重啟, 日志的管理
到此為止,我們?cè)斐隽艘粋€(gè)盒子,而線上的應(yīng)用往往是由很多的應(yīng)用綜合而成的,所以我們需要啟動(dòng)一堆盒子,關(guān)于這一堆盒子的怎么一起運(yùn)行起來,docker提供了docker-compose,看一下官方的例子:
services:
web:
build: .
ports:
- "5000:5000"
volumes:
- .:/code
redis:
image: "redis:alpine"
這里描述了兩個(gè)應(yīng)用,一個(gè)應(yīng)用是服務(wù)器應(yīng)用,一個(gè)是redis應(yīng)用,通過這個(gè)文件的描述,docker會(huì)構(gòu)建兩個(gè)應(yīng)用的鏡像,將兩個(gè)鏡像通過描述的配置啟動(dòng)起來。這種整合的配置相比于每個(gè)小應(yīng)用各自啟動(dòng)各自配置而言,更加利于多應(yīng)用的配置, 配置顯而易見,可讀性好。
接下來稍微講解一下最近項(xiàng)目的經(jīng)驗(yàn),以及遇到的問題:
我們的第一個(gè)目標(biāo)是docker化一個(gè)前端項(xiàng)目,這個(gè)項(xiàng)目是基于Node.js服務(wù)的,webpack打包了若干個(gè)零零散散的小項(xiàng)目。
傳統(tǒng)的node.js的docker策略一般是這樣的:
FROM node:6.10.2
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ADD package.json .
RUN npm install
ADD . .
RUN npm build
RUN npm upload:assets
CMD ["pm2", "start", ".....json"]
在這個(gè)項(xiàng)目里應(yīng)用,會(huì)發(fā)現(xiàn),npm install的速度非常慢,因?yàn)橐惭b的npm包往往非常多,其次就是上傳圖片資源,因?yàn)槿绻覀儾痪彺嬉呀?jīng)上傳過的圖片的話,我們每次都會(huì)上傳所有的圖片,那么時(shí)間又會(huì)大幅度增加。再加上本來就比較長(zhǎng)的打包時(shí)間,用這個(gè)方案構(gòu)建應(yīng)用,我們花了15~30分鐘。這實(shí)在是太長(zhǎng)了,所以我們需要優(yōu)化這個(gè)方案來適應(yīng)一個(gè)前端項(xiàng)目,總結(jié)了一下我們的問題主要是緩存問題,因?yàn)闆]有任何的緩存,所以我們需要每次都下載npm包,每次都要上傳所有的圖片資源,所以我們需要緩存住一些文件。所以思考過之后,我覺得應(yīng)該把下載依賴和打包的事情放在運(yùn)行的時(shí)候,所以Dockerfile就變成了這么簡(jiǎn)單
FROM node:6.10.2
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ADD . .
CMD ["sh", "start.sh"]
然后將其余的部分放到了start.sh的腳本中,在運(yùn)行的時(shí)候添加了node_modules和上傳圖片緩存的宿主機(jī)映射,經(jīng)過這次的改進(jìn),在構(gòu)建的時(shí)候幾乎是秒構(gòu)建,在運(yùn)行的時(shí)候,幾乎只存在一個(gè)打包的時(shí)間和少量上傳資源的時(shí)間,加起來也就不到5分鐘。
完成了這個(gè)項(xiàng)目之后,又來了一個(gè)新的挑戰(zhàn),現(xiàn)在的目標(biāo)項(xiàng)目也是一個(gè)node 項(xiàng)目,前端上會(huì)分成若干個(gè)項(xiàng)目,和上面那個(gè)不痛,這些前端項(xiàng)目結(jié)構(gòu)都差不多,并且都可以獨(dú)立打包,而且這個(gè)項(xiàng)目之后會(huì)有越來越多的這這樣的前端項(xiàng)目往里塞,如果使用和上面那個(gè)項(xiàng)目相同的構(gòu)建和運(yùn)行方式,之后的打包時(shí)間會(huì)不停增長(zhǎng),可能會(huì)產(chǎn)生不可預(yù)期的問題。所以我們需要改變策略。
針對(duì)這個(gè)項(xiàng)目,我們把項(xiàng)目拆分開來,node服務(wù)器只會(huì)docker服務(wù)器邏輯方面的應(yīng)用,而每個(gè)前端的子項(xiàng)目中,每個(gè)子項(xiàng)目也能Docker成一個(gè)應(yīng)用,那么一個(gè)前端子項(xiàng)目,只能編譯出一些js、css文件和一些圖片,怎么變成一個(gè)應(yīng)用呢。對(duì)此,參照webpack-dev-server的思想,制作了一個(gè)只提供轉(zhuǎn)發(fā)功能的服務(wù)器來承載并轉(zhuǎn)發(fā)頁面的請(qǐng)求,將請(qǐng)求轉(zhuǎn)發(fā)到對(duì)應(yīng)的node服務(wù)器docker應(yīng)用。這樣的修改讓應(yīng)用的邏輯更加清晰,獨(dú)立了各個(gè)應(yīng)用,但是也讓服務(wù)變得更加零散,難以管理,之后可能會(huì)配合docker-compose和nginx整合成一個(gè)大型應(yīng)用來處理這樣的問題,待問題解決之后,再來補(bǔ)充。