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

dockone.io發表於2016-07-24

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

  作業系統概述

  我們可以把作業系統簡化為:

作業系統 = 核心 + 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技術實現隔離性,以下參考酷殼

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》

  由上表可知,容器利用核心的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與虛擬機器原理對比:

docker-and-vm.jpg

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

Docker = LXC + Docker Image

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

docker-filesystems-multilayer.png

  知乎:Docker的幾點疑問:

Image的分層,可以想象成Photoshop中不同的layer。每一層中包含特定的檔案,當Container執行時,這些疊加在一起的層就構成了Container的執行環境(包括相應的檔案、執行庫等,不包括核心)。Image通過依賴的關係,來確定整個映象內到底包含那些檔案。之後的版本的Docker,會推出Squash的功能,把不同的層壓縮成為一個,和Photoshop中合併層的感覺差不多。
作者:Honglin Feng
連結:https://www.zhihu.com/question ... 71258
來源:知乎

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

  這樣的好處是:

  • 節省儲存空間——多個映象共享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

  Hyper技術

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

Docker = LXC + Docker Image

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

Hyper - a Hypervisor-based Containerization solution

即:

Hyper = Hypervisor + Docker Image

  簡而言之Hyper是一種基於虛擬化技術(Hypervisor)的Docker引擎。官方認為:

雖然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。

  以下為官方資料

hyper.png

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

  • 使用極簡的核心,會不會導致某些功能丟失?
  • 是不是需要為每一個應用維護一個微核心?
  • 有些應用需要特定核心,這些應用實際多麼?可以通過其他方式避免麼?
  • Hyper引擎能否提供和Docker引擎一樣的api,能否在生態圈中相互替代?
  • 隔離性加強的同時也犧牲了部分效能,如何權衡? 

  總結

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

相關文章