上篇文章說了容器只是宿主機中的一個使用者程式,和虛擬機器完全不同,那麼,為什麼在容器內看不到宿主機程式,容器又是如何實現程式、檔案、網路等資源的隔離呢?
這就牽涉到今天要介紹的linux核心的Namespace和Cgroups特性了。
1.Namespace
Linux 核心從版本 2.4.19 開始陸續引入了 Namespace 的概念。其目的是將某個特定的全域性系統資源通過抽象方法使得namespace 中的程式看起來擁有它們自己的隔離的全域性系統資源例項。Linux 核心中實現了六種 Namespace,按照引入的先後順序,列表如下:
例如,容器程式啟動時,只要啟用了Mount Namespace,並將自己打包的檔案系統掛載好,就可以實現每個容器僅看到自己的檔案,實現檔案資源的隔離。總之,Docker 守護程式建立容器例項時都啟用了相應的Namespace,使得容器中的程式都處於一種隔離的執行環境之中。
那麼如何啟用相應Namespace呢?
通過系統呼叫clone()來建立一個具有獨立Namespace的程式是最常見的做法,它可以通過flags引數傳入相應標誌位來控制程式的各種狀態,如以下示意程式碼:
pid = clone(fun,stack,flags,clone_arg);
(flags:CLONE_NEWPID | CLONE_NEWNS |
CLONE_NEWUSER | CLONE_NEWNUT |
CLONE_NEWIPC | CLONE_NEWUTS |
...)
docker run中namespace相關引數
- --ipc string IPC namespace to use
- --pid string PID namespace to use
- --userns string User namespace to use
- --uts string UTS namespace to use
你可以在容器啟動的時候,指定這些引數,從而強制容器執行在特定namespace之中。例如,你可以指定 --pid host,從而讓容器程式使用宿主機程式空間,此時容器可以看到host上所有的程式(想象這樣一個場景,你把常用的效能診斷工具都打包到一個映象中,然後必要的時候在伺服器上使用此映象進行問題分析,此時加上該引數會很方便)。
2.Cgroups
通過Namespace,容器實現了資源的隔離,從而每個容器看起來都像是擁有自己獨立的執行環境。注意,只是看起來。因為容器使用cpu、記憶體等並不受限制,假如某個容器佔用這些資源過高,就可能會造成其它容器執行遲緩甚至異常,這就需要Cgroups了。
cgroups 的全稱是control groups,是Linux核心提供的一種可以限制單個程式或者多個程式所使用資源的機制,可以對 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。
而Cgroups的實現也很有意思,它並不是一組系統呼叫,linux將其實現為了檔案系統,這很符合Unix一切皆檔案的哲學,因此我們可以直接檢視。
例如,我在ubuntu18.04系統中,直接執行mount -t cgroup即可看到,系統已經自動在sys/fs/cgroup目錄下掛載好了相應檔案,每個文夾件代表了上面所講的某種資源型別。
我們可以檢視sys/fs/cgroup/cpu資料夾下的檔案,它代表對cpu資源的控制,其中tasks檔案中是我們系統的程式pid,表示對這些程式進行資源控制,其它檔案如cpu.cfs_quota_us表示cpu的利用率,預設值為-1,表示不做限制。
如何使用Cgroups呢
很簡單,我們可以直接在相應資源控制組目錄下建立資料夾,系統會自動建立需要的檔案,例如,在上述cpu目錄下建立hello目錄,然後看到相應檔案已自動建立。
然後我們寫一個死迴圈,程式碼如下,然後編譯執行
//deadLoop.c
int main(void)
{
int i = 0;
for(;;) i++;
return 0;
}
//編譯 gcc deadLoop.c -o deadLoop
//執行 ./deadLoop
執行top命令,很容易發現,cpu使用率達到了100%左右,怎麼辦呢?
進入剛才建立的hello目錄,將cpu.cfs_quota_us的值改為20000(此參數列示1秒週期內程式使用cpu的最大微秒數,因此20000表示20%),然後將deadLoop程式的Pid寫入tasks檔案中去,再次執行top命令,你將看到cpu使用率只有20%了!
命令如下:
top
cd /sys/fs/cgroup/cpu/hello
echo 20000 > cpu.cfs_quota_us
ps aux | grep deadLoop
echo pid > tasks
top
很直觀吧,其它資源的控制也與此類似。
docker對Cgroups的使用
預設情況下,docker 啟動一個容器後,就會在 /sys/fs/cgroup 目錄下的各個資源目錄下生成以容器 ID 為名字的目錄,在容器被 stopped 後,該目錄被刪除。那麼,對容器資源進行控制的方式,就同上邊的例子一樣,顯而易見了。
至於docker run提供的Cgroups相關引數,就請你自己查閱文件吧。
3.其它
本篇簡單介紹了下Linux核心的Namespace和Cgroups功能,從而理解容器中資源的隔離和控制技術的實現原理,主要是幫助你建立基本的概念,而關於這些功能的使用細節以及核心中的實現原理,還很複雜,想了解的話還需要你投入大量精力。
另外,我們也可以看到,docker並不是全新的技術,它是對很多已有技術的封裝、抽象,只要深入的學習下去,你就會看到很多熟悉的東西,你對容器的理解也會越來越深刻,加油!
參考資料: