轉載請注明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
第一章: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,文件名以及與網絡訪問和進程間通信相關的某些名稱。其種類列舉如下:
- Mount namespaces
- UTS namespaces
- IPC namespaces
- PID namespaces
- Network namespaces
- User namespaces
超級進程
在Linux操作系統中,PID==1的進程被稱為超級進程,它是整個進程樹的root,負責產生其他所有用戶進程。所有的進程都會被掛在這個進程下,如果這個進程退出了,那么所有的進程都被 kill。
隔離 & 限制
剛才我們提到了隔離和限制,具體指的是什么呢?
隔離
以Docker為例(Cloud Foundry同理,我的機器上沒有安裝后者),我們可以執行下列的命令創建一個簡單的鏡像:
$ docker run -it busybox /bin/sh
這條語句執行的內容是:用docker運行一個容器,容器的鏡像名稱叫busybox,並且運行之后需要執行的命令是/bin/sh,而-it參數表示需要使用標准輸入stdin和分配一個文本輸入輸出環境tty與外部交互。通過這個命令,我們就可以進入到一個容器內部了,分別在容器中和宿主機中執行top命令,可以看到以下結果:
(在容器內外執行top語句的返回結果)
從中可以發現,容器中的運行進程只剩下了兩個。一個是主進程PID==1的/bin/sh超級進程,另一個是我們運行的top。而宿主機中的其余的所有進程在容器中都看不到了——這就是隔離。
(被隔離的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功能。
(被cgroup限制的進程,圖片來自網絡)
在Linux系統(Ubuntu)中可以執行以下命令查看CgroupsAPI文件:
mount -t Cgroups
(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:
(默認的CPU資源文件列表)
為了展示CPU限制的實際效果,讓我們執行一個用以下腳本創建的死循環:
while : ; do : ; done &
我們在top命令結果中會看到返回的進程為398,因為死循環,cpu占用率為100%:
(死循環的進程占了100% CPU)
這時,我們再看下container目錄下的cpu.cfs_quota_us和cpu.cfs_period_us:
(默認情況下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使用資源限制開始生效。
(使用cgroup限制CPU使用量的死循環進程)
以上,就是通過Cgroups功能對容器做限制的原理了。同理,我們可以用此方法,對一個進程的內存、帶寬等做限制,如果這個進程是一個容器進程,一個資源受控的容器基本就可以展現在你面前了事實上,在雲時代的早期,Cloud Foundry等“前浪”都是采用這種方式創建和管理容器。相比於后來者,Cloud Foundry等在容器的隔離和限制上,雖相對簡單、易於理解,但在一些場景下難免會受到制約。
這里要做一個特別的說明,只有Linux中運行的容器是通過對進程進行限制模擬出來的結果,Windows和Mac下的容器,都是通過Docker Desktop等容器軟件,操作虛擬機模擬出來的“真實”的虛擬容器。
小結
本節從容器的原理和Linux下實現容器隔離和限制的技術入手,介紹了在雲時代早期Cloud Foundry等Paas平台的容器原理。下一節將繼續為大家介紹Docker在Cloud Foundry容器基礎之上又做了什么改動,是如何解決Cloud Foundry致命短板的。
如果您想了解Docker如何攪動風雲,Docker的這個容器又和傳統虛擬機有何區別?
敬請期待下篇,我們繼續嘮。