天天看點

linux資源限制的原理,Docker技術原理之Linux Cgroups(資源限制)

0.前言

首先要知道一個運作的容器,其實就是一個受到隔離和資源限制的Linux程序——對,它就是一個程序。前面我們讨論了Docker容器實作隔離用到的技術Linux namespace,本篇我們來讨論容器實作資源限制的技術 Linux CGroups。

1.關于Linux CGroups

Linux Cgroups的全稱是Linux Control Groups。它最主要的作用,就是限制一個程序組能夠使用的資源上限,包括CPU、記憶體、磁盤、網絡帶寬等等。此外,還能夠對程序進行優先級設定,以及将程序挂起和恢複等操作。

在Linux中,Cgroups給使用者暴露出來的操作接口是檔案系統,即它以檔案和目錄的方式組織在作業系統的/sys/fs/cgroup路徑下。在我的centos伺服器下,用mount指令把它們展示出來:

//CentOS Linux release 7.5.1804

$ mount -t cgroup

cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)

cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)

cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)

cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)

cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)

cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)

cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)

cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)

cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)

cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)

可以看到,在/sys/fs/cgroup下面有很多諸如cpuset、cpu、 memory這樣的子目錄,也叫子系統。這些都是我這台機器目前可以被Cgroups進行限制的資源種類。而在子系統對應的資源種類下,你就可以看到該類資源具體可以被限制的方法。比如,對CPU子系統來說,我們就可以看到如下幾個配置檔案:

$ ls /sys/fs/cgroup/cpu

cgroup.clone_children cgroup.sane_behavior cpu.rt_period_us cpu.stat cpuacct.usage_percpu system.slice

cgroup.event_control cpu.cfs_period_us cpu.rt_runtime_us cpuacct.stat notify_on_release tasks

cgroup.procs cpu.cfs_quota_us cpu.shares cpuacct.usage release_agent user.slice

2.舉個例子(CPU限制)

1) 配置你的控制組

$ cd /sys/fs/cgroup/cpu

$ mkdir testlimit

$ ls testlimit/

cgroup.clone_children cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat cpuacct.usage notify_on_release

cgroup.event_control cpu.cfs_period_us cpu.rt_period_us cpu.shares cpuacct.stat cpuacct.usage_percpu tasks

$ cat /sys/fs/cgroup/cpu/testlimit/cpu.cfs_quota_us

-1

$ cat /sys/fs/cgroup/cpu/testlimit/cpu.cfs_period_us

100000

你建立的這個目錄testlimit就稱為一個“控制組”。你會發現,作業系統會在你新建立的目錄下,自動生成該子系統對應的資源限制檔案。可以看到testlimit控制組裡的CPU quota還沒有任何限制(即:-1),CPU period則是預設的100000us

配置一個隻能使用30%cpu的限制,即長度為cfs_period的一段時間内,隻能被配置設定到總量為cfs_quota的CPU時間。

$ echo 30000 > /sys/fs/cgroup/cpu/testlimit/cpu.cfs_quota_us

至此我們的testlimit控制組就配置好了,它限制程序在100000us裡隻能使用30000us的cpu時間。隻是目前沒有将它應用于任何程序。

2) 執行腳本

$ while : ; do : ; done &

[1] 4477

該腳本執行了一個無限循環,可以把cpu吃到100%,可以看到它在背景的程序id是4477,後面限制的時候我們會用到。

通過top檢視cpu使用情況:

%Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

3) 使用cgroups限制該程序的cpu

執行如下指令,使用剛才配置好的testlimit控制組,限制上面4477号程序的cpu:

$ echo 4477 > /sys/fs/cgroup/cpu/testlimit/tasks

再次通過top檢視cpu使用情況:

%Cpu0 : 30.1 us, 3.0 sy, 0.0 ni, 65.5 id, 1.0 wa, 0.0 hi, 0.3 si, 0.0 st

可以看到使用剛才建立的testlimit控制組,将cpu被限制到了30%左右。

4) 啟動一個容器加上CPU時鐘周期限制

接下來,我們啟動一個容器,并加上cpu限制,然後看看cgroups對應的目錄裡有沒有該容器的限制。

$ docker run -td --cpu-period 100000 --cpu-quota 200000 busybox /bin/sh -c "while : ; do : ; done"

c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f

//檢視該容器的程序id: 26430

$ ps -x |grep '/bin/sh'

26430 pts/0 Rs+ 20:52 /bin/sh -c while : ; do : ; done

//檢視cgroups

$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/cpu.cfs_period_us

100000

$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/cpu.cfs_quota_us

200000

$ cat /sys/fs/cgroup/cpu/docker/c3e3fb30f3cbdcc707dff9f5937018c0ac6b07002d80656760026111c569ca4f/tasks

26430

$ top

%Cpu0 : 50.8 us, 49.2 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st

如上所示,可以通過啟動容器時傳回的容器ID在cgroups中找到對應的限制。

3.對照Docker源碼

// New creates and initializes a new containerd server

func New(ctx context.Context, config *Config) (*Server, error) {

//...

if err := apply(ctx, config); err != nil {

return nil, err

}

//...

}

// apply sets config settings on the server process

func apply(ctx context.Context, config *Config) error {

if config.OOMScore != 0 {

log.G(ctx).Debugf("changing OOM score to %d", config.OOMScore)

if err := sys.SetOOMScore(os.Getpid(), config.OOMScore); err != nil {

log.G(ctx).WithError(err).Errorf("failed to change OOM score to %d", config.OOMScore)

}

}

if config.Cgroup.Path != "" {

cg, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(config.Cgroup.Path))

if err != nil {

if err != cgroups.ErrCgroupDeleted {

return err

}

if cg, err = cgroups.New(cgroups.V1, cgroups.StaticPath(config.Cgroup.Path), &specs.LinuxResources{}); err != nil {

return err

}

}

if err := cg.Add(cgroups.Process{

Pid: os.Getpid(),

}); err != nil {

return err

}

}

return nil

}

在上面代碼中,建立容器時會調用apply接口,裡面的cgroups.Load調用就會去加載cgroups,cg.Add把建立的容器程序加入到cgroups task中。

4.下一代Linux Cgroups

在Kernel 3.16後,引入了一個叫__DEVEL__sane_behavior的特性(還在開發試驗階段),它可以把所有子系統都挂載到根層級下,隻有葉子節點可以存在tasks,非葉子節點隻進行資源控制。

The unified control group hierarchy in 3.16

參考