Docker與k8s的恩怨情仇(二)—用最簡單的技術實現“容器”


轉載請注明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

第一章:Docker與k8s的恩怨情仇(一)—成為PaaS前浪的Cloud Foundry

第三章:Docker與k8s的恩怨情仇(三)—后浪Docker來勢洶洶

第四章:Docker與k8s的恩怨情仇(四)-雲原生時代的閉源落幕

Docker與k8s的恩怨情仇(五)——Kubernetes的創新

Docker與k8s的恩怨情仇(六)—— “容器編排”上演“終結者”大片

上次我們說到PaaS的發展歷史,從Cloud Foundry黯然退場,到Docker加冕,正是Docker“一點點”的改進,掀起了一場蝴蝶效應,煽動了整個PaaS開源項目市場風起雲涌。

為了讓大家更好的理解“容器”這個PaaS中最核心的技術,本篇將從一個進程開始,為大家講述容器到底是什么,Cloud Foundry等PaaS“前浪”是如何實現容器的。

進程 vs 容器

以Linux操作系統為例,計算機里運行的進程是程序執行之后,從磁盤的二進制文件,到內存、寄存器、堆棧指令等等所用到的相關設備狀態的一個集合,是數據和狀態綜合的動態表現。而容器技術的目標就是對一個進程的狀態和數據進行的隔離和限制。可以說,容器的本質其實就是Linux中的一個特殊進程。這個特殊的進程,主要靠Linux系統提供的兩個機制來實現,這里先回顧一下。

Namespace

Linux Namespace是Linux內核的一項功能,該功能對內核資源進行分區,以使一組進程看到一組資源,而另一組進程看到另一組資源。該功能通過為一組資源和進程具有相同的名稱空間而起作用,但是這些名稱空間引用了不同的資源。資源可能存在於多個空間中。此類資源的示例是進程ID,主機名,用戶ID,文件名以及與網絡訪問和進程間通信相關的某些名稱。其種類列舉如下:

  1. Mount namespaces
  2. UTS namespaces
  3. IPC namespaces
  4. PID namespaces
  5. Network namespaces
  6. User namespaces

超級進程

在Linux操作系統中,PID==1的進程被稱為超級進程,它是整個進程樹的root,負責產生其他所有用戶進程。所有的進程都會被掛在這個進程下,如果這個進程退出了,那么所有的進程都被 kill。

隔離 & 限制

剛才我們提到了隔離和限制,具體指的是什么呢?

隔離

以Docker為例(Cloud Foundry同理,我的機器上沒有安裝后者),我們可以執行下列的命令創建一個簡單的鏡像:
$ docker run -it busybox /bin/sh

這條語句執行的內容是:用docker運行一個容器,容器的鏡像名稱叫busybox,並且運行之后需要執行的命令是/bin/sh,而-it參數表示需要使用標准輸入stdin和分配一個文本輸入輸出環境tty與外部交互。通過這個命令,我們就可以進入到一個容器內部了,分別在容器中和宿主機中執行top命令,可以看到以下結果:

7.png

(在容器內外執行top語句的返回結果)

從中可以發現,容器中的運行進程只剩下了兩個。一個是主進程PID==1的/bin/sh超級進程,另一個是我們運行的top。而宿主機中的其余的所有進程在容器中都看不到了——這就是隔離。

5.jpg

(被隔離的top進程,圖片來自網絡)

原本,每當我們在宿主機上運行一個/bin/sh程序,操作系統都會給它分配一個進程編號,比如PID 100。而現在,我們要通過Docker把這個/bin/sh程序運行在一個容器中,這時候,Docker就會在這個PID 100創建時施加一個“障眼法”,讓他永遠看不到之前的99個進程,這樣運行在容器中的程序就會當自己是PID==1的超級進程。

而這種機制,其實就是對被隔離的程序的進程空間做了手腳,雖然在容器中顯示的PID 1,但是在原本的宿主機中,它其實還是那個PID==100的進程。所使用到的技術就是Linux中的Namespace機制。而這個機制,其實就是Linux在創建進程時的一個可選參數。在Linux中,創建一個線程的函數是(這里沒寫錯就是線程,Linux中線程是用進程實現的,所以可以用來描述進程):

int pid = clone(main_function, stack_size, SIGCHLD, NULL);

如果我們給這個方法添加一個參數比如CLONE_NEWPID:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

那么這個新的進程就會看到一個全新的進程空間,在這個空間里,因為該空間中僅有這一個進程,所以它自己的PID就等於1了。

這樣一個過程就是Linux容器最基本的隔離實現了。

限制

光有namespace隔離的容器就和沒有電腦的程序員一樣,是殘缺不全的。

如果我們只隔離不限制,籠子里面的程序照樣占用系統資源,訪問依舊自由。為了給有了隔離性的程序添加資源限制,就用到了第二個技術:cgroups

cgroups本來是google的工程師在2006年開發的一個程序,全稱是Linux Control Group,是Linux操作系統中用來限制一個進程組能使用資源的上限,包括CPU、內存、磁盤、網絡帶寬等的功能。

通過Cgroups給用戶暴露的API文件系統,用戶可以通過修改文件的值來操作Cgroups功能。

8.png

(被cgroup限制的進程,圖片來自網絡)

在Linux系統(Ubuntu)中可以執行以下命令查看CgroupsAPI文件:

mount -t Cgroups

9.png

(cgroup文件系統)

從上圖可以看到,系統中存在包括cpu、內存、IO等多個Cgroups配置文件。

我們以CPU為例來說明以下Cgroups這個功能。對CPU的限制需要引入兩個參數cfs_period和cfs_quota,我們為了給活字格公有雲Docker內的程序限制CPU時,會經常操作這兩個參數。這兩個參數是組合使用的,意思是在長度為cfs_period時間內,程序組只能分到總量為cfs_quota的CPU時間。也就是說cfs_quota / cfs_period == cpu使用上限。

要想限制某個進程的CPU使用,可以在/sys/fs/Cgroups/cpu目錄下,執行以下命令創建一個文件夾container:

/sys/fs/Cgroups/cpu/ > mkdir container

此時,我們可以發現系統自動在container目錄下生成的一系列CPU限制的參數文件,這是Linux系統自動生成的,表示我們成功為CPU創建了一個控制組container:

10.png

(默認的CPU資源文件列表)

為了展示CPU限制的實際效果,讓我們執行一個用以下腳本創建的死循環:

while : ; do : ; done &

我們在top命令結果中會看到返回的進程為398,因為死循環,cpu占用率為100%:

11.png

(死循環的進程占了100% CPU)

這時,我們再看下container目錄下的cpu.cfs_quota_us和cpu.cfs_period_us:

12.png

(默認情況下CPU的限制參數)
這里是沒有做過限制時的樣子。cfs_quota_us為-1說明並沒有限制CPU的運行上限。現在我們改一下這個值:

echo 20000 > /sys/fs/Cgroups/cpu/container/cpu.cfs_quota_us

然后將之前的進程398寫入這個控制組的tasks文件中:

echo 398 > /sys/fs/Cgroups/cpu/container/tasks

這時再top一下,發現剛才的死循環的CPU使用率變成20%了,CPU使用資源限制開始生效。

13.png

(使用cgroup限制CPU使用量的死循環進程)

以上,就是通過Cgroups功能對容器做限制的原理了。同理,我們可以用此方法,對一個進程的內存、帶寬等做限制,如果這個進程是一個容器進程,一個資源受控的容器基本就可以展現在你面前了事實上,在雲時代的早期,Cloud Foundry等“前浪”都是采用這種方式創建和管理容器。相比於后來者,Cloud Foundry等在容器的隔離和限制上,雖相對簡單、易於理解,但在一些場景下難免會受到制約。

這里要做一個特別的說明,只有Linux中運行的容器是通過對進程進行限制模擬出來的結果,Windows和Mac下的容器,都是通過Docker Desktop等容器軟件,操作虛擬機模擬出來的“真實”的虛擬容器。

小結

本節從容器的原理和Linux下實現容器隔離和限制的技術入手,介紹了在雲時代早期Cloud Foundry等Paas平台的容器原理。下一節將繼續為大家介紹Docker在Cloud Foundry容器基礎之上又做了什么改動,是如何解決Cloud Foundry致命短板的。

如果您想了解Docker如何攪動風雲,Docker的這個容器又和傳統虛擬機有何區別?

敬請期待下篇,我們繼續嘮。


免責聲明!

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



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