阿里微服務架構師隨手筆記:教你如何手寫Docker

模擬Docker實現一個簡單的容器,不到 200行代碼(包括空行、註釋、異常處理),這並非吹牛B。容器技術幾乎是Linux kernel內置的模塊,咱們簡單調用一下API就能搞定不少事情。固然你要考慮各類商業因素、政治因素那就會成長爲Docker這種量級的代碼量了。git

盜用一下朋友圈裏的段子:小公司與大公司的區別就是,以殺豬爲例,小公司是找到豬直接亂刀砍死。大公司要先作一套籠具抓豬,再作一套流程磨刀,再發明一套刀法(工程師一般會就刀法爭論好久)殺豬。抓豬的籠具除了能抓豬還能抓跳騷,磨刀的工具除了能磨柴刀,還能磨指甲刀。殺豬的流程除了能殺豬,也能殺雞。作完了以後你只敲一個殺豬的命令就行。你不知道豬在哪裏,由於這是另外一我的負責的,代碼放在你不知道的某個目錄下;你也不知道刀在哪裏,由於目錄不可見,格式不可讀。刀法是啥你也不知道。這套系統理論上威力無比,一羣人費了老大勁作出來,除了用柴刀殺豬沒幹過別的,殺雞歷來沒測試過,殺跳騷代碼都不完整。可是公司裏的全部人都以爲,殺豬就應該這樣。因此你們天天忙忙碌碌,豬快活的過了一年又一年。github

因此這系列文章我主要介紹如何找到豬、怎麼持刀不傷到本身,如何發力可以更兇狠;而後現場表演一下把一頭活蹦亂跳的豬捅死。docker

涉及到的技術

寫一個容器只須要兩個技術——Namespace和CGroup,而這兩個東西都是Linux kernel提供的,咱們要作的就是——調用一下。無恥的盜用一下Brendan Gregg大神的圖。shell

這張圖中蘊含了一個常常被忽視的細節——容器是共享內核的,它們屬於多個進程同時運行在一個內核上,只不過是利用Namespace把它們隔離開,用CGroup限制可用資源。而虛擬機是共享「硬件」的,每一個虛擬機都有本身獨立的操做系統。因此,虛擬機是可引導的、絕對安全的隔離技術;而容器是很是脆弱的,不安全的隔離技術。安全

Namespace是Linux內核提供的一種隔離技術,它提供了六種隔離空間:性能優化

看的一臉懵逼對不對?不要緊,簡單的解釋一下。bash

學過操做系統原理的同窗都知道(沒學過?你還敢在這個行業混?),在一個內核全部進程都共享操做系統定義的資源——主機名、域名、ARP表、路由表、NAT表;文件系統、用戶和組、進程編號。以主機名爲例,它是由操做系統定義在一塊內存空間中的,因此進程A能看到,進程B也能看到(若是有權限甚至能夠修改)。Namespace提供了一種隔離技術,可讓每一個進程都定義「本身的主機名」。你能夠理解爲內核爲每一個進程都提供了一份當前主機名的備份,進程固然能夠修改這份數據,可是這個修改只能做用於本身,其餘進程感知不到——由於它再也不是「全局」的。網絡

常常有人問是否是全部應用均可以作容器化?理解Namespace就很容易回答這個問題。容器技術本質上仍是共享內核,因此任何須要修改內核的應用都不能夠被容器化。好比LVS、OpenvSwtich這些須要加載內核模塊的應用都沒有辦法作成容器。架構

推薦一個交流學習羣:478030634 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多
併發

Hello world

調用Namespace很是簡單,只須要一個API(沒錯,一個,只要一個)——clone。

它會建立一個新的線程(內核不會太區分線程和進程),第一個參數指定了線程的代碼入口,第二個參數是線程棧,第三個參數是標誌位,第四個參數是代碼入口的參數指針。

咱們上面所羅列的Namespace參數就是經過第三個參數——標誌位傳遞的。

咱們先測試一下UTS(主機名)是否能正常工做,由於子進程不涉及到遞歸調用因此定義1024字節的stack大小應該足夠了。main方法裏的os.waitpid(pid, 0)是必須的,不然子進程會由於父進程終止而提早退出。

child_func是子進程的入口,這段代碼裏咱們調用sethostname修改主機而後再執行hostname驗證修改是否生效了。

libc是我封裝好的系統調用,很是簡單。

小試牛刀一下:

首先在父進程中輸出本身的進程編號和子進程的編號,而後在子進程中輸出本身的進程編號和父進程的編號。在子進程中咱們調用sethostname修改了主機名而且經過hostname驗證了調用結果。可是這個修改並無波及到內核,最後咱們在shell中調用hostname驗證了這一結果。

要有Shell

上面只是執行一次修改hostname的動做,動做有點小,不夠過癮。咱們但願可以在獨立的Namespace中拿到一個shell。

只須要更改兩行代碼。父進程裏面增長NEW_PID、NEW_IPC的標誌位,子進程裏調用execle執行bash,經過最後一個參數指定了環境變量PS1,這個表示提示符。

再次執行,咱們發現shell已經變化了。經過hostname驗證咱們已經「在容器裏面」了。鍵入exit,退出容器。

是否是已經沒法掩蓋本身心裏的興奮了。別急,還有更興奮的,咱們進行第三步——分離文件系統。

推薦一個交流學習羣:478030634  裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多:

完全分離

若是你在上一部的shell中輸入一些top、ps、ls命令會發現幾乎和「Host」環境中一摸同樣。這是由於咱們尚未作最重要的一部——分離文件系統。

Docker提供的有Ubuntu、CentOS的鏡像,其實這些並非嚴格意義上的鏡像,它們準確的叫法應該是——根文件系統(root filesystem)。

容器是共享內核的,因此不管是Ubuntu、CentOS它們裏面都使用Host的內核,若是你在Docker中經過uname查看會發現不管什麼鏡像它們的內核版本都和Host一摸同樣。因此,不一樣「操做系統」Docker鏡像其實就是不一樣的根文件系統。

不少人用BusyBox的rootfs作演示,做爲一個風騷的男人怎麼怎麼可能如此俗套。因此我用CentOS 7做爲演示。

真正的緣由切換容器中的根目錄,後續的代碼執行會使用新的根文件系統,然後續的代碼是依賴Python運行環境的。因此咱們須要一個帶Python的rootfs,CentOS 7恰好知足這個。若是咱們用C或者Golang就不會有這個限制了。

你能夠經過CentOS提供的Dockerfile找到相關的rootfs的下載,好比:https://github.com/CentOS/sig- ... ocker

把下載到的文件解壓到/tmp目錄下。

分離文件系統分爲三個步驟,首先咱們創建容器裏面的/proc文件系統,不少Linux命令都是讀取這個文件系統下的內容(好比top中顯示的進程列表);其次咱們要把如今的用戶和容器裏面的用戶作映射,不然會提示權限不足;最後咱們要經過pivot_root 函數把「切換」根文件系統。

不要忘記修改main方法,爲標誌位增長三個參數,映射用戶。

再次執行。

和CentOS 7一摸同樣,你甚至能夠用yum命令,固然因爲咱們如今尚未實現網絡功能因此yum會告訴你沒法訪問網絡。

再多執行幾個添加文件、刪除文件看看?你會發現不管作什麼動做最終的數據都會被緊緊地固定在/tmp/rootfs下,也就是說——在容器裏面咱們是沒有辦法訪問host的文件的。

完整代碼:https://github.com/fireflyc/mini-docker。

相關文章
相關標籤/搜索