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

葡萄城技術團隊發表於2021-06-23

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

上次我們說到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程式,作業系統都會給它分配一個程式編號,比如PID100。而現在,我們要通過Docker把這個/bin/sh程式執行在一個容器中,這時候,Docker就會在這個PID100建立時施加一個“障眼法”,讓他永遠看不到之前的99個程式,這樣執行在容器中的程式就會當自己是PID==1的超級程式。

而這種機制,其實就是對被隔離的程式的程式空間做了手腳,雖然在容器中顯示的PID1,但是在原本的宿主機中,它其實還是那個PID100的程式。所使用到的技術就是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的這個容器又和傳統虛擬機器有何區別?

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

相關文章