Docker?隔離與限制原理介紹
一、為什么 Docker 比虛擬機受歡迎
在上一篇文章中,詳細(xì)介紹了 Linux 容器中用來實現(xiàn)“隔離”的技術(shù)手段:Namespace。而通過這些講解,我們能夠明白,Namespace 技術(shù)實際上修改了應(yīng)用進程看待整個計算機“視圖”,即它的“視線”被操作系統(tǒng)做了限制,只能“看到”某些指定的內(nèi)容。但對于宿主機來說,這些被“隔離”了的進程跟其他進程并沒有太大區(qū)別。
說到這一點,在之前虛擬機與容器技術(shù)的對比圖里,不應(yīng)該把 Docker Engine 或者任何容器管理工具放在跟 Hypervisor 相同的位置,因為它們并不像 Hypervisor 那樣對應(yīng)用進程的隔離環(huán)境負(fù)責(zé),也不會創(chuàng)建任何實體的“容器”,真正對隔離環(huán)境負(fù)責(zé)的是宿主機操作系統(tǒng)本身:
所以,在這個對比圖里,我們應(yīng)該把 Docker 畫在跟應(yīng)用同級別并且靠邊的位置。這意味著,用戶運行在容器里的應(yīng)用進程,跟宿主機上的其他進程一樣,都由宿主機操作系統(tǒng)統(tǒng)一管理,只不過這些被隔離的進程擁有額外設(shè)置過的 Namespace 參數(shù)。而 Docker 項目在這里扮演的角色,更多的是旁路式的輔助和管理工作。
在后續(xù)分享 CRI 和容器運行時的時候還會專門介紹到,其實像 Docker 這樣的角色甚至可以去掉。
這樣的架構(gòu)也解釋了為什么 Docker 項目比虛擬機更受歡迎的原因。
1、優(yōu)點
這是因為,使用虛擬化技術(shù)作為應(yīng)用沙盒,就必須要由 Hypervisor 來負(fù)責(zé)創(chuàng)建虛擬機,這個虛擬機是真實存在的,并且它里面必須運行一個完整的 Guest OS 才能執(zhí)行用戶的應(yīng)用進程。這就不可避免地帶來了額外的資源消耗和占用。
根據(jù)實驗,一個運行著 CentOS 的 KVM 虛擬機啟動后,在不做優(yōu)化的情況下,虛擬機自己就需要占用 100~200 MB 內(nèi)存。此外,用戶應(yīng)用運行在虛擬機里面,它對宿主機操作系統(tǒng)的調(diào)用就不可避免地要經(jīng)過虛擬化軟件的攔截和處理,這本身又是一層性能損耗,尤其對計算資源、網(wǎng)絡(luò)和磁盤 I/O 的損耗非常大。
而相比之下,容器化后的用戶應(yīng)用,卻依然還是一個宿主機上的普通進程,這就意味著這些因為虛擬化而帶來的性能損耗都是不存在的;而另一方面,使用 Namespace 作為隔離手段的容器并不需要單獨的 Guest OS,這就使得容器額外的資源占用幾乎可以忽略不計。
所以說,“敏捷”和“高性能”是容器相較于虛擬機最大的優(yōu)勢,也是它能夠在 PaaS 這種更細(xì)粒度的資源管理平臺上大行其道的重要原因。
2、不足
不過,有利就有弊,基于 Linux Namespace
的隔離機制相比于虛擬化技術(shù)也有很多不足之處,其中最主要的問題就是:隔離得不徹底。
首先,既然容器只是運行在宿主機上的一種特殊的進程,那么多個容器之間使用的就還是同一個宿主機的操作系統(tǒng)內(nèi)核。
盡管你可以在容器里通過 Mount Namespace 單獨掛載其他不同版本的操作系統(tǒng)文件,比如 CentOS 或者 Ubuntu,但這并不能改變共享宿主機內(nèi)核的事實。這意味著,如果你要在 Windows 宿主機上運行 Linux 容器,或者在低版本的 Linux 宿主機上運行高版本的 Linux 容器,都是行不通的。
而相比之下,擁有硬件虛擬化技術(shù)和獨立 Guest OS 的虛擬機就要方便得多了。最極端的例子是,Microsoft 的云計算平臺 Azure,實際上就是運行在 Windows 服務(wù)器集群上的,但這并不妨礙你在它上面創(chuàng)建各種 Linux 虛擬機出來。
其次,在 Linux 內(nèi)核中,有很多資源和對象是不能被 Namespace 化的,最典型的例子就是:時間。
這就意味著,如果你的容器中的程序使用 settimeofday(2) 系統(tǒng)調(diào)用修改了時間,整個宿主機的時間都會被隨之修改,這顯然不符合用戶的預(yù)期。相比于在虛擬機里面可以隨便折騰的自由度,在容器里部署應(yīng)用的時候,“什么能做,什么不能做”,就是用戶必須考慮的一個問題。
此外,由于上述問題,尤其是共享宿主機內(nèi)核的事實,容器給應(yīng)用暴露出來的攻擊面是相當(dāng)大的,應(yīng)用“越獄”的難度自然也比虛擬機低得多。
更為棘手的是,盡管在實踐中我們確實可以使用 Seccomp 等技術(shù),對容器內(nèi)部發(fā)起的所有系統(tǒng)調(diào)用進行過濾和甄別來進行安全加固,但這種方法因為多了一層對系統(tǒng)調(diào)用的過濾,一定會拖累容器的性能。何況,默認(rèn)情況下,誰也不知道到底該開啟哪些系統(tǒng)調(diào)用,禁止哪些系統(tǒng)調(diào)用。
所以,在生產(chǎn)環(huán)境中,沒有人敢把運行在物理機上的 Linux 容器直接暴露到公網(wǎng)上。當(dāng)然,我后續(xù)會講到的基于虛擬化或者獨立內(nèi)核技術(shù)的容器實現(xiàn),則可以比較好地在隔離與性能之間做出平衡。
在介紹完容器的“隔離”技術(shù)之后,我們再來研究一下容器的“限制”問題。
二、資源限制
也許你會好奇,我們不是已經(jīng)通過 Linux Namespace 創(chuàng)建了一個“容器”嗎,為什么還需要對容器做“限制”呢?
我還是以 PID Namespace 為例,來給你解釋這個問題。
雖然容器內(nèi)的第 1 號進程在“障眼法”的干擾下只能看到容器里的情況,但是宿主機上,它作為第 100 號進程與其他所有進程之間依然是平等的競爭關(guān)系。這就意味著,雖然第 100 號進程表面上被隔離了起來,但是它所能夠使用到的資源(比如 CPU、內(nèi)存),卻是可以隨時被宿主機上的其他進程(或者其他容器)占用的。當(dāng)然,這個 100 號進程自己也可能把所有資源吃光。這些情況,顯然都不是一個“沙盒”應(yīng)該表現(xiàn)出來的合理行為。
而Linux Cgroups 就是 Linux 內(nèi)核中用來為進程設(shè)置資源限制的一個重要功能。
有意思的是,Google 的工程師在 2006 年發(fā)起這項特性的時候,曾將它命名為“進程容器”(process container)。實際上,在 Google 內(nèi)部,“容器”這個術(shù)語長期以來都被用于形容被 Cgroups 限制過的進程組。后來 Google 的工程師們說,他們的 KVM 虛擬機也運行在 Borg 所管理的“容器”里,其實也是運行在 Cgroups“容器”當(dāng)中。這和我們今天說的 Docker 容器差別很大。
Linux Cgroups 的全稱是 Linux Control Group。它最主要的作用,就是限制一個進程組能夠使用的資源上限,包括 CPU、內(nèi)存、磁盤、網(wǎng)絡(luò)帶寬等等。
此外,Cgroups 還能夠?qū)M程進行優(yōu)先級設(shè)置、審計,以及將進程掛起和恢復(fù)等操作。在今天的分享中,我只和你重點探討它與容器關(guān)系最緊密的“限制”能力,并通過一組實踐來帶你認(rèn)識一下 Cgroups。
在 Linux 中,Cgroups 給用戶暴露出來的操作接口是文件系統(tǒng),即它以文件和目錄的方式組織在操作系統(tǒng)的 /sys/fs/cgroup 路徑下。在 Ubuntu 16.04 機器里,我可以用 mount 指令把它們展示出來,這條命令是:
$ mount -t cgroup cpuset on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cpu on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu) cpuacct on /sys/fs/cgroup/cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct) blkio on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) memory on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) ...
它的輸出結(jié)果,是一系列文件系統(tǒng)目錄。如果你在自己的機器上沒有看到這些目錄,那你就需要自己去掛載 Cgroups,具體做法可以自行 Google。
可以看到,在 /sys/fs/cgroup 下面有很多諸如 cpuset、cpu、 memory 這樣的子目錄,也叫子系統(tǒng)。這些都是我這臺機器當(dāng)前可以被 Cgroups 進行限制的資源種類。而在子系統(tǒng)對應(yīng)的資源種類下,你就可以看到該類資源具體可以被限制的方法。比如,對 CPU 子系統(tǒng)來說,我們就可以看到如下幾個配置文件,這個指令是:
$ ls /sys/fs/cgroup/cpu cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
如果熟悉 Linux CPU 管理的話,你就會在它的輸出里注意到 cfs_period 和 cfs_quota 這樣的關(guān)鍵詞。這兩個參數(shù)需要組合使用,可以用來限制進程在長度為 cfs_period 的一段時間內(nèi),只能被分配到總量為 cfs_quota 的 CPU 時間。
而這樣的配置文件又如何使用呢?
你需要在對應(yīng)的子系統(tǒng)下面創(chuàng)建一個目錄,比如,我們現(xiàn)在進入 /sys/fs/cgroup/cpu 目錄下:
root@ubuntu:/sys/fs/cgroup/cpu$ mkdir container root@ubuntu:/sys/fs/cgroup/cpu$ ls container/ cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
這個目錄就稱為一個“控制組”。你會發(fā)現(xiàn),操作系統(tǒng)會在你新創(chuàng)建的 container 目錄下,自動生成該子系統(tǒng)對應(yīng)的資源限制文件。
現(xiàn)在,我們在后臺執(zhí)行這樣一條腳本:
$ while : ; do : ; done & [1] 226
顯然,它執(zhí)行了一個死循環(huán),可以把計算機的 CPU 吃到 100%,根據(jù)它的輸出,我們可以看到這個腳本在后臺運行的進程號(PID)是 226。
這樣,我們可以用 top 指令來確認(rèn)一下 CPU 有沒有被打滿:
$ top %Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
在輸出里可以看到,CPU 的使用率已經(jīng) 100% 了(%Cpu0 :100.0 us)。
而此時,我們可以通過查看 container 目錄下的文件,看到 container 控制組里的 CPU quota 還沒有任何限制(即:-1),CPU period 則是默認(rèn)的 100 ms(100000 us):
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us -1 $ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us 100000
接下來,我們可以通過修改這些文件的內(nèi)容來設(shè)置限制。
比如,向 container 組里的 cfs_quota 文件寫入 20 ms(20000 us):
$ echo 20000 > /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us
結(jié)合前面的介紹,你應(yīng)該能明白這個操作的含義,它意味著在每 100 ms 的時間里,被該控制組限制的進程只能使用 20 ms 的 CPU 時間,也就是說這個進程只能使用到 20% 的 CPU 帶寬。
接下來,我們把被限制的進程的 PID 寫入 container 組里的 tasks 文件,上面的設(shè)置就會對該進程生效了:
$ echo 226 > /sys/fs/cgroup/cpu/container/tasks
我們可以用 top 指令查看一下:
$ top %Cpu0 : 20.3 us, 0.0 sy, 0.0 ni, 79.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
可以看到,計算機的 CPU 使用率立刻降到了 20%(%Cpu0 : 20.3 us)。
除 CPU 子系統(tǒng)外,Cgroups 的每一項子系統(tǒng)都有其獨有的資源限制能力,比如:
- blkio,為塊設(shè)備設(shè)定I/O 限制,一般用于磁盤等設(shè)備;
- cpuset,為進程分配單獨的 CPU 核和對應(yīng)的內(nèi)存節(jié)點;
- memory,為進程設(shè)定內(nèi)存使用的限制。
Linux Cgroups 的設(shè)計還是比較易用的,簡單粗暴地理解呢,它就是一個子系統(tǒng)目錄加上一組資源限制文件的組合。而對于 Docker 等 Linux 容器項目來說,它們只需要在每個子系統(tǒng)下面,為每個容器創(chuàng)建一個控制組(即創(chuàng)建一個新目錄),然后在啟動容器進程之后,把這個進程的 PID 填寫到對應(yīng)控制組的 tasks 文件中就可以了。
而至于在這些控制組下面的資源文件里填上什么值,就靠用戶執(zhí)行 docker run 時的參數(shù)指定了,比如這樣一條命令:
$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash
在啟動這個容器后,我們可以通過查看 Cgroups 文件系統(tǒng)下,CPU 子系統(tǒng)中,“docker”這個控制組里的資源限制文件的內(nèi)容來確認(rèn):
$ 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 帶寬。
三、總結(jié)
在這篇文章中,我首先介紹了容器使用 Linux Namespace 作為隔離手段的優(yōu)勢和劣勢,對比了 Linux 容器跟虛擬機技術(shù)的不同,進一步明確了“容器只是一種特殊的進程”這個結(jié)論。
除了創(chuàng)建 Namespace 之外,在后續(xù)關(guān)于容器網(wǎng)絡(luò)的分享中,我還會介紹一些其他 Namespace 的操作,比如看不見摸不著的 Linux Namespace 在計算機中到底如何表示、一個進程如何“加入”到其他進程的 Namespace 當(dāng)中,等等。
緊接著,我詳細(xì)介紹了容器在做好了隔離工作之后,又如何通過 Linux Cgroups 實現(xiàn)資源的限制,并通過一系列簡單的實驗,模擬了 Docker 項目創(chuàng)建容器限制的過程。
通過以上講述,你現(xiàn)在應(yīng)該能夠理解,一個正在運行的 Docker 容器,其實就是一個啟用了多個 Linux Namespace 的應(yīng)用進程,而這個進程能夠使用的資源量,則受 Cgroups 配置的限制。
這也是容器技術(shù)中一個非常重要的概念,即:容器是一個“單進程”模型。
由于一個容器的本質(zhì)就是一個進程,用戶的應(yīng)用進程實際上就是容器里 PID=1 的進程,也是其他后續(xù)創(chuàng)建的所有進程的父進程。這就意味著,在一個容器中,你沒辦法同時運行兩個不同的應(yīng)用,除非你能事先找到一個公共的 PID=1 的程序來充當(dāng)兩個不同應(yīng)用的父進程,這也是為什么很多人都會用 systemd 或者 supervisord 這樣的軟件來代替應(yīng)用本身作為容器的啟動進程。
但是,在后面分享容器設(shè)計模式時,我還會推薦其他更好的解決辦法。這是因為容器本身的設(shè)計,就是希望容器和應(yīng)用能夠同生命周期,這個概念對后續(xù)的容器編排非常重要。否則,一旦出現(xiàn)類似于“容器是正常運行的,但是里面的應(yīng)用早已經(jīng)掛了”的情況,編排系統(tǒng)處理起來就非常麻煩了。
另外,跟 Namespace 的情況類似,Cgroups 對資源的限制能力也有很多不完善的地方,被提及最多的自然是 /proc 文件系統(tǒng)的問題。
眾所周知,Linux 下的 /proc 目錄存儲的是記錄當(dāng)前內(nèi)核運行狀態(tài)的一系列特殊文件,用戶可以通過訪問這些文件,查看系統(tǒng)以及當(dāng)前正在運行的進程的信息,比如 CPU 使用情況、內(nèi)存占用率等,這些文件也是 top 指令查看系統(tǒng)信息的主要數(shù)據(jù)來源。
但是,你如果在容器里執(zhí)行 top 指令,就會發(fā)現(xiàn),它顯示的信息居然是宿主機的 CPU 和內(nèi)存數(shù)據(jù),而不是當(dāng)前容器的數(shù)據(jù)。
造成這個問題的原因就是,/proc 文件系統(tǒng)并不知道用戶通過 Cgroups 給這個容器做了什么樣的資源限制,即:/proc 文件系統(tǒng)不了解 Cgroups 限制的存在。
在生產(chǎn)環(huán)境中,這個問題必須進行修正,否則應(yīng)用程序在容器里讀取到的 CPU 核數(shù)、可用內(nèi)存等信息都是宿主機上的數(shù)據(jù),這會給應(yīng)用的運行帶來非常大的困惑和風(fēng)險。這也是在企業(yè)中,容器化應(yīng)用碰到的一個常見問題,也是容器相較于虛擬機另一個不盡如人意的地方。
到此這篇關(guān)于Docker 隔離與限制原理介紹的文章就介紹到這了,更多相關(guān)Docker 隔離與限制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
CentOS系統(tǒng)下docker的安裝配置及使用介紹
這篇文章主要介紹了CentOS系統(tǒng)下docker的安裝配置及使用詳細(xì)介紹,需要的朋友可以參考下2016-10-10Docker部署java項目的詳細(xì)步驟(利用Dockerfile方式)
docker可以利用簡單的編寫程序構(gòu)建出任何你想要的環(huán)境,同時可以跟業(yè)務(wù)代碼相結(jié)合,快速構(gòu)建和生成所需要的應(yīng)用,下面這篇文章主要給大家介紹了關(guān)于Docker部署java項目的詳細(xì)步驟,本文主要利用的是Dockerfile方式,需要的朋友可以參考下2022-08-08docker中如何將jar包構(gòu)建成鏡像并執(zhí)行
這篇文章主要介紹了docker中如何將jar包構(gòu)建成鏡像并執(zhí)行問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05Docker中關(guān)于Namespace隔離機制全面解析
為了更好地理解容器的運行原理,本篇文章將會以?Linux?宿主機為例,介紹容器的底層技術(shù),包括容器的命名空間、控制組、聯(lián)合文件系統(tǒng)等,需要的朋友可以參考下2022-06-06Docker安裝RabbitMQ AMQP協(xié)議及重要角色
這篇文章主要為大家介紹了Docker安裝RabbitMQ AMQP協(xié)議和主要角色詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05