1. 程式人生 > 其它 >沒有 Cgroups,就沒有 Docker

沒有 Cgroups,就沒有 Docker

Cgroups 是什麼?

Cgroups 是 control groups 的縮寫,是 Linux 核心提供的一種可以限制記錄隔離程序組(progress groups)所使用的物理資源(如:cpu,memory,IO 等等)的機制。最初由 google 的工程師提出,後面被整合進 Linux 核心。

Cgroups 可以做什麼?

Cgroups 最初的目標是為資源管理提供的一個統一的框架,既整合需要的 cpuset 等子系統,也為未來開發新的子系統提供介面。現在的 cgroups 適用多種應用場景,從單個程序的資源控制,到實現作業系統層次的虛擬化(OS Level Virtualization)。

cgroups 子系統(subsystem)

它的實現原理是通過對各類 Linux subsystem 進行引數設定,然後將程序和這些子系統進行繫結。

Linux subsystem 有以下幾種:

  • blkio
  • cpu
  • cpuacct 統計cgroup中程序的CPU佔用
  • cpuset
  • devices
  • freezer 使用者掛起和恢復從group中的程序
  • memeory 控制cgroup中程序的記憶體佔用
  • net_cls
  • net_prio
  • ns

通過安裝 cgroup 工具

$ apt-get install cgroup-tools
$ lssubsys -a
cpuset
cpu,cpuacct
blkio
memory
devices
freezer
net_cls,net_prio
perf_event
hugetlb
pids
rdma

cgroups 層級結構(hierarchy)

hierarchy的功能是把一組 cgroup 組織成一個樹狀結構,讓 Cgroup 可以實現繼承

一個 cgroup1 限制了其下的程序(P1、P2、P3)的 CPU 使用頻率,如果還想對程序P2進行記憶體的限制,可以在 cgroup1 下建立 cgroup2,使其繼承於 cgroup1,可以限制 CPU 使用率,又可以設定記憶體的限制而不影響其他程序。

核心使用 cgroups 結構體來表示對某一個或某幾個 cgroups 子系統的資源限制,它是通過一棵樹的形式進行組織,被成為hierarchy.

cgroups 與程序

hierarchy、subsystem 與cgroup程序組間的關係
hierarchy 只實現了繼承關係,真正的資源限制還是要靠 subsystem
通過將 subsystem 附加到 hierarchy上,
將程序組 加入到 hierarchy下(task中),實現資源限制

通過這張圖可以看出:

  • 一個 subsystem 只能附加到一個 hierarchy 上面
  • 一個 hierarchy 可以附加多個 subsystem
  • 一個程序可以作為多個 cgroup 的成員,但是這些 cgroup 必須在不同hierarchy 中。
  • 一個程序 fork 出子程序時,子程序是和父程序在同一個 cgroup 中的,也可以根據需要將其移動到其他 cgroup 中。

cgroups 檔案系統

cgroups 的底層實現被 Linux 核心的 VFS(Virtual File System)進行了隱藏,給使用者態暴露了統一的檔案系統 API 藉口。我們來體驗一下這個檔案系統的使用方式:

  1. 首先,要建立並掛載一個hierarchy(cgroup樹)
$ mkdir cgroup-test
$ sudo mount -t cgroup -o none,name=cgroup-test cgrout-test ./cgroup-test
$ ls ./cgrpup-test
cgroup.clone_children  cgroup.sane_behavior  release_agent
cgroup.procs           notify_on_release     tasks

這些檔案就是這個hierarchy中cgroup根節點的配置項

cgroup.clone_children 會被 cpuset 的 subsystem 讀取,如果是1,子 cgroup 會繼承父 cgroup 的 cpuset 的配置。

notify_on_release 和 release_agent 用於管理當最後一個程序退出時執行一些操作

tasks 標識該 cgroup 下面的程序 ID,將 cgroup 的程序成員與這個 hierarchy 關聯

2.再建立兩個子 hierarchy建立剛剛建立好的hierarchy上cgroup根節點中擴展出的兩個子cgroup

$ cd cgroup-test
$ sudo mkdir cgroup-1
$ sudo mkdir cgroup-2
$ tree
.
├── cgroup-1
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── notify_on_release
│   └── tasks
├── cgroup-2
│   ├── cgroup.clone_children
│   ├── cgroup.procs
│   ├── notify_on_release
│   └── tasks
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── notify_on_release
├── release_agent
└── tasks

2 directories, 14 files

可以看到,在一個 cgroup 的目錄下建立資料夾時,Kernel 會把資料夾標記為這個 cgroup 的子 cgroup,它們會繼承父 cgroup 的屬性。

  1. 向cgroup中新增和移動程序
    一個程序在一個Cgroups的hierarchy中,只能在一個cgroup節點上存在,系統的所有程序都會預設在根節點上存在,可以將程序移動到其他cgroup節點,只需要將程序ID寫到移動到的cgroup節點的tasks檔案中即可。
# cgroup-test
$ ehco $$
3444
$ cat /proc/3444/cgroup 
13:name=cgroup-test:/
12:cpuset:/
11:rdma:/
10:devices:/user.slice
9:perf_event:/
8:net_cls,net_prio:/
7:pids:/user.slice/user-1000.slice/[email protected]
6:memory:/user.slice/user-1000.slice/[email protected]
...

可以看到當前終端的程序在根 cgroup 下,我們現在把他移動到子 cgroup 下

$ cd cgroup-1
$ sudo sh -c "echo $$ >> tasks"
$ cat /proc/3444/cgroup
13:name=cgroup-test:/cgroup-1
12:cpuset:/
11:rdma:/
10:devices:/user.slice
9:perf_event:/
8:net_cls,net_prio:/
7:pids:/user.slice/user-1000.slice/[email protected]
6:memory:/user.slice/user-1000.slice/[email protected]
...

可以看到終端程序所屬的 cgroup 已將變成了 cgroup-1,再看一下父 cgroup 的tasks,已經沒有了終端程序的 ID

$ cd cgroup-test
$ cat tasks | grep "3444"
# 返回為空
  1. 通過 subsystem 限制 cgroup 中程序的資源。

作業系統預設已為每一個 subsystem 建立了一個預設的 hierarchy,在sys/fs/cgroup/目錄下

$ ls /sys/fs/cgroup
blkio    cpu,cpuacct  freezer  net_cls           perf_event  systemd
cpu      cpuset       hugetlb  net_cls,net_prio  pids        unified
cpuacct  devices      memory   net_prio          rdma

可以看到記憶體子系統的 hierarchy 也在其中建立一個子cgroup

$ cd /sys/fs/cgroup/memory
$ sudo mkdir test-limit-memory && cd test-limit-memorysudo
# 設定最大記憶體使用為 100MB
$ sudo sh -c "echo "100m" > memory.limit_in_bytes"sudo sh -c "echo $$ > tasks"
sudo sh -c "echo $$ > tasks"
$ sudo sh -c "echo $$ > tasks"
# 執行佔用記憶體200MB 的 stress 經常
$ stress --vm-bytes 200m --vm-keep -m 1

可以對比執行前後的記憶體剩餘量,大概只減少了100MB

# 執行前
$ top
top - 12:04:12 up  6:45,  1 user,  load average: 1.87, 1.29, 1.06
任務: 348 total,   1 running, 346 sleeping,   0 stopped,   1 zombie
%Cpu(s):  1.3 us,  0.9 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
MiB Mem :   5973.4 total,    210.8 free,   2820.9 used,   2941.8 buff/cache
MiB Swap:    923.3 total,    921.9 free,      1.3 used.   2746.3 avail Mem 

# 執行後
$ top
top - 12:04:57 up  6:45,  1 user,  load average: 2.25, 1.44, 1.12
任務: 351 total,   3 running, 347 sleeping,   0 stopped,   1 zombie
%Cpu(s): 34.3 us, 32.8 sy,  0.0 ni, 21.1 id,  4.9 wa,  0.0 hi,  6.9 si,  0.0 st
MiB Mem :   5973.4 total,    118.6 free,   2956.7 used,   2898.1 buff/cache
MiB Swap:    923.3 total,    817.7 free,    105.5 used.   2604.5 avail Mem 

說明 cgroup 的限制生效了

docker 中是怎樣進行 cgroup 限制的

首先執行一個被限制記憶體的容器

$ sudo docker pull redis:4
$ sudo docker run -tid -m 100m redis:4
d79f22eb11d22c56a90f88e0aeb3cfda7cbe9639e2ab0e8532003a695e375e8d

檢視原來的記憶體子系統繫結的cgroup,會看到裡面多了子cgroup, docker

$ ls /sys/fs/cgroup/memory
... docker
...
$ ls /sys/fs/cgroup/memory/docker
cgroup.clone_children                                             memory.max_usage_in_bytes
cgroup.event_control                                              memory.memsw.failcnt
cgroup.procs                                                      memory.memsw.limit_in_bytes
d79f22eb11d22c56a90f88e0aeb3cfda7cbe9639e2ab0e8532003a695e375e8d  memory.memsw.max_usage_in_bytes
memory.failcnt                                                    memory.memsw.usage_in_bytes
memory.force_empty                                                memory.move_charge_at_immigrate
memory.kmem.failcnt                                               memory.numa_stat
memory.kmem.limit_in_bytes                                        memory.oom_control
memory.kmem.max_usage_in_bytes                                    memory.pressure_level
memory.kmem.slabinfo                                              memory.soft_limit_in_bytes
memory.kmem.tcp.failcnt                                           memory.stat
memory.kmem.tcp.limit_in_bytes                                    memory.swappiness
memory.kmem.tcp.max_usage_in_bytes                                memory.usage_in_bytes
memory.kmem.tcp.usage_in_bytes                                    memory.use_hierarchy
memory.kmem.usage_in_bytes                                        notify_on_release
memory.limit_in_bytes                                             tasks

可以看到dockercgroup裡面的d79f22eb11d22c56a90f88e0aeb3cfda7cbe9639e2ab0e8532003a695e375e8dcgroup 正好是我們
剛才建立的容器 ID,那麼看一下里面吧

$ cd /sys/fs/cgroup/memory/docker/d79f22eb11d22c56a90f88e0aeb3cfda7cbe9639e2ab0e8532003a695e375e8d
$ cat memory.limit_in_bytes
104857600cat
# 正好是100MB

總結

講述了 cgroups 的原理,它是通過三個概念(cgroup、subsystem、hierarchy)進行組織和關聯的,可以理解為
3層結構,將程序關聯在 cgroup 中,然後把 cgroup 與 hierarchy 關聯,subsystem 再與 hierarchy 關聯,從而在限制程序資源的基礎上達到一定
的複用能力。

講述了 docker 的具體實現方式,在使用 docker 時,也能從心中瞭然它時怎麼做到對容器使用資源的限制的。