轉載請註明出處:葡萄城官網,葡萄城爲開發者提供專業的開發工具、解決方案和服務,賦能開發者。
上一節咱們爲你們介紹了Cloud Foundry等最初的PaaS平臺如何解決容器問題,本文將爲你們展現Docker如何解決Cloud Foundry遭遇的一致性和複用性兩個問題,並對比分析Docker和傳統虛擬機的差別。shell
在本系列文章的第一節中,咱們提到Docker經過Docker 鏡像(Docker Image)功能迅速取代了Cloud Foundry,那這個Docker鏡像究竟是什麼呢,如何經過爲不一樣的容器使用不一樣的文件系統以解決一致性問題?先賣個關子,咱們先來看看上一節中說過隔離功能和Namespace機制。安全
Mount Namespace,這個名字中的「Mount」可讓咱們想到這個機制是與文件掛載內容相關的。Mount Namespace是用來隔離進程的掛載目錄的,讓咱們能夠經過一個「簡單」的例子來看看它是怎麼工做的。bash
(用C語言開發出未實現文件隔離的容器)網絡
上面是一個簡單的的C語言代碼,內容只包括兩個邏輯:
1.在main函數中建立了一個子進程,而且傳遞了一個參數CLONE_NEWNS,這個參數就是用來實現Mount Namespace的;
2.在子進程中調用了/bin/bash命令運行了一個子進程內部的shell。app
讓咱們編譯而且執行一下這個程序:函數
gcc -o ns ns.c
./ns工具
這樣咱們就進入了這個子進程的shell中。在這裏,咱們能夠運行ls /tmp查看該目錄的結構,並和宿主機進行一下對比:性能
(容器內外的/tmp目錄)開發工具
咱們會發現兩邊展現的數據竟然是徹底同樣的。按照上一部分Cpu Namespace的結論,應該分別看到兩個不一樣的文件目錄纔對。爲何?優化
容器內外的文件夾內容相同,是由於咱們修改了Mount Namespace。Mount Namespace修改的是進程對文件系統「掛載點」的認知,意思也就是隻有發生了掛載這個操做以後生成的全部目錄纔會是一個新的系統,而若是不作掛載操做,那就和宿主機的徹底一致。
如何解決這個問題,實現文件隔離呢?咱們只須要在建立進程時,在聲明Mount Namespace以外,告訴進程須要進行一次掛載操做就能夠了。簡單修改一下新進程的代碼,而後運行查看:
(實現文件隔離的代碼和執行效果)
此時文件隔離成功,子進程的/tmp已經被掛載進了tmpfs(一個內存盤)中了,這就至關於建立了徹底一個新的tmp環境,所以子進程內部新建立的目錄宿主機中已經沒法看到。
上面這點簡單的代碼就是來自Docker鏡像的實現。Docker鏡像在文件操做上本質是對rootfs的一次封裝,Docker將一個應用所需操做系統的rootfs經過Mount Namespace進行封裝,改變了應用程序和操做系統的依賴關係,即本來應用程序是在操做系統內運行的,而Docker把「操做系統」封裝變成了應用程序的依賴庫,這樣就解決了應用程序運行環境一致性的問題。不論在哪裏,應用所運行的系統已經成了一個「依賴庫」,這樣就能夠對一致性有所保證。
在實現文件系統隔離,解決一致性問題後,咱們還須要面對複用性的問題。在實際使用過程當中,咱們不大可能每作一個鏡像就掛載一個新的rootfs,費時費力,不帶任何程序的「光盤」也須要佔用很大磁盤空間來實現這些內容的掛載。
所以,Docker鏡像使用了另外一個技術:UnionFS以及一個全新的概念:層(layer),來優化每個鏡像的磁盤空間佔用,提高鏡像的複用性。
咱們先簡單看一下UnionFS是幹什麼的。UnionFS是一個聯合掛載的功能,它能夠將多個路徑下的文件聯合掛載到同一個目錄下。舉個「栗子」,如今有一個以下的目錄結構:
(使用tree命令,查看包含A和B兩個文件夾)
A目錄下有a和x兩個文件,B目錄下有b和x兩個文件,經過UnionFS的功能,咱們能夠將這兩個目錄掛載到C目錄下,效果以下圖所示:
mount -t aufs -o dirs=./a:./b none ./C
(使用tree命令查看聯合掛載的效果)
最終C目錄下的x只有一份,而且若是咱們對C目錄下的a、b、x修改,以前目錄A和B中的文件一樣會被修改。而Docker正是用了這個技術,對其鏡像內的文件進行了聯合掛載,好比能夠分別把/sys,/etc,/tmp目錄一塊兒掛載到rootfs中造成一個在子進程看起來就是一個完整的rootfs,但沒有佔用額外的磁盤空間。
在此基礎上,Docker還本身創新了一個層的概念。首先,它將系統內核所須要的rootfs內的文件掛載到了一個「只讀層」中,將用戶的應用程序、系統的配置文件等之類能夠修改的文件掛載到了「可讀寫層」中。在容器啓動時,咱們還能夠將初始化參數掛載到了專門的「init層」中。容器啓動的最後階段,這三層再次被聯合掛載,最終造成了容器中的rootfs。
(Docker的只讀層、可讀寫層和init層)
從上面的描述中,咱們能夠了解到只讀層最適合放置的是固定版本的文件,代碼幾乎不會改變,才能實現最大程度的複用。好比活字格公有云是基於.net core開發的,咱們將其用到的基礎環境等都會設計在了只讀層,每次獲取最新鏡像時,由於每一份只讀層都是徹底同樣的,因此徹底不用下載。
Docker的「層」解釋了爲何Docker鏡像只在第一次下載時那麼慢,而以後的鏡像都很快,而且明明每份鏡像看起來都幾百兆,可是最終機器上的硬盤缺沒有佔用那麼多的緣由。更小的磁盤空間、更快的加載速度,讓Docker的複用性有了很是顯著的提高。
上面介紹的是Docker容器的整個原理。咱們結合上一篇文章,能夠總結一下Docker建立容器的過程實際上是:
其實Docker還作了不少功能,好比權限配置,DeviceMapper等等,這裏說的僅僅是一個普及性質的概念性講解,底層的各類實現還有很複雜的概念。具體而言,容器和傳統的虛擬機有啥區別?
其實容器技術和虛擬機是實現虛擬化技術的兩種手段,只不過虛擬機是經過Hypervisor控制硬件,模擬出一個GuestOS來作虛擬化的,其內部是一個幾乎真實的虛擬操做系統,內部外部是徹底隔離的。而容器技術是經過Linux操做系統的手段,經過相似於Docker Engine這樣的軟件對系統資源進行的一次隔離和分配。它們之間的對比關係大概以下:
(Docker vs 虛擬機)
虛擬機是物理隔離,相比於Docker容器來講更加安全,但也會帶來一個結果:在沒有優化的狀況下,一個運行CentOS 的 KVM 虛擬機啓動後自身須要佔用100~200MB內存。此外,用戶應用也運行在虛擬機裏面,應用系統調用宿主機的操做系統不可避免須要通過虛擬化軟件的攔截和處理,自己會帶來性能損耗,尤爲是對計算資源、網絡和磁盤I/O的損耗很是大。
但容器與之相反,容器化以後的應用依然是一個宿主機上的普通進程,這意味着由於虛擬化而帶來的損耗並不存在;另外一方面使用Namespace做爲隔離手段的容器並不須要單獨的Guest OS,這樣一來容器額外佔用的資源內容幾乎能夠忽略不計。
因此,對於更加須要進行細粒度資源管理的PaaS平臺而言,這種「敏捷」和「高效」的容器就成爲了其中的佼佼者。看起來解決了一切問題的容器。難道就沒有缺點嗎?
其實容器的弊端也特別明顯。首先因爲容器是模擬出來的隔離性,因此對Namespace模擬不出來的資源:好比操做系統內核就徹底沒法隔離,容器內部的程序和宿主機是共享操做系統內核的,也就是說,一個低版本的Linux宿主機極可能是沒法運行高版本容器的。還有一個典型的栗子就是時間,若是容器中經過某種手段修改了系統時間,那麼宿主機的時間同樣會改變。
另外一個弊端是安全性。通常的企業,是不會直接把容器暴露給外部用戶直接使用的,由於容器內能夠直接操做內核代碼,若是黑客能夠經過某種手段修改內核程序,那就能夠黑掉整個宿主機,這也是爲何咱們本身的項目從剛開始本身寫Docker到最後棄用的直接緣由。如今通常解決安全性的方法有兩個:一個是限制Docker內進程的運行權限,控制它值能操做咱們想讓它操做的系統設備,可是這須要大量的定製化代碼,由於咱們可能並不知道它須要操做什麼;另外一個方式是在容器外部加一層虛擬機實現的沙箱,這也是如今許多頭部大廠的主要實現方式。
Docker憑藉一致性、複用性的優點打敗了前輩Cloud Foundry。本文介紹了Docker具體對容器作的一點改變,同時也介紹了容器的明顯缺點。下一篇文章,咱們會爲你們介紹Docker又是如何落寞,然後Docker時代,誰又是時代新星。敬請期待。