兩年之后,再來思考Docker的價(jià)值
【原文編者的話】作者應(yīng)該是個Geeker,喜歡思考很多本質(zhì)性的東西,并開創(chuàng)性的將Docker跟鏈接器來作比較,并提出了很多有意思的觀點(diǎn)如:應(yīng)用程序棧的鏈接器等。不管對不對,至少他這種思考的方式值得我們學(xué)習(xí)。
我必須告訴Docker,他們的創(chuàng)意完全滿足我對“天才般創(chuàng)新”的標(biāo)準(zhǔn),Docker讓我花了一段時間來弄明白它到底是什么。
偉大的創(chuàng)新絕對不是發(fā)明能飛的汽車,而常常是一些很簡單但是細(xì)細(xì)想想背后的原理非常復(fù)雜的一類事物。舉個例子,比特幣就是一個絕好的發(fā)明。它很簡單,但是背后的故事非常復(fù)雜,仔細(xì)想想很多人都會發(fā)出疑問,它可以流通嗎?有股票價(jià)值嗎?可以用于債券嗎?這其實(shí)是個分類的問題,可能把所有的這些不同方面的屬性結(jié)合在一起就是個新的事物。
Docker絕對不是一個像OpenVZ或者BSD jails一樣的容器。當(dāng)我***次使用Docker的時候,我費(fèi)了很多時間在找如何在一個運(yùn)行的容器中配置一個/dev的node;***我終于理解了 Docker:其實(shí)你沒有必要這樣做,你只需要啟動一個/dev的node的容器即可,Docker的容器是進(jìn)程,而不是一個完整的虛擬機(jī)系統(tǒng)。
Docker是一個新的虛擬機(jī):在不運(yùn)行時表現(xiàn)為“靜止”(immutable)包含運(yùn)行程序的容器(其實(shí)就是鏡像),然后啟動此鏡像后在系統(tǒng)中表現(xiàn)為一個進(jìn)程。
但是,Docker的本質(zhì)是什么呢?
這個問題我思考了好幾天,***我頓悟了:Docker是個能夠?qū)⑹謩渔溄幼兊幂p松的工具。更具體的說,是個可以手動鏈接應(yīng)用程序還能將其保存的工具。(作者的意圖是從鏈接這個低層工具出發(fā)來思考Docker的價(jià)值,因?yàn)榈蛯拥逆溄悠骶褪菍⒉煌ǔ绦蚰K組合成一個程序來運(yùn)行,從哲學(xué)層面上將確實(shí)跟 Docker做的事情有些類似,但是維度不一樣。)
但是后來我又仔細(xì)的思考了一下,好像又不是這樣的。
鏈接器是什么
如果你使用過GCC、clang或者Visual Studio,你就在使用編譯器來構(gòu)造可以執(zhí)行的程序。對于C或C++來說,編譯器會將源代碼編譯成一堆的目標(biāo)文件;目標(biāo)文件中包含了源代碼對應(yīng)于目標(biāo)平臺的機(jī)器代碼(或者是中間代碼)。但是,要使程序能夠運(yùn)行,你的代碼一定會調(diào)用其他模塊的代碼——函數(shù);所以鏈接過程就是把你寫的程序中所有依賴于其他模塊、庫中的代碼收集起來并鏈接到你的目標(biāo)代碼中。(注:關(guān)于程序是如何執(zhí)行這個問題,可以參考譯者之前的博文:程序是如何運(yùn)行的)
在維基百科中有關(guān)于“鏈接器”的簡明的解釋。舉個例子,比如你使用C語言寫了個程序,調(diào)用了“printf”這個函數(shù),代碼通過編譯器、鏈接器處理過后會得到一個目標(biāo)文件,中間會有這么一段“從這里開始調(diào)用printf函數(shù)”;所以printf不是我程序的一部分——它是一個叫做C語言標(biāo)準(zhǔn)庫的一部分(注:glibc,C語言運(yùn)行時,分為靜態(tài)鏈接與動態(tài)鏈接兩個版本。)。鏈接器會分析glibc的靜態(tài)庫文件,收集我的程序中所需要重定位的符號信息,包括printf這個符號;然后將printf的代碼從glibc中抽取出來,鏈接到我的程序中,組成一個完整的可以運(yùn)行的可執(zhí)行文件;這樣我就能通過printf函數(shù)向終端輸出字符信息了。
但是真實(shí)環(huán)境更加復(fù)雜,還有個叫做動態(tài)鏈接的概念。很多時候,除開我使用Go語言,我不希望我的可執(zhí)行文件過大;我會更加希望我編譯出來的文件模塊分明,不同的模塊包含在不同的文件中,我的入口程序在運(yùn)行時來決定加載哪個模塊并運(yùn)行——這就是動態(tài)鏈接。動態(tài)鏈接做的很多事情跟靜態(tài)鏈接類似,只是將鏈接的過程延遲到運(yùn)行時完成罷了。
但是從概念上講,什么是鏈接器呢?
當(dāng)程序員談起『鏈接器』時,他們***反應(yīng)是C/C++語言編譯出來的目標(biāo)文件。但是,我們回過頭想想,忽略一些細(xì)節(jié),鏈接器到底干了些什么呢?鏈接器的工作是:能夠自動完成一些將分散的代碼組合起來的復(fù)雜工作的程序。從學(xué)術(shù)上來說,Unix的『ID』,Windows中的『link』都是鏈接器;而廣義上來講,任何能夠自動將代碼片段鏈接在一起運(yùn)行的程序都是鏈接器,比如以膠水工廠著稱的SWIG。
二進(jìn)制元(META-BINARIES)
想象一下沒有鏈接器的情況,任何時候你生成一個程序后——或者運(yùn)行一個需要眾多動態(tài)庫支持的程序——你都必須手工打開hex編輯器,從二進(jìn)制代碼的層面手工進(jìn)行符號收集與重定位工作。但是,廣義上來講,你不用去想象,你已經(jīng)在做這部分工作了,只是層面不同,比如,你需要配置一個LAMP系統(tǒng)的時候,你需要編輯很多配置文件使各個系統(tǒng)能夠配合運(yùn)行通暢,再比如,你需要增加Redis或 Memcached組件時,你必須要更改配置文件一樣;你在進(jìn)行手工鏈接,鏈接不同的程序。
再深入一點(diǎn)去思考的話,程序的鏈接跟應(yīng)用程序級別的配置沒什么兩樣,只是前者是鏈接低層代碼段,后者是將不同的模塊、系統(tǒng)拼接起來運(yùn)行。但是,市場上并沒有專注做“應(yīng)用程序棧”鏈接的“鏈接器”,你必須要手動配置他們的運(yùn)行參數(shù)來實(shí)現(xiàn)它們之間的協(xié)同工作,如:設(shè)置TCP監(jiān)聽端口,設(shè)置訪問權(quán)限規(guī)則等等。這其實(shí)是高層次的鏈接過程,應(yīng)用程序之間靠協(xié)議通行或者ABIs的調(diào)用。
#p#
Docker不是鏈接器
所以Docker是鏈接器了?答案是:不是!
Docker實(shí)際上是提供了一個環(huán)境,你可以在這個環(huán)境中將你要的”應(yīng)用程序棧“配置好,然后保存你的設(shè)置,形成鏡像;然后你就能復(fù)制這些鏡像,多處運(yùn)行,而不需要再次進(jìn)行配置。
Docker還包含了很多配置工具,你可以配置容器運(yùn)行時的網(wǎng)絡(luò),運(yùn)行時訪問安全,配置VXLAN overlays來實(shí)現(xiàn)同一個數(shù)據(jù)中心或者局域網(wǎng)中不同容器間的互訪。但是這些都是附加屬性,Docker的核心是可以讓你保存你的手動鏈接結(jié)果。撇去這個功能,Docker只是部署、運(yùn)行服務(wù)器端應(yīng)用程序的另一種方式而已。(注:Docker的基于進(jìn)程的虛擬機(jī)模型也是一個亮點(diǎn)。)
應(yīng)用程序棧的鏈接器?
是不是沒有基于“應(yīng)用程序棧的鏈接器”呢?也不盡然,也有些人在嘗試,但是都遇到了困難,導(dǎo)致都不知道要做成什么了。比如,Chef、Puppet與Saltstack這些產(chǎn)品,他們都提供了很多附屬工具,但是核心功能是用腳本化的鏈接方式把小的程序模塊組合成大的應(yīng)用程序執(zhí)行棧來運(yùn)行。
但是,使用過這些工具的人都覺得它們復(fù)雜與笨重;我覺得主要是因?yàn)殚_發(fā)它們的程序員錯誤將它們定位在企業(yè)級服務(wù)器管理工具上了。他們應(yīng)該開發(fā)更加通用的系統(tǒng)會更加實(shí)用。
如果我們能達(dá)到這些,我們還會需要Docker嗎?
可能吧,因?yàn)镈ocker很有用。但是,如果我們擁有一個“應(yīng)用程序棧”級別的“ld.so”(Linux下的動態(tài)鏈接器)就不好說了;因?yàn)椋啾任覀儼褢?yīng)用程序棧打包生成一個Docker鏡像,我們只需要一個動態(tài)鏈接器就能把不同的應(yīng)用程序鏈接在一起工作了。