一文帶你搞懂 Docker 容器的核心基石 Cgroups

Cgroups 是 Linux 系統內核提供的一種機制,這種機制可以根據需求將一些列系統任務機器子任務整合或分離到按資源劃分登記的不同組內,從而爲系統資源管理提供一個的框架。簡單地說,cgroups 可以限制、記錄任務組所使用的物理組員(比如 CPU、Memory、IO 等),爲容器實現虛擬化提供了基本保證,是構建 Docker 等一些列虛擬化管理工具的基石。今天我們就來詳細介紹一下 cgroups 相關的內容。

  1. 爲什麼要了解 Cgroups

從 2013 年開源的 Docker 推出、2014 年開源的 Kubernetes 出現,到現在的雲原生技術與生態的全面普及與火熱化,容器技術已經逐步成爲主流的基礎雲原生技術之一。使用容器技術,可以很好地實現資源層面上的限制和隔離,這都依賴於 Linux 系統內核所提供的 Cgroups 和 Namespace 技術。

Cgroups 主要用來管理資源的分配、限制;Namespace 主要用來封裝抽象、限制、隔離資源,使命名空間內的進程擁有它們自己的全局資源。

Linux 內核提供的 Cgroups 和 Namespace 技術,爲容器實現虛擬化提供了基本保證,是構建 Docker 等一些列虛擬化管理工具的基石。下面我們就來詳細介紹一下 Cgroups 相關的內容。

  1. Cgroups 簡介

Cgroups 是 control groups 的縮寫,是 Linux 內核提供的一種可以限制、記錄、隔離進程組(process groups)所使用的物理資源(如 CPU、Memory、IO 等等)的機制。

通過使用 Cgroups,系統管理員在分配、排序、拒絕、管理和監控系統資源等方面,可以進行精細化控制。硬件資源可以在應用程序和用戶間智能分配,從而增加整體效率。最初由 google 的工程師提出,後來被整合進 Linux 內核。也是目前輕量級虛擬化技術 XC(Linux Container)的基礎之一。

Cgroups 和 Namespace 類似,也是將進程進行分組,但它的目的和 Namespace 不一 樣,Namespace 是爲了隔離進程組之間的資源,而 Cgroups 是爲了對一組進程進行統一的資源監控和限制。

Cgroups 分 v1 和 v2 兩個版本,v1 實現較早,功能比較多,但是由於它裏面的功能都是零零散散的實現的,所以規劃的不是很好,導致了一些使用和維護上的不便,v2 的出現 就是爲了解決 v1 中這方面的問題,在最新的 4.5 內核中,Cgroups v2 聲稱已經可以用於生產環境了,但它所支持的功能還很有限,隨着 v2 一起引入內核的還有 Cgroups、Namespace,v1 和 v2 可以混合使用,但是這樣會更復雜,所以一般沒人會這樣用。

  1. 什麼是 Cgroups?

Cgroups 是 Linux 下的一種將進程按組進行管理的機制,在用戶層看來,Cgroups 技術就是把系統中的所有進程組織成一顆一顆獨立的樹,每棵樹都包含系統的所有進程,樹的每個節點是一個進程組,而每顆樹又和一個或者多個 subsystem 關聯,樹的作用是將進程分組,而 subsystem 的作用就是對這些組進行操作,Cgroups 的主體架構提如下:

Cgroups 主要包括下面兩部分:

目前 Linux 支持 12 種 subsystem,如果不考慮不與任何 subsystem 關聯的情況(systemd 就屬於這種情況),Linux 裏面最多可以建 12 顆 cgroup 樹,每棵樹關聯一個 subsystem,當然也可以只建一棵樹,然後讓這 棵樹關聯所有的 subsystem。當一顆 cgroup 樹不和任何 subsystem 關聯的時候,意味着這棵樹只是將進程進行分組,至於要在分組的基礎上做些什麼,將由應用程序自己決定, systemd 就是一個這樣的例子。

  1. 爲什麼需要 Cgroups?

在 Linux 裏,一直以來就有對進程進行分組的概念和需求,比如 session group, progress group 等,後來隨着人們對這方面的需求越來越多,比如需要追蹤一組進程的內存和 IO 使用情況等,於是出現了 cgroup,用來統一將進程進行分組,並在分組的基礎上對進程進行監控和資源控制管理等。

舉個例子,Linux 系統中安裝了殺毒軟件 ESET 或者 ClamAV,殺毒時佔用系統資源過高,影響系統承載業務運行,怎麼辦?單個虛擬機進程或者 docker 進程使用過高的資源,怎麼辦?單個 Java 進行佔用系統過多的內存的資源,怎麼辦?

cgroup 就是能夠控制並解決上述問題的工具,cgroup 在 linux 內核實現、用於控制 linux 系統資源。

  1. Cgroups 是如何實現的?

在 CentOS 7 系統中(包括 Red Hat Enterprise Linux 7),通過將 cgroup 層級系統與 systemd 單位樹捆綁,可以把資源管理設置從進程級別移至應用程序級別。默認情況下 systemd 會自動創建 slice、scope 和 service 單位的層級(具體的意思稍後再解釋),來爲 cgroup 樹提供統一結構。

可以通過 systemctl 命令創建自定義 slice 進一步修改此結構。如果我們將系統的資源看成一塊餡餅,那麼所有資源默認會被劃分爲 3 個 cgroup:System, User 和 Machine。每一個 cgroup 都是一個 slice,每個 slice 都可以有自己的子 slice,如下圖所示:

下面我們以 CPU 資源爲例,來解釋一下上圖中出現的一些關鍵詞。如上圖所示,系統默認創建了 3 個頂級 slice(System, User 和 Machine),每個 slice 都會獲得相同的 CPU 使用時間(僅在 CPU 繁忙時生效),如果 user.slice 想獲得 100% 的 CPU 使用時間,而此時 CPU 比較空閒,那麼 user.slice 就能夠如願以償。這三種頂級 slice 的含義如下:

假設該系統上運行了 4 個 service,登錄了兩個用戶,還運行了一個虛擬機。同時假設 每個進程都要求使用盡可能多的 CPU 資源(每個進程都很繁忙),則:

  1. Cgroups 的作用

Cgroups 最初的目標是爲資源管理提供的一個統一的框架,既整合現有的 cpuset 等子系統,也爲未來開發新的子系統提供接口。現在的 cgroups 適用於多種應用場景,從單個進程的資源控制,到實現操作系統層次的虛擬化(OS Level Virtualization),框架圖如下:

Cgroups 提供了以下功能:

  1. Cgroups 相關概念及相互關係

7.1 相關概念

1)任務(task):在 cgroups 中,任務就是系統的一個進程。

2)控制族羣(control group):控制族羣就是一組按照某種標準劃分的進程。Cgroups 中的資源控制都是以控制族羣爲單位實現。一個進程可以加入到某個控制族羣,也從一個進程組遷移到另 一個控制族羣。一個進程組的進程可以使用 cgroups 以控制族羣爲單位分配的資源,同時受到 cgroups 以控制族羣爲單位設定的限制。

3)層級(hierarchy):控制族羣可以組織成 hierarchical 的形式,既一顆控制族羣樹。控制族 羣樹上的子節點控制族羣是父節點控制族羣的孩子,繼承父控制族羣的特定的屬性。

4)子系統(subsystem):一個子系統就是一個資源控制器,比如 cpu 子系統就是控制 cpu 時間分配的一個控制器。子系統必須附加(attach)到一個層級上才能起作用,一個子系統附加到某個 層級以後,這個層級上的所有控制族羣都受到這個子系統的控制。

7.2 相互關係

  1. Cgroups 子系統介紹

可以看到,在 /sys/fs/cgroup 下面有很多 cpu、memory 這樣的子目錄,也就稱爲子系統 subsystem:

它是一組資源控制模塊,一般包含如下幾項:

8.1 如何查看當前系統支持哪些 subsystem?

可以通過查看 /proc/cgroups(since Linux 2.6.24) 知道當前系統支持哪些 subsystem,下面 是一個例子:

#subsys_name hierarchy num_cgroups enabled 
cpuset 11 1 1 
cpu 3 64 1 
cpuacct 3 64 1 
blkio 8 64 1 
memory 9 104 1 
devices 5 64 1 
freezer 10 4 1 
net_cls 6 1 1 
perf_event 7 1 1 
net_prio 6 1 1 
hugetlb 4 1 1 
pids 2 68 1

每一列的說明:

8.2 Cgroups 下的 CPU 子系統

cpu 子系統用於控制 cgroup 中所有進程可以使用的 cpu 時間片。cpu subsystem 主要涉及 5 接口:cpu.cfs_period_uscpu.cfs_quota_uscpu.sharescpu.rt_period_uscpu.rt_runtime_us.cpu

8.3 在 CentOS 中安裝 Cgroups

#若系統未安裝則進行安裝,若已安裝則進行更新。 
yum install libcgroup 
 
#查看運行狀態,並啓動服務 
[root@localhost ~] service cgconfig status 
Stopped 
 
[root@localhost ~] service cgconfig start 
Starting cgconfig service: [ OK ] 
service cgconfig status 9 Running 1011 
 
#查看是否安裝cgroup
[root@localhost ~] grep cgroup /proc/filesystems

8.4 查看 service 服務在哪個 cgroup 組

systemctl status [pid] | grep CGroup 23 
cat /proc/[pid]/cgroup 
cd /sys/fs/ && find * ‐name "*.procs" ‐exec grep [pid] {} /dev/null \; 2> /dev/null 
 
#查看進程cgroup的最快方法是使用以下bash腳本按進程名: 
#!/bin/bash 
THISPID=`ps ‐eo pid,comm | grep $1 | awk '{print $1}'` 
cat /proc/$THISPID/cgroup
  1. 如何使用 Cgroups

9.1 通過 systemctl 設置 cgroup

在使用命令 systemctl set-property 時,可以使用 tab 補全:

$ systemctl set‐property user‐1000.slice 
AccuracySec= CPUAccounting= Environment= LimitCPU= LimitNICE= LimitSIGPEN DING= SendSIGKILL= 
BlockIOAccounting= CPUQuota= Group= LimitDATA= LimitNOFILE= LimitSTACK= U ser= 
BlockIODeviceWeight= CPUShares= KillMode= LimitFSIZE= LimitNPROC= MemoryA ccounting= WakeSystem= 
BlockIOReadBandwidth= DefaultDependencies= KillSignal= LimitLOCKS= LimitR SS= MemoryLimit=
BlockIOWeight= DeviceAllow= LimitAS= LimitMEMLOCK= LimitRTPRIO= Nice= 
BlockIOWriteBandwidth= DevicePolicy= LimitCORE= LimitMSGQUEUE= LimitRTTIM E= SendSIGHUP=

這裏有很多屬性可以設置,但並不是所有的屬性都是用來設置 cgroup 的,我們只需要關注 Block, CPU 和 Memory。

如果你想通過配置文件來設置 cgroup,service 可以直接在 /etc/systemd/system/xxx.service.d 目錄下面創建相應的配置文件,slice 可以直接在 /run/systemd/system/xxx.slice.d 目錄下面創建相應的配置文件。事實上通過 systemctl 命令行工具設置 cgroup 也會寫到該目錄下的配置文件中:

$ cat /run/systemd/system/user‐1000.slice.d/50‐CPUQuota.conf 
[Slice] 
CPUQuota=20%

9.2 設置 CPU 資源的使用上限

如果想嚴格控制 CPU 資源,設置 CPU 資源的使用上限,即不管 CPU 是否繁忙,對 CPU 資源的使用都不能超過這個上限。可以通過以下兩個參數來設置:

systemctl 可以通過 CPUQuota 參數來設置 CPU 資源的使用上限。例如,如果你想將用戶 tom 的 CPU 資源使用上限設置爲 20%,可以執行以下命令:

$ systemctl set‐property user‐1000.slice CPUQuota=20%

9.3 通過配置文件設置 cgroup(/etc/cgconfig.conf)

cgroup 配置文件所在位置 /etc/cgconfig.conf,其默認配置文件內容

mount {
cpuset = / cgroup / cpuset ;
cpu = / cgroup / cpu ;
cpuacct = / cgroup / cpuacct ;
memory = / cgroup / memory ;
devices = / cgroup / devices ;
freezer = / cgroup / freezer ;
net_cls = / cgroup / net_cls ;
blkio = / cgroup / blkio ;
}

相當於執行命令:

mkdir /cgroup/cpuset 
mount ‐t cgroup ‐o cpuset red /cgroup/cpuset
…… 
mkdir /cgroup/blkio 
 
[root@localhost ~] vi /etc/cgrules.conf 
[root@localhost ~] echo 524288000 > /cgroup/memory/foo/memory.limit_in_b ytes

使用 cgroup 臨時對進程進行調整,直接通過命令即可,如果要持久化對進程進行控制,即重啓後依然有效,需要寫進配置文件 /etc/cgconfig.conf/etc/cgrules.conf

  1. 查看 Cgroup

10.1 通過 systemd 查看 cgroup

1)systemd-cgls 命令:通過 systemd-cgls 命令來查看,它會返回系統的整體 cgroup 層級,cgroup 樹的最高層 由 slice 構成,如下所示:

$ systemd‐cgls ‐‐no‐page 
 
├─1 /usr/lib/systemd/systemd ‐‐switched‐root ‐‐system ‐‐deserialize 22 
├─user.slice 
│ ├─user‐1000.slice 
│ │ └─session‐11.scope 
│ │ ├─9507 sshd: tom [priv] 
│ │ ├─9509 sshd: tom@pts/3 
│ │ └─9510 ‐bash 
│ └─user‐0.slice 
│ └─session‐1.scope 
│ ├─ 6239 sshd: root@pts/0 
│ ├─ 6241 ‐zsh 
│ └─11537 systemd‐cgls ‐‐no‐page 
└─system.slice 15 ├─rsyslog.service 
│ └─5831 /usr/sbin/rsyslogd ‐n 
├─sshd.service 18 │ └─5828 /usr/sbin/sshd ‐D 
├─tuned.service 
│ └─5827 /usr/bin/python2 ‐Es /usr/sbin/tuned ‐l ‐P 21 ├─crond.service 
│ └─5546 /usr/sbin/crond ‐n

可以看到系統 cgroup 層級的最高層由 user.slice 和 system.slice 組成。因爲系統中沒有 運行虛擬機和容器,所以沒有 machine.slice,所以當 CPU 繁忙時,user.slice 和 system.slice 會各獲得 50% 的 CPU 使用時間。

user.slice 下面有兩個子 slice:user-1000.slice 和 user-0.slice,每個子 slice 都用 User ID (UID) 來命名,因此我們很容易識別出哪個 slice 屬於哪個用戶。例如從上面的輸出信息中可以看出 user-1000.slice 屬於用戶 tom,user-0.slice 屬於用戶 root。

2)systemd-cgtop 命令:systemd-cgls 命令提供的只是 cgroup 層級的靜態信息快照,要想查看 cgroup 層級的動 態信息,可以通過 systemd-cgtop 命令查看:

$ systemd‐cgtop 
 
Path Tasks %CPU Memory Input/s Output/s 
/ 161 1.2 161.0M ‐ ‐ 5 /system.slice ‐ 0.1 ‐ ‐ ‐ 
/system.slice/vmtoolsd.service 1 0.1 ‐ ‐ ‐ 
/system.slice/tuned.service 1 0.0 ‐ ‐ ‐ 
/system.slice/rsyslog.service 1 0.0 ‐ ‐ ‐ 
/system.slice/auditd.service 1 ‐ ‐ ‐ ‐ 
/system.slice/chronyd.service 1 ‐ ‐ ‐ ‐ 
/system.slice/crond.service 1 ‐ ‐ ‐ ‐ 
/system.slice/dbus.service 1 ‐ ‐ ‐ ‐ 
/system.slice/gssproxy.service 1 ‐ ‐ ‐ ‐ 
/system.slice/lvm2‐lvmetad.service 1 ‐ ‐ ‐ ‐ 
/system.slice/network.service 1 ‐ ‐ ‐ ‐ 
/system.slice/polkit.service 1 ‐ ‐ ‐ ‐ 
/system.slice/rpcbind.service 1 ‐ ‐ ‐ ‐ 
/system.slice/sshd.service 1 ‐ ‐ ‐ ‐ 
/system.slice/system‐getty.slice/getty@tty1.service 1 ‐ ‐ ‐ ‐ 
/system.slice/systemd‐journald.service 1 ‐ ‐ ‐ ‐ 
/system.slice/systemd‐logind.service 1 ‐ ‐ ‐ ‐ 
/system.slice/systemd‐udevd.service 1 ‐ ‐ ‐ ‐ 
/system.slice/vgauthd.service 1 ‐ ‐ ‐ ‐ 
/user.slice 3 ‐ ‐ ‐ ‐ 
/user.slice/user‐0.slice/session‐1.scope 3 ‐ ‐ ‐ ‐ 
/user.slice/user‐1000.slice 3 ‐ ‐ ‐ ‐ 
/user.slice/user‐1000.slice/session‐11.scope 3 ‐ ‐ ‐ ‐ 
/user.slice/user‐1001.slice/session‐8.scope

scope systemd-cgtop 提供的統計數據和控制選項與 top 命令類似,但該命令只顯示那些開啓了 資源統計功能的 service 和 slice。

如果你想開啓 sshd.service 的資源統計功能,可以進行如下操作:

$ systemctl set‐property sshd.service CPUAccounting=true MemoryAccounting=true 
 
#該命令會在 /etc/systemd/system/sshd.service.d/ 目錄下創建相應的配置文件: 
$ ll /etc/systemd/system/sshd.service.d/ 
總用量 8 
4 ‐rw‐r‐‐r‐‐ 1 root root 28 5月 31 02:24 50‐CPUAccounting.conf 
4 ‐rw‐r‐‐r‐‐ 1 root root 31 5月 31 02:24 50‐MemoryAccounting.conf 
 
$ cat /etc/systemd/system/sshd.service.d/50‐CPUAccounting.conf 
[Service] 
CPUAccounting=yes 1415 
 
$ cat /etc/systemd/system/sshd.service.d/50‐MemoryAccounting.conf 
[Service] 
MemoryAccounting=yes 1819 
 
#配置完成之後,再重啓 sshd 服務: 
$ systemctl daemon‐reload 21 $ systemctl restart sshd

這時再重新運行 systemd‐cgtop 命令,就能看到 sshd 的資源使用統計了。

10.2 通過 proc 查看 cgroup

如何查看當前進程屬於哪些 cgroup 可以通過查看 /proc/[pid]/cgroup(since Linux 2.6.24) 知道指定進程屬於哪些 cgroup,如下:

$ cat /proc/777/cgroup 
 
11:cpuset:/ 
10:freezer:/ 
9:memory:/system.slice/cron.service 
8:blkio:/system.slice/cron.service 
7:perf_event:/ 7 6:net_cls,net_prio:/ 
5:devices:/system.slice/cron.service 
4:hugetlb:/ 
3:cpu,cpuacct:/system.slice/cron.service 
2:pids:/system.slice/cron.service 
1:name=systemd:/system.slice/cron.service

每一行包含用冒號隔開的三列,他們的意思分別是:

cgroup樹的ID :和cgroup樹綁定的所有subsystem :進程在cgroup樹中的路徑

10.3 通過 /sys 查看 cgroup

查看 cgroup 下 CPU 資源的使用上限:

$ cat /sys/fs/cgroup/cpu,cpuacct/user.slice/user‐1000.slice/cpu.cfs_perio d_us 
100000 
 
$ cat /sys/fs/cgroup/cpu,cpuacct/user.slice/user‐1000.slice/cpu.cfs_quota _us 
20000

這表示用戶 tom 在一個使用週期內(100 毫秒)可以使用 20 毫秒的 CPU 時間。不管 CPU 是否空閒,該用戶使用的 CPU 資源都不會超過這個限制。

CPUQuota 的值可以超過 100%,例如:如果系統的 CPU 是多核,且 CPUQuota 的值爲 200%,那麼該 slice 就能夠使用 2 核的 CPU 時間。


作者丨 dvlinker

來源丨網址:https://blog.csdn.net/chenlycly/article/details/125956805

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uvtuaXIDoCyy7-JO4qYY8Q