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