從2013年3月發(fā)布第一個(gè)版本,僅僅兩年時(shí)間,Docker已經(jīng)成為最炙手可熱的開(kāi)源項(xiàng)目之一,它也受到越來(lái)越多的開(kāi)發(fā)與運(yùn)維人員的親睞。
在由InfoQ主辦的QClub之Docker專(zhuān)場(chǎng)上,七牛全棧架構(gòu)師李兆海分享了七牛如何使用Docker來(lái)構(gòu)建項(xiàng)目。項(xiàng)目的構(gòu)建經(jīng)常會(huì)困擾開(kāi)發(fā)者,因?yàn)闃?gòu)建流程可能會(huì)依賴(lài)編譯環(huán)境,并且項(xiàng)目還可能會(huì)有外部依賴(lài)。李兆海分別介紹了七牛在使用Docker構(gòu)建項(xiàng)目的幾種方案,并分析了各個(gè)方案的優(yōu)缺點(diǎn)。
為什么構(gòu)建項(xiàng)目是個(gè)問(wèn)題?
大家在工作中會(huì)發(fā)現(xiàn)項(xiàng)目構(gòu)成是一個(gè)比較復(fù)雜的過(guò)程,它會(huì)依賴(lài)很多東西,它需要很多編譯語(yǔ)言,需要依賴(lài)到包的環(huán)境和其他外部的相關(guān)工作,像測(cè)試集成等等,這些都是相對(duì)比較復(fù)雜的。比如要構(gòu)建一個(gè)C的庫(kù)的話會(huì)有很多依賴(lài),有很多東西需要配置。管理這些依賴(lài)的情況下有很多工具,工具會(huì)有自己不同的特點(diǎn)需要去考慮。
一、編譯環(huán)境
作為編譯環(huán)境,首先會(huì)涉及到操作系統(tǒng)的問(wèn)題,在不同的操作系統(tǒng)下編譯會(huì)有微妙的差別,社區(qū)本身會(huì)有很多工具屏蔽這個(gè)差距,即便屏蔽的再好也會(huì)有很微妙的差距。
項(xiàng)目管理也是需要考慮的,比如低版本的問(wèn)題,有些功能不一樣,還有團(tuán)隊(duì)比較大的話,大家用的版本管理工具可能不太一樣,這個(gè)現(xiàn)在不是太突出了,但是以前項(xiàng)目管理的問(wèn)題還是比較突出的。
編譯器也屬于編譯環(huán)境里的問(wèn)題,一般公司內(nèi)部會(huì)統(tǒng)一一個(gè)官方的編譯器,但是開(kāi)發(fā)者在自己使用的時(shí)候有可能偏離公司內(nèi)部編譯器的版本。
目標(biāo)環(huán)境也是大家經(jīng)常需要考慮的問(wèn)題,一般來(lái)說(shuō)的話有可能你的開(kāi)發(fā)環(huán)境和實(shí)際的生產(chǎn)環(huán)境不一致,這個(gè)時(shí)候也會(huì)造成很微妙的差異問(wèn)題。
二、外部依賴(lài)
構(gòu)建項(xiàng)目需要確認(rèn)依賴(lài)包的版本,這方面有些語(yǔ)言有包管理工具可以使用。不過(guò)包管理工具并不能做所有事精。比如七牛內(nèi)部經(jīng)常使用的Go語(yǔ)言,這個(gè)會(huì)自帶一個(gè)包管理,但是不能自定義,有一些需要靠自己編譯環(huán)境解決的問(wèn)題。
如果公司大的話還會(huì)有項(xiàng)目之間的依賴(lài),這個(gè)也是一些需要去考慮的,如何從別的項(xiàng)目組來(lái)拿取相關(guān)的版本依賴(lài)的問(wèn)題。
三、持續(xù)集成
構(gòu)建自動(dòng)化:構(gòu)建的過(guò)程要想自動(dòng)化,需要保證編譯行為和本地開(kāi)發(fā)一致(環(huán)境一致,結(jié)果一致)。自動(dòng)化構(gòu)建失敗時(shí)的清理工作。
部署自動(dòng)化:部署自動(dòng)化要保證依賴(lài)項(xiàng)目也要部署一致。部署失敗時(shí)的回滾。
Docker是如何解決這些問(wèn)題的?
“Build, Ship and Run Any App, Anywhere.”
大家可能更多講的是run和ship,我更多的是講build,而且這個(gè)目的是在任何地方都可以對(duì)它做build。
一、編譯環(huán)境
編譯環(huán)境也是一種環(huán)境,Dockerfile作為一個(gè)Docker的封裝語(yǔ)言,它是在描述這個(gè)環(huán)境本身,而且非常幸運(yùn)的是Dockerfile是非常好的純文本文件,所以它很容易和平時(shí)使用的版本控制工具集成在一起,然后對(duì)整個(gè)環(huán)境做一個(gè)版本控制。而且由于Docker本身是一個(gè)自封裝式的東西可以做編譯環(huán)境和運(yùn)行時(shí)的環(huán)境完全一致。這對(duì)屏蔽差異是一件非常好的事情。
二、外部依賴(lài)
包依賴(lài)屬于環(huán)境的一部分,其實(shí)如何做包的管理,就相當(dāng)于說(shuō)你在管理相關(guān)的具體的環(huán)境。項(xiàng)目間的依賴(lài)會(huì)有另外的方法,一種就是把項(xiàng)目之間做成一個(gè)單獨(dú)的包的依賴(lài),這種方法等同于之前處理環(huán)境下的環(huán)境內(nèi)部包的依賴(lài)。另外一種方法就是把所有的項(xiàng)目合并在一個(gè)大的項(xiàng)目,這樣的話就不會(huì)出現(xiàn)項(xiàng)目間的依賴(lài),其實(shí)都是在一個(gè)項(xiàng)目?jī)?nèi)部。
三、持續(xù)集成
Docker本身是一個(gè)統(tǒng)一環(huán)境的能力,所以從持續(xù)集成的工具來(lái)看的話,它不需要涉及到具體的構(gòu)建的內(nèi)部和部署的內(nèi)部,只需要能夠構(gòu)建Docker的一些功能就可以。所以它其實(shí)是在這個(gè)層面上是可以簡(jiǎn)化編譯腳本和簡(jiǎn)化部署腳本編寫(xiě)的。
使用Docker構(gòu)建的嘗試
這是很簡(jiǎn)單的Go的程序。它首先會(huì)引入兩個(gè)包,一個(gè)是官方包,一個(gè)是外部包,它需要包管理依賴(lài)工具來(lái)引用到。這個(gè)數(shù)主函數(shù),它做的就是先建一個(gè)http,然后通過(guò)官方里的App功能把這個(gè)啟動(dòng)起來(lái),然后在1234端口打開(kāi)。可以看到這個(gè)程序沒(méi)做太多事情,主要演示的就是包工具管理依賴(lài)的事情。
這是做的第一次嘗試,可以看到這個(gè)目錄結(jié)構(gòu)是這樣的,這個(gè)是按照Go的官方習(xí)慣的編寫(xiě)方式,會(huì)把文件放在叫做src下面,hello可以看作一個(gè)是項(xiàng)目表,main就是文件。
這個(gè)是剛才目錄里的Dockerfile。它首先引用了一個(gè)Golang,這個(gè)是Docker官方提供的,它除了提供這個(gè)還提供其他語(yǔ)言的,Java都有。它做的事情很簡(jiǎn)單,首先把src目錄加到main里,然后拉去hello模塊里的依賴(lài)。從Golang,當(dāng)把src目錄加進(jìn)去之后就會(huì)把它作為Golang執(zhí)行時(shí)的目錄。這里不用把前面的前綴都寫(xiě),只需要寫(xiě)在Src目錄下面的名就可以了,就是hello。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):同步更新項(xiàng)目依賴(lài);結(jié)構(gòu)簡(jiǎn)單,容易維護(hù)。
缺點(diǎn):無(wú)法對(duì)依賴(lài)做Cache,編譯耗時(shí);不同項(xiàng)目之間重新編譯依賴(lài)。
第二次嘗試,這個(gè)結(jié)構(gòu)跟剛才一樣,這里和剛才有一個(gè)最大的區(qū)別,它就是說(shuō)把剛才的依賴(lài)包直接手寫(xiě)到了Docker文件里,而且這個(gè)依賴(lài)包會(huì)寫(xiě)在,把src加入到整個(gè)Docker的地址之前,就是每次先執(zhí)行這句話,然后再把所有的原密碼上傳設(shè)置。這個(gè)好處就是在于這句話執(zhí)行與否與互相間的修改不相關(guān)的。如果你重復(fù)執(zhí)行Dockerfile,可以在這里面直接Cache,這句話只要在第一次運(yùn)行一遍直接走到下面,這就會(huì)增加編譯的速度。如果想更新這個(gè)包的話,可以減Cache命令,它就會(huì)重新拉取依賴(lài),這個(gè)依賴(lài)就是一個(gè)可以被Cache目標(biāo)。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):依賴(lài)會(huì)被Cache,提高速度;結(jié)構(gòu)簡(jiǎn)單,容易維護(hù)。
缺點(diǎn):手寫(xiě)依賴(lài),不能同步更新項(xiàng)目的變化;不同項(xiàng)目之間重新編譯依賴(lài)。
第三次嘗試,這個(gè)可以看到整個(gè)目錄結(jié)構(gòu)開(kāi)始變得比較復(fù)雜了,首先最大不同就是編譯變成了兩個(gè)腳本。Dockerfile沒(méi)有變,在src會(huì)出現(xiàn)一個(gè)github.com。Dockerfile只是直接把一個(gè)hello拷到了Docker里,而沒(méi)有做其他的事情。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):依賴(lài)會(huì)被保存在本地,提高速度;依賴(lài)同步更新。
缺點(diǎn):不同編譯環(huán)境保存的依賴(lài)可能不一致;依賴(lài)侵入項(xiàng)目,造成干擾;gitignore,但是很麻煩。
使用Docker進(jìn)行構(gòu)建
這是我們現(xiàn)在在使用的一些方法,是我們最終在使用的。首先可以從目錄結(jié)構(gòu)來(lái)看,它會(huì)比剛才第三種方法變得更加復(fù)雜,首先它會(huì)有兩個(gè)編譯的腳本,這個(gè)應(yīng)該是build sh.sh,hello.sh,我們寫(xiě)程序的時(shí)候知道,如果有問(wèn)題的話可以單獨(dú)抽象出一層來(lái)解決問(wèn)題,這個(gè)就是把一個(gè)基礎(chǔ)的依賴(lài)和真正構(gòu)建程序的Docker分成兩個(gè)Docker,base這層是在做依賴(lài),而hello會(huì)基于base做構(gòu)建。
這個(gè)是來(lái)介紹base Image,它所做的事情首先是起名字,同時(shí)會(huì)指定。下面這個(gè)Dockerfile就是base,它做的就是從Golang程序里作為一個(gè)基礎(chǔ)隱秘,把src加入進(jìn)去,這是Go的一個(gè)配置方法,相當(dāng)于它會(huì)拉取所有項(xiàng)目依賴(lài)。這樣做完以后歸做base Image有項(xiàng)目所有依賴(lài)的包。
這是整個(gè)項(xiàng)目的所有依賴(lài),這里hello.sh是構(gòu)建項(xiàng)目的腳本,它跟剛才的最大區(qū)別是它構(gòu)建的配置文件發(fā)生了變化,這是指定了一個(gè)hello的名字。而下面是hello的Cache文件。這個(gè)地方會(huì)發(fā)現(xiàn),把src重新加入到image,這樣做就是當(dāng)hello image有變化的時(shí)候,它還是需要重新覆蓋到image。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):依賴(lài)會(huì)被Cache在本地,提高速度;依賴(lài)同步更新;依賴(lài)可以被多個(gè)項(xiàng)目共享。
缺點(diǎn):多維護(hù)一個(gè)image。