當前位置:
首頁 > 科技 > 如何搞懂容器的核心技術點?

如何搞懂容器的核心技術點?

作者 |阿文

責編 | 郭芮

很多接觸過類似KVM、Vmware 等虛擬化產品的開發者一定知道,傳統的虛擬機其實是模擬真實計算機硬體,然後需要獨立安裝一個單獨的操作系統。這個操作系統可以是 Linux 或者是 windows,而 Docker 容器則不需要你去安裝動則幾十個 G 的操作系統——它提供的鏡像啟動後能夠做到很小,例如 busybox 鏡像只有幾 M 大小:

而當我們執行:

進入到 docker 容器中,得到一個和宿主機一樣的 shell 終端,和我們連接虛擬機得到的終端幾乎沒什麼不同,我們還可以在終端中執行例如:

亦或是安裝各種命令。

等等,這似乎這就是一台虛擬機嘛。可是事實上真的是這樣嗎?

作為一名雲計算行業的從業人員,我見過很多小白用戶把容器當初虛擬機來使用,因為在他們看來啟動一個容器比啟動一個虛擬機要快很多,而且不需要手動去安裝操作系統,在容器的終端中執行各種命令也和虛擬機沒什麼區別?這些初學者,他們會使用 Dockerfile 構建 Docker 鏡像,也能夠熟練的使用 Docker 的各種命令來管理容器。可是當遇到一些問題時,他們便束手無策,因為他們把容器鏡像當成一個小型的操作系統來使用,什麼東西都往容器放,導致容器的體積不斷變大,導致一個容器鏡像就高達到了幾個 G,而對此他們便束手無策了,歸其原因,還是因為他們並不了解 Docker。

今天我就跟大家聊一聊 Docker 到底是個什麼東西。

容器其實是一種特殊進程

容器其實本質上就是一個進程,只不過容器的進程是比較特殊的。

容器技術的核心功能,就是通過約束和修改進程的動態表現,創造出一個「邊界」,通過「障眼法」讓人覺得它是一個獨立的系統。大多數容器都是使用 Cgroups 技術來約束進程,通過 Namespace 技術來修改進程的視圖。

Namespace

什麼是 Namespace ?

我們通過一個案例來講解,這裡我們使用 Docker 運行一個 busybox 的容器:

我們可以看到在容器中,只有 2 個進程在雲信,一個 PID 為 1 的進程,它就是我們的 sh 程序。

我們再重新打開一個窗口執行執行 ps aux | grep docker:

可以看到進程 14669 正在執行`docker run -it busybox /bin/sh`,事實上這個 14669 才是這個 Docker 容器的真正 PID。

那它是如何做到在我們 exec 進入容器之後把進程 ID 改成 1 的呢?事實上進程 ID=1 正是操作系統的第一號進程,它是所有進程的父進程。我們可以通過ps aux | grep systemd 查看會發現 systemd 為 1 號進行:

這種技術被稱為 Namespace。

Namespace 其實是在創建新進程時候加了一個可選參數,它利用 Linux 的系統調用 clone() 為新創建的進程指定一個 CLONE_NEWPID 的參數,那麼新創建的進程就會看到一個全新的進程空間,在這個進程空間裡面它的 PID 就是 1。

Namespace 除了可以模擬 PID 之外,還提供了 Mout、UTS、IPC、Network 和 User 等,在不同的進程上下文做隔離操作。

而這些就是 Linux 容器的基本實現原理。

Cgroups

有了 Namespace ,使得容器就像一個沙盒一樣看起來在一個獨立的系統內運行,與宿主機互不干擾,可是,事實上只是 Namespace 隔離起來並不徹底。因為容器只是運行在宿主機上的一種特殊進程,所有的容器還是要共享宿主機的操作系統內核。並且有一些資源和對象是不能被隔離的,比如時間,如果你修改了容器的時間,那麼操作系統的實際也會被修改。這顯然不是我們希望看到的。另外使用 Namespace 並不能限制一個容器使用資源的邊界,例如我們要限制一個容器使用的 CPU 資源或內存等,這些是 Namespace 無法做到的,它需要 Cgroup 來實現。那麼什麼是 Cgroup?

Cgroups 是Linux內核提供的一種可以限制單個進程或者多個進程所使用資源的機制,全稱是 control groups,可以對 CPU、內存等資源實現精細化的控制。典型的子系統介紹如下:

cpu 子系統,主要限制進程的 CPU 使用率。

cpuacct 子系統,可以統計 Cgroups 中的進程的CPU使用報告。

cpuset 子系統,可以為 Cgroups 中的進程分配單獨的CPU節點或者內存節點。

memory 子系統,可以限制進程的 memory 使用量。

blkio 子系統,可以限制進程的塊設備 IO。

devices 子系統,可以控制進程能夠訪問某些設備。

net_cls 子系統,可以標記 Cgroups 中進程的網路數據包,然後可以使用 tc 模塊(traffic control)對數據包進行控制。

freezer 子系統,可以掛起或者恢復 Cgroups 中的進程。

ns 子系統,可以使不同 Cgroups 下面的進程使用不同的 Namespace。

在 Linux 系統中,我們可以使用 mount 查看:

會看到輸出一系列的系統目錄和文件。我們可以看到在 /sys/fs/cgroup 中有我們上述的這些資源的名稱所對應的子目錄,也被稱為子系統。

例如下面就是 CPU 的相關配置文件:

例如 cpu.cfs_period_us cpu.cfs_quota_us 這樣的文件,它們就是用來限制 CPU 在一定時間內只能分配總量是多少的 CPU 時間。

舉個例子,我們執行:

這是一個死循環,它能夠把系統的 CPU 資源消耗到 100%,它的進程 ID 我們記住是 10108。

我們可以使用 top 命令來查看 CPU 的使用率已經高達 98.7:

我們接下來在 CPU 的 Cgroup 中創建一個 demo 的文件:

查看cfs_quota_us 和 cfs_period_us 的默認值,cfs_period_us 默認是 100ms(100000us):

我們修改 cfs_quota_us 的值為 10000,設置為 10ms:

然後我們將進程 ID 寫入 task:

此時我們再次執行 top 就可以發現 CPU 利用率瞬間降到了 30%以下,其他限制進程的資源使用的方法也類似。

這就是 Cgroup 技術的魅力所在,但是 Cgroup 並非萬能,例如 /proc 目錄下存儲記錄當前內核運行狀態的一些文件,例如 top 等命令的的信息就來源於這裡,如果此時你在容器中執行 top,會發現它顯示的是宿主機的 CPU 個數以及內存大小等信息,這又不符合我們的預期,因為 /proc 文件系統不了解 Cgroup 限制的存在。

此時我們可以利用 LXCFS 來實現,LXCFS,FUSE filesystem for LXC是一個常駐服務,它啟動以後會在指定目錄中自行維護與上面列出的 /proc 目錄中的文件同名的文件,容器從 lxcfs 維護的 /proc 文件中讀取數據時,得到的是容器的狀態數據,而不是整個宿主機的狀態。

rootfs

現在我們知道了,容器最核心的原理實際上就是為用戶創建進程,然後啟動 Linux 的 Namespace 和配置 Cgroup 參數為用戶創建了一個隔離環境。

但是僅僅這樣還是不夠的,我們進入容器其實會發現我們是在一個完全與宿主機不同的目錄結構當作,這其實是容器通過 chroot 切換了進程的根目錄來實現的,容器在啟動時候重新掛載了它的整個根目錄,並且依賴於 Namespace 的 mount,這個掛載點對於宿主機來說是不可見的,因此我們在容器中的任何操作宿主機都無感知,在 rootfs 中包含了操作系統所需要的文件、配置和目錄,但是並不包含內核。

事實上,容器無法實現啟動自己的獨立內核,它只能使用宿主機的內核。同時由於 rootfs 裡面打包的至少應用和相關依賴。保證了容器的非常重要的一個特性,即一致性。

鏡像

容器的鏡像體積一般都很小,但是很多稍微使用過容器的肯定會發現往容器裡面寫入一些文件,有時候可能這些文件很小,但是它會導致容器的體積變得非常大,這是為什麼呢?

上面我們介紹了 rootfs,我們模擬了一個獨立的系統根目錄環境,那我們每次要更新應用軟體,難道都需要按照上面的操作重新做一遍系統鏡像嗎?這個時候我們希望在製作 rootfs 的時候,能夠以增量的方法去實現,即每次修改我只修改我需要修改的地方,而其他部分則保持和原來一樣,而不是每次都全部fork 一次之前的操作。

事實上,Docker 公司在實現 Docker 時候就做類似的技術創建,他們引入了層的概念,即用戶在製作鏡像時的每一步操作都會生成一個層,也就是增量的 rootfs。通過 Union file System 即 UnionFS 可以將多個不同位置的目錄聯合掛載到同一個目錄中,Docker 鏡像的層分為只讀層、可讀寫層和 init 層三部分組成。

只讀層

這一層包含了整個底層操作系統所必須的一些目錄和依賴,例如:

它們的掛載方式都是只讀的(ro wh,即 readonly whiteout 的方式掛載。

可讀寫層

在這一層,它的掛載方式是 rw,在沒寫入文件之前,這個目錄是空的。而一旦有了寫操作,修改的內容就會以增量的方式出現在這層中。

如果要刪除只讀層的一個文件,AUFS 會在可讀寫層創建一個 whiteout 文件,把只讀層的文件隱藏起來。

init 層

這一層用來存放一些例如 /etc/hosts、/etc/resolv.conf等信息,因為這部分內容往往是需要用戶在啟動容器時候寫入一些指定的值,比如 hostname。所以就需要在可讀寫層對他們進行修改,而這些修改一般只對當前容器有效。用戶在 commit 時候只會提交可讀寫層,並不包含 init 層的內容。

最後這些層被合併為一個目錄下,組成了一個完整的操作系統供容器使用。

好了,以上就是給大家總結的容器技術的幾個核心知識點。

【END】

熱 文推 薦

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 CSDN 的精彩文章:

華為發布開發者召集令,等你來戰
程序員上海租房指南

TAG:CSDN |