由淺入深 docker 系列: (5) 資源隔離

lixiang9194發表於2019-05-27

上篇文章說了容器只是宿主機中的一個使用者程式,和虛擬機器完全不同,那麼,為什麼在容器內看不到宿主機程式,容器又是如何實現程式、檔案、網路等資源的隔離呢?

這就牽涉到今天要介紹的linux核心的Namespace和Cgroups特性了。

1.Namespace

Linux 核心從版本 2.4.19 開始陸續引入了 Namespace 的概念。其目的是將某個特定的全域性系統資源通過抽象方法使得namespace 中的程式看起來擁有它們自己的隔離的全域性系統資源例項。Linux 核心中實現了六種 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目錄下掛載好了相應檔案,每個文夾件代表了上面所講的某種資源型別。
mount -t cgroup

我們可以檢視sys/fs/cgroup/cpu資料夾下的檔案,它代表對cpu資源的控制,其中tasks檔案中是我們系統的程式pid,表示對這些程式進行資源控制,其它檔案如cpu.cfs_quota_us表示cpu的利用率,預設值為-1,表示不做限制。
Cgroup/cpu

如何使用Cgroups呢

很簡單,我們可以直接在相應資源控制組目錄下建立資料夾,系統會自動建立需要的檔案,例如,在上述cpu目錄下建立hello目錄,然後看到相應檔案已自動建立。
cgroup/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並不是全新的技術,它是對很多已有技術的封裝、抽象,只要深入的學習下去,你就會看到很多熟悉的東西,你對容器的理解也會越來越深刻,加油!

參考資料:

Docker背後的核心知識——Namespace資源隔離 - InfoQ

理解Docker(4):Docker 容器使用 cgroups 限制資源使用

相關文章