Linux容器中用來實現「隔離」的技術手段:Namespace。Namespace實際上修改了應用進程看待整個計算機「視圖」,即它的「視線」被操做系統作了限制,只能「看到」某些指定的內容
。對於宿主機來講,這些被「隔離」了的進程跟其餘進程並無區別。docker
在以前虛擬機與容器技術的對比圖裏,不該該把Docker Engine或者任何容器管理工具放在跟Hypervisor相同的位置,由於它們並不像Hypervisor那樣對應用進程的隔離環境負責,也不會建立任何實體的「容器」,真正對隔離環境負責的是宿主機操做系統自己:ubuntu
在這個對比圖裏,應該把Docker畫在跟應用同級別而且靠邊的位置。
用戶運行在容器裏的應用進程,跟宿主機上的其餘進程同樣,都由宿主機操做系通通一管理,只不過這些被隔離的進程擁有額外設置過的Namespace參數
Docker在這裏更多的是輔助和管理工做。segmentfault
這樣的架構也解釋了爲何Docker項目比虛擬機更受歡迎的緣由。設計模式
使用虛擬化技術做爲應用沙盒,就必需要由Hypervisor來負責建立虛擬機,這個虛擬機是真實存在的,它裏面必須運行一個完整的Guest OS才能執行用戶的應用進程。這就不可避免地帶來了額外的資源消耗和佔用。安全
根據實驗,一個運行着CentOS的KVM虛擬機啓動後,在不作優化的狀況下,虛擬機本身就須要佔用100~200 MB內存。此外,用戶應用運行在虛擬機裏面,它對宿主機操做系統的調用就不可避免地要通過虛擬化軟件的攔截和處理,這自己又是一層性能損耗,尤爲對計算資源、網絡和磁盤I/O的損耗很是大。
而容器化後的用戶應用,依然仍是宿主機上的一個普通進程,這就意味着這些由於虛擬化而帶來的性能損耗都是不存在的
使用Namespace做爲隔離手段的容器並不須要單獨的Guest OS,這就使得容器額外的資源佔用幾乎能夠忽略不計。bash
「敏捷」和「高性能」是容器相較於虛擬機最大的優點服務器
不過,有利就有弊,基於Linux Namespace的隔離機制相比於虛擬化技術也有不少不足之處,其中最主要的問題就是:網絡
儘管能夠在容器裏經過 Mount Namespace
單獨掛載其餘不一樣版本的操做系統文件,好比 CentOS 或者 Ubuntu,但這並不能改變共享宿主機內核的事實!
這表明若是要在Windows宿主機上運行Linux容器,或者在低版本的Linux宿主機上運行高版本的Linux容器,都是impossible!架構
相比之下,擁有硬件虛擬化技術和獨立Guest OS的虛擬機就要方便
最極端的例子是,Microsoft的雲計算平臺Azure,實際上就是運行在Windows服務器集羣上的,但這並不妨礙你在它上面建立各類Linux虛擬機工具
最典型的例子:時間
若是你的容器中的程序使用settimeofday(2)系統調用修改了時間,整個宿主機的時間都會被隨之修改,這顯然不符合用戶的預期
相比於在虛擬機裏面能夠隨便折騰,在容器裏部署應用的時候,「什麼能作,什麼不能作」,都是用戶必須考慮的問題。
此外,因爲上述問題,尤爲是共享宿主機內核的事實
應用「越獄」的難度天然也比虛擬機低得多。
儘管可使用Seccomp等技術,過濾和甄別容器內部發起的全部系統調用來進行安全加固,但這就多了一層對系統調用的過濾,必定會拖累容器的性能。況且,默認狀況下,誰也不知道到底該開啓哪些系統調用,禁止哪些系統調用。
因此,在生產環境中,沒有人敢把運行在物理機上的Linux容器直接暴露到公網上。
基於虛擬化或者獨立內核技術的容器實現,則能夠比較好地在隔離與性能之間作出平衡。
Linux Namespace建立了一個「容器」,爲何還要對容器作「限制」呢?
以PID Namespace爲例
雖然容器內的第1號進程在「障眼法」的干擾下只能看到容器裏的狀況,可是宿主機上,它做爲第100號進程與其餘全部進程之間依然是平等的競爭關係
。
這就意味着,雖然第100號進程表面上被隔離了起來,可是它所可以使用到的資源(好比CPU、內存),卻可隨時被宿主機上其餘進程(或容器)佔用的。固然,這個100號進程本身也可能把全部資源吃光。這些狀況,顯然都不是一個「沙盒」應該表現出來的合理行爲。
Linux Cgroups就是Linux內核中用來爲進程設置資源限制的一個重要功能。
Google的工程師在2006年發起這項特性的時候,曾將它命名爲「進程容器」(process container)。實際上,在Google內部,「容器」這個術語長期以來都被用於形容被Cgroups限制過的進程組。後來Google的工程師們說,他們的KVM虛擬機也運行在Borg所管理的「容器」裏,其實也是運行在Cgroups「容器」當中。這和咱們今天說的Docker容器差異很大。
Linux Cgroups的全稱是Linux Control Group。它最主要的做用,就是限制一個進程組可以使用的資源上限,包括CPU、內存、磁盤、網絡帶寬等等。
此外,Cgroups還可以對進程進行優先級設置、審計,以及將進程掛起和恢復等操做。只探討它與容器關係最緊密的「限制」能力,並經過一組實踐來認識一下Cgroups。
在Linux中,Cgroups給用戶暴露出來的操做接口是文件系統,即它以文件和目錄的方式組織在操做系統的/sys/fs/cgroup路徑下
它的輸出結果,是一系列文件系統目錄(若是你在本身的機器上沒有看到這些目錄,那你就須要本身去掛載Cgroups)
在/sys/fs/cgroup下面有不少諸如cpuset、cpu、 memory這樣的子目錄,也叫子系統
這些都是我這臺機器當前能夠被Cgroups進行限制的資源種類。
而在子系統對應的資源種類下,你就能夠看到該類資源具體能夠被限制的方法。
注意到cfs_period和cfs_quota這樣的關鍵詞,這兩個參數須要組合使用,可用來限制進程在長度爲cfs_period的一段時間內,只能被分配到總量爲cfs_quota的CPU時間
須要在對應的子系統下面建立一個目錄
好比,咱們如今進入/sys/fs/cgroup/cpu目錄下:
這個目錄就稱爲一個「控制組」。
OS會在你新建立的container目錄下,自動生成該子系統對應的資源限制文件!
如今,咱們在後臺執行這樣一條腳本:
顯然,它執行了一個死循環,能夠把計算機的CPU吃到100%,根據它的輸出,咱們能夠看到這個腳本在後臺運行的進程號(PID)
因而,能夠用top指令來確認一下CPU有沒有被打滿:
在輸出裏能夠看到,CPU的使用率已經100%了(%Cpu0 :100.0 us)。
而此時,咱們能夠經過查看container目錄下的文件,看到container控制組裏的CPU quota尚未任何限制(即:-1),CPU period則是默認的100 ms(100000 us):
接下來,咱們能夠經過修改這些文件的內容來設置限制。
好比,向container組裏的cfs_quota文件寫入20 ms(20000 us):
結合前面的介紹,你應該能明白這個操做的含義,它意味着在每100 ms的時間裏,被該控制組限制的進程只能使用20 ms的CPU時間,也就是說這個進程只能使用到20%的CPU帶寬。
接下來,咱們把被限制的進程的PID寫入container組裏的tasks文件,上面的設置就會對該進程生效了:
咱們能夠用top指令查看一下:
能夠看到,計算機的CPU使用率馬上降到了20%
除CPU子系統外,Cgroups的每一項子系統都有其獨有的資源限制能力,好比:
Linux Cgroups 就是一個子系統目錄加上一組資源限制文件的組合
而對於Docker等Linux容器項目來講,只需在每一個子系統下面,爲每一個容器建立一個控制組(即建立一個新目錄),而後在啓動容器進程以後,把這個進程的PID填寫到對應控制組的tasks文件中!
而至於在這些控制組下面的資源文件裏填上什麼值,就靠用戶執行docker run時的參數指定了,好比這樣一條命令:
$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
在啓動這個容器後,咱們能夠經過查看Cgroups文件系統下,CPU子系統中,「docker」這個控制組裏的資源限制文件的內容來確認:
$ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_period_us 100000 $ cat /sys/fs/cgroup/cpu/docker/5d5c9f67d/cpu.cfs_quota_us 20000
這就意味着這個Docker容器,只能使用到20%的CPU帶寬。
首先介紹了容器使用Linux Namespace做爲隔離手段的優點和劣勢,對比了Linux容器跟虛擬機技術的不一樣,進一步明確了「容器只是一種特殊的進程」這個結論。
除了建立Namespace以外,在後續還會介紹一些其餘Namespace的操做,好比看不見摸不着的Linux Namespace在計算機中到底如何表示、一個進程如何「加入」到其餘進程的Namespace當中,等等。
緊接着詳細介紹了容器在作好了隔離工做以後,又如何經過Linux Cgroups實現資源的限制,並經過一系列簡單的實驗,模擬了Docker項目建立容器限制的過程。
如今應該可以理解,一個正在運行的Docker容器,其實就是一個啓用了多個Linux Namespace的應用進程,而這個進程可以使用的資源量,則受Cgroups配置的限制。
這也是容器技術中一個很是重要的概念,即:容器是一個「單進程」模型
因爲一個容器的本質就是一個進程,用戶的應用進程實際上就是容器裏PID=1的進程,也是其餘後續建立的全部進程的父進程。
這就意味着,在一個容器中,你沒辦法同時運行兩個不一樣的應用,除非你能事先找到一個公共的PID=1的程序來充當兩個不一樣應用的父進程,這也是爲何不少人都會用systemd或者supervisord這樣的軟件來代替應用自己做爲容器的啓動進程。
可是,在後面分享容器設計模式時,我還會推薦其餘更好的解決辦法。這是由於容器自己的設計,就是但願容器和應用可以同生命週期,這個概念對後續的容器編排很是重要。不然,一旦出現相似於「容器是正常運行的,可是裏面的應用早已經掛了」的狀況,編排系統處理起來就很是麻煩了。
另外,跟Namespace的狀況相似,Cgroups對資源的限制能力也有不少不完善的地方,被說起最多的天然是/proc文件系統的問題。
Linux下的/proc目錄存儲的是記錄當前內核運行狀態的一系列特殊文件,用戶能夠經過訪問這些文件,查看系統以及當前正在運行的進程的信息,好比CPU使用狀況、內存佔用率等,這些文件也是top指令查看系統信息的主要數據來源。
可是若是在容器裏執行top指令,就會發現,它顯示的信息竟然是宿主機的CPU和內存數據,而不是當前容器的數據。
形成這個問題的緣由就是,/proc文件系統並不知道用戶經過Cgroups給這個容器作了什麼樣的資源限制,即:/proc文件系統不瞭解Cgroups限制的存在。
在生產環境中,這個問題必須進行修正,不然應用程序在容器裏讀取到的CPU核數、可用內存等信息都是宿主機上的數據,這會給應用的運行帶來很是大的困惑和風險。
這也是在企業中,容器化應用碰到的一個常見問題,也是容器相較於虛擬機另外一個不盡如人意的地方