深入理解虛擬機、容器和Hyper技術

本文首先介紹了操作系統,然後引出容器技術以及虛擬機技術,最後介紹了Docker和Hyper技術。通過本文可以清楚地對三者有感性認識


操作系統概述

我們可以把操作系統簡化爲:640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=操作系統 = 內核 + apps

其中內核負責管理底層硬件資源,包括CPU、內存、IO設備等,並向上爲apps提供系統調用接口,上層apps應用必須通過系統調用方式使用硬件資源,通常並不能直接訪問資源。這裏的apps指的是用戶接口,比如shell、gui、services、包管理工具等(Linux的圖形界面也是作爲可選應用之一,而不像Windows是集成到內核中的),注意與我們手動安裝的應用區別開來。同一個內核加上不同的apps,就構成了不同的操作系統發行版,比如Ubuntu、Red Hat、Android等。因此我們可以認爲,不同的Linux發行版本其實就是由應用apps構成的環境的差別,比如默認安裝的軟件、鏈接庫、軟件包管理以及圖形界面等。我們把所有這些apps環境打成一個包,就可以稱之爲鏡像。

問題來了,假如我們同時有多個apps環境,能否在同一個內核上運行呢?因爲操作系統只負責提供服務,而並不管爲誰服務,因此同一個內核之上可以同時運行多個apps環境是沒有問題的。比如假設我們現在有ubuntu和fedora的apps環境,即兩個發行版鏡像,分別位於/home/int32bit/ubuntu/home/int32bit/fedora,我們最簡單的方式,採用chroot工具即可快速切換到指定的應用環境中,相當於同時有多個apps環境在運行。


容器技術


我們以上通過chroot方式,感覺上就已經接近了容器的功能,但其實容器並沒有那麼簡單,工作其實還差得遠。首先要作爲雲資源管理還必須滿足:

資源隔離

因爲雲計算本質就是集中資源再分配(社會主義),再分配過程就是資源的邏輯劃分,提供資源抽象的實現方式,我們暫且定義爲虛擬實體,虛擬實體可以是虛擬機、容器等。虛擬實體必須滿足隔離性,包括用戶隔離(或者說權限隔離)、進程隔離、網絡隔離、文件系統隔離等,即虛擬實體只能感知其內部的資源,並且自以爲是獨佔整個資源空間,它既不能感知其所在宿主機的真實資源,也不能感知其他虛擬實體的資源。

資源控制

資源控制指爲虛擬實體分配一定量的資源,虛擬實體得到所分配的資源,不能超出資源最大使用量。

以上是虛擬實體的兩個最基本要求,當然還包括其他很多條件,比如安全、性能等。本文主要基於以上兩個基本條件進行研究。

虛擬機技術

顯然滿足以上兩個條件,虛擬機是一種實現方式,這是因爲:

  • 隔離毋容置疑,因爲不同的虛擬機運行在不同的內核,虛擬機內部是一個獨立的隔離環境。

  • 資源控制也是毋容置疑的,Hypervisor能夠對虛擬機分配指定的資源。

目前OpenStack Nova和AWS EC2都是基於虛擬機提供計算服務,實現CPU、RAM、Disk等資源分配。其他比如Vagrant也是基於虛擬機快速構建應用環境。

但是虛擬機也帶來很多問題,比如:

  • 鏡像臃腫龐大,不僅包括apps,還包括一個龐大的內核。

  • 創建和啓動時間開銷大,不利於應用快速構建重組。

  • 額外資源開銷大,部署密度小。

  • 性能損耗。

  • … 

容器技術

除了虛擬機,有沒有其他實現方式能符合以上兩個基本條件呢?容器技術便是另一種實現方式。表面上和我們使用chroot方式相似,所有的容器實例直接運行在宿主機中,所有實例共享宿主機的內核,而虛擬機實例內部的進程是運行在GuestOS中。由以上原理可知,容器相對於虛擬機有以上好處:

  • 鏡像體積更小,只包括apps以及所依賴的環境,沒有內核。

  • 創建和啓動快,不需要啓動GuestOS,應用啓動開銷基本就是應用本身啓動的時間開銷。

  • 無GuestOS,無Hypervisor,無額外資源開銷,資源控制粒度更小,部署密度大。

  • 使用的是真實物理資源,因此不存在性能損耗。

  • 輕量級。

目前比較流行的容器實現比如LXC、LXD以及rkt等,我們需要驗證容器是否能夠實現資源隔離和控制。

隔離性

主要通過內核提供Namespace技術實現隔離性,以下參考酷殼(http://coolshell.cn/articles/17010.html):


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=Linux Namespace是Linux提供的一種內核級別環境隔離的方法。不知道你是否還記得很早以前的Unix有一個叫chroot的系統調用(通過修改根目錄把用戶jail到一個特定目錄下),chroot提供了一種簡單的隔離模式:chroot內部的文件系統無法訪問外部的內容。Linux Namespace在此基礎上,提供了對UTS、IPC、mount、PID、network、User等的隔離機制。


Linux Namespace有如下種類:

  • Mount Namespaces

  • UTS Namespaces

  • IPC Namespaces

  • PID Namespaces

  • Network Namespaces

  • User Namespaces

官方文檔在這裏《Namespace in Operation》(http://lwn.net/Articles/531114/)。

由上表可知,容器利用內核的Namespaces技術可以實現隔離性。比如網絡隔離,我們可以通過sudo ip netns ls查看Namespaces,通過ip netns add NAME增加Namespaces,不同的Namespaces可以有不同的網卡、Router、iptables等。

資源控制

內核實現了對進程組的資源控制,即Linux Control Group,簡稱CGroup,它能爲系統中運行進程組根據用戶自定義組分配資源。簡單來說,可以實現把多個進程合成一個組,然後對這個組的資源進行控制,比如CPU,內存大小、網絡帶寬、磁盤iops等,Linux把CGroup抽象成一個虛擬文件系統,可以掛載到指定的目錄下,Ubuntu 14.04默認自動掛載在/sys/fs/cgroup下,用戶也可以手動掛載,比如掛載Memory子系統(子系統可以實現某類資源的控制,比如CPU、Memory、blkio等)到/mnt下:

sudo mount  -t cgroup -o memory  memory /mnt

掛載後就能像查看本地文件一樣瀏覽進程組以及資源控制情況,控制組並不是孤立的,而是組織成樹狀結構構成進程組樹,控制組的子節點會繼承父節點。下面以Memory子系統爲例,

ls /sys/fs/cgroup/memory/

輸出:

cgroup.clone_children  memory.kmem.failcnt                 memory.kmem.tcp.usage_in_bytes   memory.memsw.usage_in_bytes      memory.swappiness
cgroup.event_control   memory.kmem.limit_in_bytes          memory.kmem.usage_in_bytes       memory.move_charge_at_immigrate  memory.usage_in_bytes
cgroup.procs           memory.kmem.max_usage_in_bytes      memory.limit_in_bytes            memory.numa_stat                 memory.use_hierarchy
cgroup.sane_behavior   memory.kmem.slabinfo                memory.max_usage_in_bytes        memory.oom_control               notify_on_release
docker                 memory.kmem.tcp.failcnt             memory.memsw.failcnt             memory.pressure_level            release_agent
memory.failcnt         memory.kmem.tcp.limit_in_bytes      memory.memsw.limit_in_bytes      memory.soft_limit_in_bytes       tasks
memory.force_empty     memory.kmem.tcp.max_usage_in_bytes  memory.memsw.max_usage_in_bytes  memory.stat                      user

以上是根控制組的資源限制情況,我們以創建控制內存爲4MB的Docker容器爲例:

docker run  -m 4MB -d busybox ping localhost

返回ID爲0532d4f4af67,自動會創建以Docker實例DI爲名的控制組,位於/sys/fs/cgroup/memory/docker/0532d4f4af67...,我們查看該目錄下的memory.limit_in_bytes文件內容爲:

cat memory.limit_in_bytes
4194304

即最大的可使用的內存爲4MB,正好是我們啓動Docker所設定的。

由以上可知,容器可以通過CGroup實現資源控制。

Docker技術

在Docker之前其實容器技術早就有了,Google的Borg以及Omega都利用了容器技術。但是之前容器一直沒有形成一個標準,也沒有一個很好的管理工具。LXC是Linux原生支持的容器,很多工具依賴於具體的發行版,可能會出現移植性差的問題,並且也缺乏一組完善的管理工具集。而Docker基於底層的內核特性的基礎上,在上層構建了一個更高層次的具備多個強大功能的工具集,它是PaaS提供商dotCloud開源的一個基於LXC的高級容器引擎,簡單說Docker提供了一個能夠方便管理容器的工具並形成標準。Docker相當於把應用以及應用所依賴的環境完完整整地打成了一個包,這個包拿到哪裏都能原生運行。

其特性包括:

  • 快速構建基於容器的分佈式應用

  • 具有容器的所有優點

  • 提供原生的資源監控

  • 自動構建和版本控制

  • 快速構建和重組

Docker與虛擬機原理對比:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

容器技術在很早就有了,因此不能說Docker發明了容器技術,而僅僅是發明了一套完整的管理容器的工具集。但其實Docker核心的創新在於它的鏡像管理,因此有人說:

Docker = LXC + Docker Image

Docker鏡像的創新之處在於使用了類似層次的文件系統AUFS,簡單說就是一個鏡像是由多個鏡像層層疊加的,從一個Base鏡像中通過加入一些軟件構成一個新層的鏡像,依次構成最後的鏡像,如圖:

640?wx_fmt=png&wxfrom=5&wx_lazy=1

知乎:Docker的幾點疑問:

Image的分層,可以想象成Photoshop中不同的layer。每一層中包含特定的文件,當Container運行時,這些疊加在一起的層就構成了Container的運行環境(包括相應的文件、運行庫等,不包括內核)。Image通過依賴的關係,來確定整個鏡像內到底包含那些文件。之後的版本的Docker,會推出Squash的功能,把不同的層壓縮成爲一個,和Photoshop中合併層的感覺差不多。
作者:Honglin Feng
鏈接:https://www.zhihu.com/question/25394149/answer/30671258 來源:知乎 著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

這裏利用了COW(copy on write)技術,即從一個鏡像啓動一個容器實例,這個鏡像是以只讀形式掛載的,即不允許任何修改操作。當在容器實例中修改一個文件時,會首先從鏡像裏把這個文件拷貝到可寫層,然後執行更新操作。當讀一個文件時,會首先從可寫層裏找這個文件,若這個文件存在,直接返回文件內容,如果不存在這個文件,則會從最頂層的鏡像開始查找,直到最底層的Base鏡像。這裏存在的一個問題是,當鏡像層很多時,查找一個文件可能需要一層一層查找,影響性能。基於Ceph構建OpenStack創建虛擬機也一樣的原理,我們上傳一個鏡像到Glance時,首先對這個鏡像創建一個快照並保護起來不允許寫操作,當基於這個鏡像創建虛擬機時,直接從鏡像快照克隆一個新的rbd p_w_picpath作爲虛擬機的根磁盤,最開始這個根磁盤除了指向其parent快照的指針,沒有任何內容,不佔任何磁盤空間,當虛擬機需要修改一個對象時,首先從parent中拷貝這個對象到它所在的空間,再執行更新操作。當讀一個文件時,如果存在這個文件,直接讀取,否則需要去parent所在的p_w_picpath中查找。

這樣的好處是:

  • 節省存儲空間——多個鏡像共享Base Image存儲;

  • 節省網絡帶寬——拉取鏡像時,只需要拉取本地沒有的鏡像層,本地已經存在的可以共享,避免多次傳輸拷貝;

  • 節省內存空間——多個實例可共享Base Image,多個實例的進程命中緩存內容的機率大大增加。如果基於某個鏡像啓動一個虛擬機需要資源k,則啓動n個同一個鏡像的虛擬機需要佔用資源kn,但如果基於某個鏡像啓動一個Docker容器需要資源k,無論啓動多少個實例,資源都是k;

  • 維護升級方便——相比於copy-on-write類型的FS,Base Image也是可以掛載爲可Writeable的,可以通過更新Base Image而一次性更新其之上的Container;

  • 允許在不更改Base Image的同時修改其目錄中的文件——所有寫操作都發生在最上層的writeable層中,這樣可以大大增加Base Image能共享的文件內容。

使用容器技術,帶來了很多優點,但同時也存在一些問題:

  • 隔離性相對虛擬機弱-由於和宿主機共享內核,帶來很大的安全隱患,容易發生逃逸。

  • 如果某些應用需要特定的內核特性,使用容器不得不更換宿主機內核。

更多關於AUFS參考酷殼:Docker基礎技術-AUFS(http://coolshell.cn/articles/17061.html)。



Hyper技術


上文提到容器存在的問題,並且Docker的核心創新在於鏡像管理,即:


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=Docker = LXC + Docker Image


於是就有人提出把容器替換成最初的Hypervisor,而又利用Docker Image的優勢,接下來介紹的Hyper技術以及VMware最新的vic技術大體如此,Hyper官方(https://hyper.sh/)定義:


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=Hyper – a Hypervisor-based Containerization solution


即:


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=Hyper = Hypervisor + Docker Image


簡而言之Hyper是一種基於虛擬化技術(Hypervisor)的Docker引擎。官方(http://mt.sohu.com/20150625/n415640410.shtml)認爲:


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=雖然Hyper同樣通過VM來運行Docker應用,但HyperVM裏並沒有GuestOS,相反的,一個HyperVM內部只有一個極簡的HyperKernel,以及要運行的Docker鏡像。這種Kernel Image的”固態”組合使得HyperVM和Docker容器一樣,實現了Immutable Infrastructure的效果。藉助VM天然的隔離性,Hyper能夠完全避免LXC共享內核的安全隱患。


創建一個基於Hyper的Ubuntu:

sudo hyper run -t ubuntu:latest bash

創建時間小於1秒,確實達到啓動容器的效率。

查看內核版本:

root@ubuntu-latest-7939453236:/# uname -a
Linux ubuntu-latest-7939453236 4.4.0-hyper  #0 SMP Mon Jan 25 01:10:46 CST 2016 x86_64 x86_64 x86_64 GNU/Linux

宿主機內核版本:

$ uname  -a
Linux lenovo 3.13.0-77-generic #121-Ubuntu SMP Wed Jan 20 10:50:42 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

啓動基於Docker的Ubuntu並查看內核版本:

$ docker run -t -i ubuntu:14.04 uname -a
Linux 73a88ca16d94 3.13.0-77-generic #121-Ubuntu SMP Wed Jan 20 10:50:42 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

我們發現Docker和宿主機的內核版本是一樣的,即3.13.0-77-generic,而Hyper內核不一樣,版本爲4.4.0-hyper

以下爲官方數據(https://github.com/hyperhq/hyper):

640?wx_fmt=png&wxfrom=5&wx_lazy=1

因此Hyper是容器和虛擬機的一種很好的折衷技術,未來可能前景廣大,但需要進一步觀察,我個人主要存在以下疑問:

  • 使用極簡的內核,會不會導致某些功能丟失?

  • 是不是需要爲每一個應用維護一個微內核?

  • 有些應用需要特定內核,這些應用實際多麼?可以通過其他方式避免麼?

  • Hyper引擎能否提供和Docker引擎一樣的API,能否在生態圈中相互替代?

  • 隔離性加強的同時也犧牲了部分性能,如何權衡?

總結


近年來容器技術以及微服務架構非常火熱,CaaS有取代傳統IaaS的勢頭,未來雲計算市場誰成爲主流值得期待。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章