VM and Docker Container


https://www.zhihu.com/question/48174633

 

在開始討論前,先拋出一些問題,可先別急着查看答案,討論的過程可以讓答案更有趣,問題如下:

1 虛擬機

先來理解一下虛擬機概念,廣義來說,虛擬機是一種模擬系統,即在軟件層面上通過模擬硬件的輸入和輸出,讓虛擬機的操作系統得以運行在沒有物理硬件的環境中(也就是宿主機的操作系統上),其中能夠模擬出硬件輸入輸出,讓虛擬機的操作系統可以啟動起來的程序,被叫做hypervisor。用一張圖來說明這個關系就是:

在這張圖中:

  • 物理機被稱為宿主機
  • 虛擬機也被稱為guest OS
  • 而被hypervisor虛擬出來的硬件被稱為虛擬硬件

比如,舉一個大家都很熟悉的例子,在編寫android程序時,調試和測試運行都可以在X86架構的台式機或筆記本進行,這就是一個典型的虛擬機例子,在這之中:

  • 宿主機就是台式機或筆記本
  • 虛擬機就是虛擬出來的android
  • 而模擬android的軟件就是android box

當然android模擬機一個大問題就是:啟動速度非常慢,最長可達10分鍾或以上,這是因為單純模擬硬件的輸入輸出,效率是很差的,所以這樣的虛擬機如果真部署在服務器上,速度是感人的。

這個時候,就有計算機科學家提出了非常偷懶的想法:假如我們不模擬硬件輸入輸出,只是做下真實硬件輸入輸出的搬運工,那么虛擬機的指令執行速度,就可以和宿主機一致了。當然這前提是宿主機的硬件架構必須和虛擬硬件架構一致。比如,

  • 我們可以在linux的台式機上輕松模擬windows,而且這個windows的運行速度基本上和原生裝一個windows速度差不多,因為windows也能被直接安裝在這台台式機上。
  • 這個思路對於在windows系統中運行android系統不管用,因為android系統的運行硬件一般是手機(arm系統,可以理解為不同的硬件架構體系和cpu指令集),所以android模擬機還是一樣的慢。

由於本篇並不是主要關於虛擬機的內容,所以這些點就點到而止,更多詳細內容可以參閱:Hypervisor

2 容器的概念

一般來說,虛擬機都會有自己的kernel,自己的硬件,這樣虛擬機啟動的時候需要先做開機自檢,啟動kernel,啟動用戶進程等一系列行為,雖然現在電腦運行速度挺快,但是這一系列檢查做下來,也要幾十秒,也就是虛擬機需要幾十秒來啟動。

  • 重新來理解虛擬機的概念,計算機科學家發現其實我們創建虛擬機也不一定需要模擬硬件的輸入和輸出,假如宿主機和虛擬機他們的kernel是一致的,就不用做硬件輸入輸出的搬運工了,只需要做kernel輸入輸出的搬運工即可,為了有別於硬件層面的虛擬機,這種虛擬機被命名為 操作系統層虛擬化:Operating-system-level virtualization 也被叫做容器
  • 讓我們來回顧虛擬機的概念,在虛擬機的系統中,虛擬機認為自己有獨立的文件系統,進程系統,內存系統,等等一系列,所以為了讓容器接近虛擬機,也需要有獨立的文件系統,進程系統,內存系統,等等一系列,為了達成這一目的,主機系統采用的辦法是:只要隔離容器不讓它看到主機的文件系統,進程系統,內存系統,等等一系列,那么容器系統就是一個接近虛擬機的玩意了

更多關於容器的內容可以看這份課件:

至此就可以回答引言提到的兩個問題:

3 how to learn more

 

在上一篇 一篇不一樣的docker原理解析 - uncle creepy的文章 - 知乎專欄 中,主要討論了容器和虛擬機的區別,在實現細節上並沒有深入,只是點到即止,在這篇提高篇中,將詳細討論容器的實現細節,當然我不希望把文章寫成對於kernel man page的簡單翻譯,所以不熟悉內核和linux api的讀者可以不急着點叉,這會是一篇不一樣的討論容器實現的提高篇。

在開始討論前,先拋出一個問題:

如圖,linux的啟動流程

0 從fork說起

在講解容器之前,先來談談linux實現進程的原理,linux實現進程的方法為fork,實現的方式分為兩個步驟:

  1. 在內存中復制一個父進程,得到“子進程”,此時子進程就是父進程上下文的簡單克隆,內容完全一致
  2. 設置子進程的 pid,parent_pid,以及其他和父進程不一致的內容

1 namespace讓進程隔離更靈活

從進程被制造的步驟可以看出,進程大部分資源和父進程共享,如果需要制造一個看起來像虛擬機的進程,我們需要比普通的進程多做幾步。

  • 可以自定義rootfs,比如我們把整個ubuntu發行版的可執行文件以及其他文件系統都放在目錄/home/admin/ubuntu/ 下,當我們重定義rootfs = /home/admin/ubuntu 后,則該文件地址被印射為 "/"
  • 把自身pid 印射為0,並看不到其他任何的pid,這樣自身的pid成為系統內唯一存在pid,看起來就像新啟動了系統
  • 用戶名隔離,可以把用戶名設置為“root”
  • hostname隔離,可以另取一個hostname,成為新啟動進程的hostname
  • IPC隔離,隔離掉進程之間的互相通信
  • 網絡隔離,隔離掉進程和主機之間的網絡

如果做完這幾步,至少在進程自身看來,和虛擬機執行環境上已經區別不大了,對應到linux系統中,這幾個隔離需要的方法:clone(2) - Linux manual page

而clone方法和fork方法,在復制上下文的時候,調用的都是syscall_clone() 本質上它們是差不多的。

2 其實docker是一個內核的搬運工

所以雖然docker幫助我們准備好了rootfs地址,鏡像里面的文件,以及各種資源隔離的配置,但是在啟動一個容器的時候,它只是調用系統中早已內置的可以隔離資源的方法,而kernel支持這些方法,也是在創建進程的方法上做了一層資源隔離的擴展而已。

這就解釋了docker兩個特性:

  • 啟動速度快,因為本質來說容器和進程差別沒有想象中的大,共享了很多代碼,流程也差的不多
  • linux內核版本有最低的要求,因為linux是在某個版本后開始支持隔離特性

3 容器內創建進程 --- Think a step further

這是我認為整個容器實現里面最優美的一點:

  • 內核開發者實現了容器的資源隔離一系列隔離后,內核開發者就不需要為容器內創建進程單獨再做任何多余的工作了。
  • 在fork方法中,第一步就是繼承父進程的一切,而這一切包含了父進程已有的資源隔離,所以容器進程創建的進程天然繼承容器所有的一切資源隔離 ------ 就和虛擬機的pid = 0 的進程創建子進程所擁有的一樣

4 One more thing

讓我們再來看看開篇提出的問題:

linux啟動流程 中,容器需要使用其中的幾步?

看完了fork,clone以及一大堆隔離后,相信讀者很容易有答案了,這中間容器做完了隔離之后就算啟動完畢,根本就不會來做kernel init之類的步驟,所以答案是一步都不用。

5 how to learn more

    • 比較除docker外其他的容器類產品,如coreOS,LXC
    • 了解linux如何做隔離,請參考:namespaces(7)
    • 了解freebsd如何做隔離,請參考:freebsd jail
    • docker 其實真正想做的事情是把資源隔離的接口標准化(最新的版本里windows的接口也被抽象到了docker自己的體系),嚴格說它是所有相似資源隔離的一層抽象和搬運工
    • docker 鏡像很小的優勢,主要是靠AUFS實現的,本篇不詳細說明,因為AUFS在docker原理介紹的口水文里被用濫了,隨便搜搜到處都是,而且我很不喜歡官方用的集裝箱比喻

 Docker和VM相比,虛擬化層次不同,導致Docker的隔離性要比VM弱,在多租戶環境中有安全隔離的問題,另外在單宿主機上,容器之間的資源競爭也可能導致業務會受到影響,需要對容器的資源隔離,特別是cgroup的隔離有較為精細的運維

虛擬機是為提供系統環境而生的,容器是為提供應用環境而生的

1、啟動快大部分情況下只影響了用戶體驗,但是對於負責大規模部署的運維來講,意義還是挺大。一個東西上線是要一天,還是一個小時,情況是很不一樣的。
2、資源利用高的意思是,虛擬化會消耗資源比容器多。不太准確地說,經過虛擬化層,留給用戶的資源只剩90%,而容器可以剩下99%。對於雲的意義直觀來講就是可以省錢啊。
3、性能開銷的意義在於,由於存在一個虛擬化層,即使虛擬機獨占所有的物理資源,但跑在里面的程序性能還是比物理機慢,這方面應該cpu內存還好一點,存儲和其他外設就比較糟糕了。而容器只是一個進程,性能與物理機幾乎一樣




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM