1. 程式人生 > 其它 >一篇搞懂容器技術的基石: cgroup

一篇搞懂容器技術的基石: cgroup

對於容器技術而言,它實現資源層面上的限制和隔離,依賴於 Linux 核心所提供的 cgroup 和 namespace 技術。

我們先對這兩項技術的作用做個概括:

  • cgroup 的主要作用:管理資源的分配、限制
  • namespace 的主要作用:封裝抽象限制隔離,使名稱空間內的程序看起來擁有他們自己的全域性資源

本篇,我們重點來聊 cgroup

什麼是 cgroup

cgroup 是 Linux 核心的一個功能,用來限制、控制與分離一個程序組的資源(如CPU、記憶體、磁碟輸入輸出等)。它是由 Google 的兩位工程師進行開發的,自 2008 年 1 月正式釋出的 Linux 核心 v2.6.24 開始提供此能力。

cgroup 到目前為止,有兩個大版本, cgroup v1 和 v2 。以下內容以 cgroup v2 版本為主,涉及兩個版本差別的地方會在下文詳細介紹。

cgroup 主要限制的資源是:

  • CPU
  • 記憶體
  • 網路
  • 磁碟 I/O

當我們將可用系統資源按特定百分比分配給 cgroup 時,剩餘的資源可供系統上的其他 cgroup 或其他程序使用。

cgroup 的組成

cgroup 代表“控制組”,並且不會使用大寫。cgroup 是一種分層組織程序的機制, 沿層次結構以受控的方式分配系統資源。我們通常使用單數形式用於指定整個特徵,也用作限定符如 “cgroup controller” 。

cgroup 主要有兩個組成部分:

  • core - 負責分層組織過程;
  • controller - 通常負責沿層次結構分配特定型別的系統資源。每個 cgroup 都有一個cgroup.controllers檔案,其中列出了所有可供 cgroup 啟用的控制器。當在cgroup.subtree_control中指定多個控制器時,要麼全部成功,要麼全部失敗。在同一個控制器上指定多項操作,那麼只有最後一個生效。每個 cgroup 的控制器銷燬是非同步的,在引用時同樣也有著延遲引用的問題;

所有 cgroup 核心介面檔案都以cgroup為字首。每個控制器的介面檔案都以控制器名稱和一個點為字首。控制器的名稱由小寫字母和“”組成,但永遠不會以“

”開頭。

cgroup 的歸屬和遷移

系統中的每個程序都屬於一個 cgroup,一個程序的所有執行緒都屬於同一個 cgroup。一個程序可以從一個 cgroup 遷移到另一個 cgroup 。程序的遷移不會影響現有的後代程序所屬的 cgroup。

什麼是 cgroups

當明確提到多個單獨的控制組時,才使用複數形式 “cgroups” 。

cgroups 形成了樹狀結構。(一個給定的 cgroup 可能有多個子 cgroup 形成一棵樹結構體)每個非根 cgroup 都有一個cgroup.events檔案,其中包含populated欄位指示 cgroup 的子層次結構是否具有實時程序。所有非根的cgroup.subtree_control檔案,只能包含在父級中啟用的控制器。

cgroups 示例

如圖所示,cgroup1 中限制了使用 cpu 及 記憶體資源,它將控制子節點的 CPU 週期和記憶體分配(即,限制 cgroup2、cgroup3、cgroup4 中的cpu及記憶體資源分配)。cgroup2 中啟用了記憶體限制,但是沒有啟用cpu的資源限制,這就導致了 cgroup3 和 cgroup4 的記憶體資源受 cgroup2中的 mem 設定內容的限制;cgroup3 和 cgroup4 會自由競爭在 cgroup1 的 cpu 資源限制範圍內的 cpu 資源。

由此,也可以明顯的看出 cgroup 資源是自上而下分佈約束的。只有當資源已經從上游 cgroup 節點分發給下游時,下游的 cgroup 才能進一步分發約束資源。所有非根的cgroup.subtree_control檔案只能包含在父節點的cgroup.subtree_control檔案中啟用的控制器內容。

那麼,子節點 cgroup 與父節點 cgroup 是否會存在內部程序競爭的情況呢?

當然不會。cgroup v2 中,設定了非根 cgroup 只能在沒有任何程序時才能將域資源分發給子節點的 cgroup。簡而言之,只有不包含任何程序的 cgroup 才能在其cgroup.subtree_control檔案中啟用域控制器,這就保證了,程序總在葉子節點上。

cgroup 和容器的聯絡

這裡我們以 Docker 為例。 建立一個容器,並對其可使用的 CPU 和記憶體進行限制

➜  ~ docker run --rm -d  --cpus=2 --memory=2g --name=2c2g redis:alpine 
e420a97835d9692df5b90b47e7951bc3fad48269eb2c8b1fa782527e0ae91c8e
➜  ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/cpu.max
200000 100000
➜  ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/memory.max
2147483648
➜  ~ 
➜  ~ docker run --rm -d  --cpus=0.5 --memory=0.5g --name=0.5c0.5g redis:alpine
8b82790fe0da9d00ab07aac7d6e4ef2f5871d5f3d7d06a5cdb56daaf9f5bc48e
➜  ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/cpu.max       
50000 100000
➜  ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/memory.max
536870912