Cgroups
在 2006 年,Google 的工程师( Rohit Seth 和 Paul Menage 为主要发起人) 发起了这个项目,起初项目名称并不是cgroups,而被称为进程容器(process containers)。在 2007 年cgroups代码计划合入Linux 内核,但是当时在 Linux 内核中,容器(container)这个词被广泛使用,并且拥有不同的含义。为了避免命名混乱和歧义,进程容器被重名为cgroups,并在 2008 年成功合入 Linux 2.6.24 版本中。cgroups目前已经成为 systemd、Docker、Linux Containers(LXC) 等技术的基础。
Cgroups 功能及核心概念
Cgroups 主要提供了如下功能:
资源限制:限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
审计:计算控制组的资源使用情况。
控制:控制进程的挂起或恢复。
cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。
子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。
Cgroups 子系统示例
下面通过一个实例演示一下在 Linux 上默认都启动了哪些子系统。
首先通过 mount 命令查看一下当前系统已经挂载的cgroups信息:
# mount -t cgroupcgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)cgroup on sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
操作系统版本为 CentOS7.8,内核为 3.10.0-1127.el7.x86_64 版本,不同内核版本cgroups子系统和使用方式可能略有差异。如果你对cgroups不是很熟悉,请尽量使用与我相同的内核环境操作。
通过输出,可以看到当前系统已经挂载了我们常用的cgroups子系统,例如 cpu、memory、pids 等我们常用的cgroups子系统。这些子系统中,cpu 和 memory 子系统是容器环境中使用最多的子系统,下面对这两个子系统做详细介绍。
cpu 子系统
首先以 cpu 子系统为例,演示一下cgroups如何限制进程的 cpu 使用时间。由于cgroups的操作很多需要用到 root 权限,我们在执行命令前要确保已经切换到了 root 用户,以下命令的执行默认都是使用 root 用户。
在 cpu 子系统下创建 cgroup
cgroups的创建很简单,只需要在相应的子系统下创建目录即可。下面我们到 cpu 子系统下创建测试文件夹:
# mkdir sys/fs/cgroup/cpu/mydocker
执行完上述命令后,我们查看一下我们新创建的目录下发生了什么?
# ls -l sys/fs/cgroup/cpu/mydockertotal 0-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.clone_children--w--w--w-. 1 root root 0 Sep 5 09:19 cgroup.event_control-rw-r--r--. 1 root root 0 Sep 5 09:19 cgroup.procs-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_period_us-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.cfs_quota_us-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_period_us-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.rt_runtime_us-rw-r--r--. 1 root root 0 Sep 5 09:19 cpu.shares-r--r--r--. 1 root root 0 Sep 5 09:19 cpu.stat-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.stat-rw-r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage-r--r--r--. 1 root root 0 Sep 5 09:19 cpuacct.usage_percpu-rw-r--r--. 1 root root 0 Sep 5 09:19 notify_on_release-rw-r--r--. 1 root root 0 Sep 5 09:19 tasks
创建进程,加入 cgroup
# cd /sys/fs/cgroup/cpu/mydocker# echo $$ > tasks
# cat tasks34853543
# while true;do echo;done;
执行完上述命令后,我们新打开一个 shell 窗口,使用 top -p 命令查看当前 cpu 使用率,-p 参数后面跟进程 ID,我这里是 3485。
# top -p 3485top - 09:51:35 up 3 days, 22:00, 4 users, load average: 1.59, 0.58, 0.27Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie%Cpu(s): 9.7 us, 2.8 sy, 0.0 ni, 87.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 32779616 total, 31009780 free, 495988 used, 1273848 buff/cacheKiB Swap: 0 total, 0 free, 0 used. 31852336 avail MemPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND3485 root 20 0 116336 2852 1688 S 99.7 0.0 2:10.71 bash
# cd /sys/fs/cgroup/cpu/mydocker# echo 50000 > cpu.cfs_quota_us
同样使用上面的命令来制造死循环:
# while true;do echo;done;
保持当前窗口,新打开一个 shell 窗口,使用 top -p 参数查看 cpu 使用率:
# top -p 3485top - 10:05:25 up 3 days, 22:14, 3 users, load average: 1.02, 0.43, 0.40Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie%Cpu(s): 5.0 us, 1.3 sy, 0.0 ni, 93.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 stKiB Mem : 32779616 total, 31055676 free, 450224 used, 1273716 buff/cacheKiB Swap: 0 total, 0 free, 0 used. 31898216 avail MemPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND3485 root 20 0 115544 2116 1664 R 50.0 0.0 0:23.39 bash
通过上面输出可以看到,此时 cpu 使用率已经被限制到了 50%,即 0.5 个核。
验证完 cgroup 限制 cpu,我们使用相似的方法来验证 cgroup 对内存的限制。
memroy 子系统
在 memory 子系统下创建 cgroup
# mkdir sys/fs/cgroup/memory/mydocker
同样,我们查看一下新创建的目录下发生了什么?
total 0-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.clone_children--w--w--w-. 1 root root 0 Sep 5 10:18 cgroup.event_control-rw-r--r--. 1 root root 0 Sep 5 10:18 cgroup.procs-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.failcnt--w-------. 1 root root 0 Sep 5 10:18 memory.force_empty-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.failcnt-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.limit_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.max_usage_in_bytes-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.slabinfo-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.failcnt-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.limit_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.max_usage_in_bytes-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.tcp.usage_in_bytes-r--r--r--. 1 root root 0 Sep 5 10:18 memory.kmem.usage_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.limit_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.max_usage_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.failcnt-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.limit_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.max_usage_in_bytes-r--r--r--. 1 root root 0 Sep 5 10:18 memory.memsw.usage_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.move_charge_at_immigrate-r--r--r--. 1 root root 0 Sep 5 10:18 memory.numa_stat-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.oom_control----------. 1 root root 0 Sep 5 10:18 memory.pressure_level-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.soft_limit_in_bytes-r--r--r--. 1 root root 0 Sep 5 10:18 memory.stat-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.swappiness-r--r--r--. 1 root root 0 Sep 5 10:18 memory.usage_in_bytes-rw-r--r--. 1 root root 0 Sep 5 10:18 memory.use_hierarchy-rw-r--r--. 1 root root 0 Sep 5 10:18 notify_on_release-rw-r--r--. 1 root root 0 Sep 5 10:18 tasks
其中 memory.limit_in_bytes 文件代表内存使用总量,单位为 byte。
例如,这里我希望对内存使用限制为 1G,则向 memory.limit_in_bytes 文件写入 1073741824,命令如下:
# cd /sys/fs/cgroup/memory/mydocker# echo 1073741824 > memory.limit_in_bytes
创建进程,加入 cgroup
同样把当前 shell 进程 ID 写入 tasks 文件内:
# cd /sys/fs/cgroup/memory/mydocker# echo $$ > tasks
执行内存测试工具,申请内存
这里我们需要借助一下工具 memtester,memtester 的安装这里不再详细介绍了。具体安装方式可以参考这里。
安装好 memtester 后,我们执行以下命令:
# memtester 1500M 1memtester version 4.2.2 (64-bit)Copyright (C) 2010 Charles Cazabon.Licensed under the GNU General Public License version 2 (only).pagesize is 4096pagesizemask is 0xfffffffffffff000want 1500MB (1572864000 bytes)got 1500MB (1572864000 bytes), trying mlock ...Killed




