docker容器技術原理

littlexiaoshuishui 發表於 2021-03-25

程式隔離

容器技術的核心功能,就是通過約束和修改程式的動態表現,從而為其創造出一個“邊界”。對於 Docker 等大多數 Linux 容器來說,Cgroups 技術是用來製造約束的主要手段,而 Namespace 技術則是用來修改程式檢視的主要方法。

Linux 裡面的 Namespace 機制

它其實只是 Linux 建立新程式的一個可選引數。我們知道,在 Linux 系統中建立執行緒的系統呼叫是 clone(),比如:

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

而當我們用 clone() 系統呼叫建立一個新程式時,就可以在引數中指定 CLONE_NEWPID 引數,比如:

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

新建立的這個程式將會“看到”一個全新的程式空間,在這個程式空間裡,它的 PID 是 1。之所以說“看到”,是因為這只是一個“障眼法”,在宿主機真實的程式空間裡,這個程式的 PID 還是真實的數值,比如 100。

當然,我們還可以多次執行上面的 clone() 呼叫,這樣就會建立多個 PID Namespace,而每個 Namespace 裡的應用程式,都會認為自己是當前容器裡的第 1 號程式,它們既看不到宿主機裡真正的程式空間,也看不到其他 PID Namespace 裡的具體情況。

而除了我們剛剛用到的 PID Namespace,Linux 作業系統還提供了 Mount、UTS、IPC、Network 和 User 這些 Namespace,用來對各種不同的程式上下文進行“障眼法”操作。比如,Mount Namespace,用於讓被隔離程式只看到當前 Namespace 裡的掛載點資訊;Network Namespace,用於讓被隔離程式看到當前 Namespace 裡的網路裝置和配置。這,就是 Linux 容器最基本的實現原理了。

所以說,容器,其實是一種特殊的程式而已。只是它的建立時加了各種各樣的namespace引數,只能看到各自 Mount Namespace 裡掛載的目錄和檔案,只能訪問到各自 Network Namespace 裡的網路裝置,就彷彿執行在一個個“容器”裡面,與世隔絕。

隔離和限制

隔離的不夠充分

首先,既然容器只是執行在宿主機上的一種特殊的程式,那麼多個容器之間使用的就還是同一個宿主機的作業系統核心。

其次,在 Linux 核心中,有很多資源和物件是不能被 Namespace 化的,最典型的例子就是:時間。

限制

Linux Cgroups 就是 Linux 核心中用來為程式設定資源限制的一個重要功能。Linux Cgroups 的全稱是 Linux Control Group。它最主要的作用,就是限制一個程式組能夠使用的資源上限,包括 CPU、記憶體、磁碟、網路頻寬等等。這個是放在某個容器佔用整個宿主機資源,必須對這些容器程式進行限制。

由於一個容器的本質就是一個程式,使用者的應用程式實際上就是容器裡 PID=1 的程式,也是其他後續建立的所有程式的父程式。這就意味著,在一個容器中,你沒辦法同時執行兩個不同的應用,除非你能事先找到一個公共的 PID=1 的程式來充當兩個不同應用的父程式

容器映象

我們使用docker,隨便進入到一個容器中

#docker exec -it 容器id /

#ls //檢視當前的檔案
bin etc lib mnt root sbin ssl tmp var
dev home media proc run srv sys usr

可以看到很多和linux專案類似的檔案目錄。而這個掛載在容器根目錄上、用來為容器程式提供隔離後執行環境的檔案系統,就是所謂的“容器映象”。它還有一個更為專業的名字,叫作:rootfs(根檔案系統)

這個就是docker在啟動這個容器程式時,為了能夠讓容器的這個根目錄看起來更“真實”,我們一般會在這個容器的根目錄下掛載一個完整作業系統的檔案系統,比如 Ubuntu16.04 的 ISO。這樣,在容器啟動之後,我們在容器裡通過執行 “ls /“ 檢視根目錄下的內容,就是 Ubuntu 16.04 的所有目錄和檔案。

而你進入容器之後執行的 /bin/bash,就是 /bin 目錄下的可執行檔案,與宿主機的 /bin/bash 完全不同。需要明確的是,rootfs 只是一個作業系統所包含的檔案、配置和目錄,並不包括作業系統核心。在 Linux 作業系統中,這兩部分是分開存放的,作業系統只有在開機啟動時才會載入指定版本的核心映象。

正是由於 rootfs 的存在,容器才有了一個被反覆宣傳至今的重要特性:一致性。

由於 rootfs 裡打包的不只是應用,而是整個作業系統的檔案和目錄,也就意味著,應用以及它執行所需要的所有依賴,都被封裝在了一起。無論在本地、雲端,還是在一臺任何地方的機器上,使用者只需要解壓打包好的容器映象,那麼這個應用執行所需要的完整的執行環境就被重現出來了。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
用過哪些工具?為啥用這個工具(速度快,支援高併發...)?底層如何實現的?