docker如何利用cgroup對容器資源進行限制

同步文件:https://blog.haohtml.com/archives/29974

在容器里有兩個非常重要的概念,一個是namespace,用來進行對容器里所有進程的隔離;另一個就是cgroup,用來對容器資源進行限制。那cgroup又是如何實現(xiàn)對進行資源的限制呢,今天我們來了解一下它的實現(xiàn)原理。

什么是cgroup

cgroup 是 Control Groups 的縮寫,是 Linux 內核提供的一種可以限制、記錄、隔離 進程組 所使用的物理資源(如 cpu、memory、磁盤IO等等) 的機制,被 LXC、docker 等很多項目用于實現(xiàn)進程資源控制。cgroup 將任意進程進行分組化管理的 Linux 內核功能。
cgroup 本身是提供將進程進行分組化管理的功能和接口的基礎結構,I/O 或內存的分配控制等具體的資源管理功能是通過這個功能來實現(xiàn)的。 一定要切記,這里的限制單元為進程組,而不是進程。

子系統(tǒng)

上面這些具體的資源管理功能統(tǒng)稱為 cgroup 子系統(tǒng),所有子系統(tǒng)列表可以通過 cat /proc/cgroups 命令查看,主要有以下幾大子系統(tǒng):

# cat /proc/cgroups
#subsys_name    hierarchy   num_cgroups enabled
cpuset          4           7           1
cpu             2           89          1
cpuacct         2           89          1
blkio           3           86          1
memory          7           150         1
devices         6           84          1
freezer         5           7           1
net_cls         10          7           1
perf_event      12          7           1
net_prio        10          7           1
hugetlb         8           7           1
pids            9           94          1
rdma            11          1           1

子系統(tǒng)功能

  • cpuset:若是是多核心的CPU, 這個子系統(tǒng)會為cgroup 任務分配單獨的CPU和內存。
  • cpu:使用調度程序為cgroup任務提供CPU的訪問。
  • cpuacct:產(chǎn)生cgroup, 任務的CPU資源報告
  • blkio:設置限制每一個塊設備的輸入輸出控制。例如:磁盤,光盤以及usb 等等。
  • memory: 設置每一個cgroup 的內存限制以及產(chǎn)生內存資源報告。
  • devices:容許或拒絕cgroup任務對設備的訪問。
  • freezer:暫停和恢復cgroup任務。
  • net_cls: 標記每一個網(wǎng)絡包以供cgroup 方便使用。
  • ns:命名空間子系統(tǒng),能夠設置一個子系統(tǒng)的上限配額。
  • perf_event: 增加了對每一個group 的監(jiān)測跟蹤的能力,能夠監(jiān)測屬于某個特定的group 的全部線程以及運行在特定,監(jiān)控能力超出限制則進行終止。
  • net_prio 設置cgroup中進程產(chǎn)生的網(wǎng)絡流量的優(yōu)先級
  • hugetlb 限制使用的內存頁數(shù)量
  • pids 限制任務的數(shù)量

目前 docker 只是用了其中一部分子系統(tǒng),實現(xiàn)對資源配額和使用的控制。如可以使用 freezer 子系統(tǒng)對 進行組 進行掛起和恢復。

cgroup組件術語

  • task:在cgroup中,任務就是系統(tǒng)的一個進程
  • subsystem:一個子系統(tǒng)就是一個資源控制器,比如 cpu 子系統(tǒng)就是控制 cpu 時間分配的一個控制器。子系統(tǒng)必須附加(attach)到一個層級上才能起作用,一個子系統(tǒng)附加到某個層級以后,這個層級上的所有控制族群都受到這個子系統(tǒng)的控制。
  • control group:控制族群就是按照某種標準劃分的進程。Cgroups 中的資源控制都是以控制族群為單位實現(xiàn)。一個進程可以加入到某個控制族群,也從一個進程組遷移到另一個控制族群。一個進程組的進程可以使用 cgroups 以控制族群為單位分配的資源,同時受到 cgroups 以控制族群為單位設定的限制;
  • hierarchy:樹形結構的 CGroup 層級,每個子 CGroup 節(jié)點會繼承父 CGroup 節(jié)點的子系統(tǒng)配置,每個 Hierarchy 在初始化時會有默認的 CGroup(Root CGroup);
    控制族群可以組織成 hierarchical 的形式,既一顆控制族群樹??刂谱迦簶渖系淖庸?jié)點控制族群是父節(jié)點控制族群的孩子,繼承父控制族群的特定的屬性。比如一組task進程通過cgroup1限制了CPU使用率,然后其中一個日志進程還需要限制磁盤IO,為了避免限制磁盤IO影響到其他進程,就可以創(chuàng)建cgroup2,使其繼承cgroup1并限制磁盤IO,這樣這樣cgroup2便繼承了cgroup1中對CPU使用率的限制并且添加了磁盤IO的限制而不影響到cgroup1中的其他進程;

組件關系

遵循原則:

  • 每次在系統(tǒng)中創(chuàng)建新層級時,該系統(tǒng)中的所有任務都是那個層級的默認 cgroup(我們稱之為 root cgroup,此 cgroup 在創(chuàng)建層級時自動創(chuàng)建,后面在該層級中創(chuàng)建的 cgroup 都是此 cgroup 的后代)的初始成員;
  • 一個 subsystem 最多只能附加到一個層級 hierarchy;
  • 一個層級 hierarchy 可以附加多個子系統(tǒng) subsystem;
  • 一個任務 task 可以是多個 cgroup 的成員,但是這些 cgroup 必須在不同的層級hierarchy;
    系統(tǒng)中的進程(任務)創(chuàng)建子進程(任務)時,該子任務自動成為其父進程所在 cgroup 的成員。然后可根據(jù)需要將該子任務移動到不同的 cgroup 中,但開始時它總是繼承其父任務的 cgroup。
  • 一個進程fork出子進程時,該子進程默認自動成為父進程所在的cgroup的成員,也可以根據(jù)情況將其移動到到不同的cgroup中.

如圖所示,CPU 和 Memory 兩個子系統(tǒng)有自己獨立的層級系統(tǒng),而又通過 Task Group 取得關聯(lián)關系

關聯(lián)圖
CGroup 典型應用架構圖

CGroup 技術可以被用來在操作系統(tǒng)底層限制物理資源,起到 Container 的作用。上圖中每一個 JVM 進程對應一個 Container Cgroup 層級,通過 CGroup 提供的各類子系統(tǒng),可以對每一個 JVM 進程對應的線程級別進行物理限制,這些限制包括 CPU、內存等等許多種類的資源。

cgroup實戰(zhàn)

在 Linux 中,cgroups 給用戶暴露出來的操作接口是文件系統(tǒng),即它以文件和目錄的方式組織在操作系統(tǒng)的 /sys/fs/cgroup 路徑下。在 Ubuntu 16.04 機器里,可以用 mount 指令把它們展示出來:

$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)

它的輸出是一些文件系統(tǒng)目錄,這些目錄名就是當前系統(tǒng)所支持的子系統(tǒng),這些子系統(tǒng)都在 /sys/fs/cgroup/目錄內,如對于cpu子系統(tǒng)來說,相關的幾個配置文件為

$ ls /sys/fs/cgroup/cpu
aegis                  cgroup.procs          cpu.cfs_quota_us  cpuacct.stat       cpuacct.usage_percpu       cpuacct.usage_sys   kubepods.slice     system.slice  user.slice
assist                 cgroup.sane_behavior  cpu.shares        cpuacct.usage      cpuacct.usage_percpu_sys   cpuacct.usage_user  notify_on_release  tasks
cgroup.clone_children  cpu.cfs_period_us     cpu.stat          cpuacct.usage_all  cpuacct.usage_percpu_user  init.scope          release_agent      test

其中 cpu.cfs_quota_uscpu.cfs_period_us 是經(jīng)常使用的兩個配置項,兩者必須組合使用,表示一個進程組在 cpu.cfs_period_us 段時間內,分配給CPU的時間比例為 cpu.cfs_quota_us

另外輸出結果中包含一些子目錄,如 aegis、assist、kubepods.slice、system.slice、user.slice、test 和 init.scope。

現(xiàn)在我們看下這些子系統(tǒng)配置文件如何使用,首先我們在 /sys/fs/cgroup/cpu/ 目錄下創(chuàng)建一個目錄 mycontainer,這個目錄稱為cgroup,即"控制組"。

$ cd /sys/fs/cgroup/cpu/
$ mkdir mycontainer
$ sys/fs/cgroup/cpu# ls mycontainer/
cgroup.clone_children  cpu.cfs_period_us  cpu.shares  cpu.uclamp.max  cpuacct.stat   cpuacct.usage_all     cpuacct.usage_percpu_sys   cpuacct.usage_sys   notify_on_release
cgroup.procs           cpu.cfs_quota_us   cpu.stat    cpu.uclamp.min  cpuacct.usage  cpuacct.usage_percpu  cpuacct.usage_percpu_user  cpuacct.usage_user  tasks

會發(fā)現(xiàn)mycontainer目錄時會自動出現(xiàn)一些cpu配置文件,有些配置文件內容為-1,表示不限制,其中tasks文件里表示要控制的進程pid。
我們現(xiàn)在做個實現(xiàn)執(zhí)行一下死循環(huán)腳本,便其完全占用CPU達到100%,然后再對此PID進行CPU限制,看下效果如果。

$ while : ; do : ; done &
[1] 1626025

執(zhí)行top查看

PID     USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
1626025 root      20   0   12724   1768      0 R 100.0   0.0   0:28.60 bash

發(fā)現(xiàn)這個進程的CPU已經(jīng)達到了100%,下面我們對其進行一下限制。先將進程PID寫到 mycontainer 控制組下的tasks文件里,然后限制cpu使用率

$ echo 1626025 > /sys/fs/cgroup/cpu/mycontainer/tasks
$ cat /sys/fs/cgroup/cpu/mycontainer/tasks
1626025

現(xiàn)在我們已成功將其進程號寫入tasks文件。上面我們提到過對cpu的限制主要使用兩個文件,分別為cpu.cfs_quota_us 和 cpu.cfs_quota_us, 先看一下他們的默認值。

$ cat /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us 
100000

表示在100ms內分配給cpu的機會為不限制,也就是表示100%的資源。我們要做一下限制,讓其在100ms時間內,只分配給 20% 的cpu機會

$ echo 20000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us

然后再執(zhí)行一下top命令發(fā)現(xiàn)cpu使用率立即降下來了,最多為20%左右,可能會有一點點的超出,這個很正常。

PID     USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
1626025 root      20   0   12724   1768      0 R  20.0   0.0  12:43.38 bash

這里我們只對cpu做了限制,你也可以做內在memory做一下限制,由于這里的腳本只會占用cpu,所以不再演示。對于我們經(jīng)常使用docker run 命令啟動一個容器的時候,其實都有一個配置參數(shù)與配置文件相對應,如

$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

如果你到容器目錄查看配置文件會發(fā)現(xiàn)相應 cpu.cfs_period_us 和 cpu.vfs_quota_us 的值都已被修改。

參考鏈接

https://blog.csdn.net/xwy9526/article/details/110594876
http://edsionte.com/techblog/archives/4322
https://www.cnblogs.com/plxx/p/5129245.html

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容