如何通过 Cgroups 机制实现资源限制

简介: 如何通过 Cgroups 机制实现资源限制

cgroups功能及核心概念

cgroups(全称:control groups)是 Linux 内核的一个功能,它可以实现限制进程或者进程组的资源(如 CPU、内存、磁盘 IO 等)。

在 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 主要提供了如下功能

  • 资源限制:限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。
  • 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。
  • 审计:计算控制组的资源使用情况。
  • 控制:控制进程的挂起或恢复。

cgroups功能的实现依赖于三个核心概念:子系统、控制组、层级树。

  • 子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。
  • 控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。
  • 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。

cgroups 的三个核心概念中,子系统是最核心的概念,因为子系统是真正实现某类资源的限制的基础。

cgroups 子系统实例

下面我通过一个实例演示一下在 Linux 上默认都启动了哪些子系统。我们先通过 mount 命令查看一下当前系统已经挂载的cgroups信息:

root@cr7-ubuntu:~# 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/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
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/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)

通过输出,可以看到当前系统已经挂载了我们常用的cgroups子系统,例如 cpu、memory、pids 等我们常用的cgroups子系统。这些子系统中,cpu 和 memory 子系统是容器环境中使用最多的子系统,下面我对这两个子系统做详细介绍。

cpu 子系统

首先我们在没有限制CPU使用率的情况下在后台执行这样一条脚本:

root@cr7-ubuntu:~# while : ; do : ; done &
[1] 839960

显然,它执行了一个死循环,可以把计算机的 CPU 吃到 100%,根据它的输出,我们可以看到这个脚本在后台运行的进程号(PID)是 839960。

我们可以用 top 指令来确认一下 CPU 有没有被打满:image.png在输出里可以看到,CPU 的使用率已经 100% 了(%Cpu(s): 98.0 us)。

现在我们 以 cpu 子系统为例,演示一下cgroups如何限制进程的 cpu 使用时间。由于cgroups的操作很多需要用到 root 权限,我们在执行命令前要确保已经切换到了 root 用户,以下命令的执行默认都是使用 root 用户。

第一步:在 cpu 子系统下创建 cgroup cgroups的创建很简单,只需要在相应的子系统下创建目录即可。下面我们到 cpu 子系统下创建测试文件夹:

root@cr7-ubuntu:~# mkdir  /sys/fs/cgroup/cpu/mydocker

这个目录就称为一个“控制组”。你会发现,操作系统会在你新创建的 container 目录下,自动生成该子系统对应的资源限制文件:

root@cr7-ubuntu:~# ls -l /sys/fs/cgroup/cpu/mydocker
total 0
-rw-r--r-- 1 root root 0 Mar 10 09:34 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 10 09:34 cgroup.procs
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.stat
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_all
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Mar 10 09:34 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpu.shares
-r--r--r-- 1 root root 0 Mar 10 09:34 cpu.stat
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpu.uclamp.max
-rw-r--r-- 1 root root 0 Mar 10 09:34 cpu.uclamp.min
-rw-r--r-- 1 root root 0 Mar 10 09:34 notify_on_release
-rw-r--r-- 1 root root 0 Mar 10 09:34 tasks

你会在它的输出里注意到 cfs_period 和 cfs_quota 这样的关键词。这两个参数需要组合使用,可以用来限制进程在长度为 cfs_period 的一段时间内,只能被分配到总量为 cfs_quota 的 CPU 时间。如果用这个值去除以调度周期(也就是 cpu.cfs_period_us),50ms/100ms = 0.5,这样这个控制组被允许使用的 CPU 最大配额就是 0.5 个 CPU。从这里能够看出,cpu.cfs_quota_us 是一个绝对值。如果这个值是 200000,也就是 200ms,那么它除以 period,也就是 200ms/100ms=2,结果超过了 1 个 CPU,这就意味着这时控制组需要 2 个 CPU 的资源配额。

而此时,我们可以通过查看 mydocker 目录下的文件,看到 mydocker 控制组里的 CPU quota 还没有任何限制(即:-1),CPU period 则是默认的 100 ms(100000 us):

root@cr7-ubuntu:~# cat /sys/fs/cgroup/cpu/mydocker/cpu.cfs_quota_us
-1
root@cr7-ubuntu:~# cat /sys/fs/cgroup/cpu/mydocker/cpu.cfs_period_us
100000

第二步:设置限制参数

接下来,我们可以通过修改这些文件的内容来设置限制。向 mydocker 组里的 cfs_quota 文件写入 20 ms(20000 us):

root@cr7-ubuntu:~# echo 20000 > /sys/fs/cgroup/cpu/mydocker/cpu.cfs_quota_us

它意味着在每 100 ms 的时间里,被该控制组限制的进程只能使用 20 ms 的 CPU 时间,也就是说这个进程只能使用到 20% 的 CPU 带宽。(如果CPU是多核,那么20%指的是这个进程在所有CPU中能使用的带宽总和,例如CPU1是10%,CPU2是10%)

第三步:将进程加入cgroup控制组

接下来,我们把被限制的进程的 PID 写入 mydocker 组里的 tasks 文件,上面的设置就会对该进程生效了:

root@cr7-ubuntu:~# echo 839960 > /sys/fs/cgroup/cpu/mydocker/tasks

此时我们用top命令查看一下:可以看到,计算机的 CPU 使用率立刻降到了 20%(%Cpu0 : 21.5 us)。

memroy 子系统

第一步:在 memory 子系统下创建 cgroup

root@cr7-ubuntu:~#  mkdir /sys/fs/cgroup/memory/mydocker

同样,我们查看一下新创建的目录下自动创建的文件:

root@cr7-ubuntu:~# ls  -l /sys/fs/cgroup/memory/mydocker
total 0
-rw-r--r-- 1 root root 0 Mar 10 09:50 cgroup.clone_children
--w--w--w- 1 root root 0 Mar 10 09:50 cgroup.event_control
-rw-r--r-- 1 root root 0 Mar 10 09:50 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.failcnt
--w------- 1 root root 0 Mar 10 09:50 memory.force_empty
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.numa_stat
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.oom_control
---------- 1 root root 0 Mar 10 09:50 memory.pressure_level
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.stat
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.swappiness
-r--r--r-- 1 root root 0 Mar 10 09:50 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Mar 10 09:50 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Mar 10 09:50 notify_on_release
-rw-r--r-- 1 root root 0 Mar 10 09:50 tasks

其中 memory.limit_in_bytes 文件代表内存使用总量,单位为 byte。

第二步:设置限制参数 例如,这里我希望对内存使用限制为 1G,则向 memory.limit_in_bytes 文件写入 1073741824,命令如下:

root@cr7-ubuntu:~# echo 1073741824 >  /sys/fs/cgroup/memory/mydocker/memory.limit_in_bytes

第三步:将进程加入cgroup控制组 把当前 shell 进程 ID 写入 tasks 文件内:

root@cr7-ubuntu:~# echo $$ > /sys/fs/cgroup/memory/mydocker/tasks

这里我们需要借助一下工具 memtester,memtester 的安装这里不再详细介绍了。

安装好 memtester 后,我们执行以下命令:

root@cr7-ubuntu:~# memtester 1500M 1
memtester version 4.2.2 (64-bit)
Copyright (C) 2010 Charles Cazabon.
Licensed under the GNU General Public License version 2 (only).
pagesize is 4096
pagesizemask is 0xfffffffffffff000
want 1500MB (1572864000 bytes)
got  1500MB (1572864000 bytes), trying mlock ...Killed

该命令会申请 1500 M 内存,并且做内存测试。由于上面我们对当前 shell 进程内存限制为 1 G,当 memtester 使用的内存达到 1G 时,cgroup 便将 memtester 杀死。

上面最后一行的输出结果表示 memtester 想要 1500 M 内存,但是由于 cgroup 限制,达到了内存使用上限,被杀死了,与我们的预期一致。

我们可以使用以下命令,降低一下内存申请,将内存申请调整为 500M:

root@cr7-ubuntu:~# memtester 500M 1
memtester version 4.2.2 (64-bit)
Copyright (C) 2010 Charles Cazabon.
Licensed under the GNU General Public License version 2 (only).
pagesize is 4096
pagesizemask is 0xfffffffffffff000
want 500MB (524288000 bytes)
got  500MB (524288000 bytes), trying mlock ...locked.
Loop 1/1:
  Stuck Address       : ok
  Random Value        : ok
  Compare XOR         : ok
  Compare SUB         : ok
  Compare MUL         : ok
  Compare DIV         : ok
  Compare OR          : ok
  Compare AND         : ok
  Sequential Increment: ok
  Solid Bits          : ok
  Block Sequential    : ok
  Checkerboard        : ok
  Bit Spread          : ok
  Bit Flip            : ok
  Walking Ones        : ok
  Walking Zeroes      : ok
  8-bit Writes        : ok
  16-bit Writes       : ok
Done.

这里可以看到,此时 memtester 已经成功申请到 500M 内存并且正常完成了内存测试。

删除 cgroups

上面创建的cgroups如果不想使用了,直接删除创建的文件夹即可。例如我想删除内存下的 mydocker 目录,使用以下命令即可:

root@cr7-ubuntu:~# rmdir /sys/fs/cgroup/memory/mydocker/

Docker 使用 cgroups

限制容器cpu

限制容器只能使用到 20% 的 CPU 带宽。

root@cr7-ubuntu:~# docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash

在启动这个容器后,我们可以通过查看 Cgroups 文件系统下,CPU 子系统中,“docker”这个控制组里的资源限制文件的内容来确认:

root@cr7-ubuntu:~# cat /sys/fs/cgroup/cpu/docker/99cdb60e28191871c456f736470c9a1d344b95c6bcdb39036926428378db55c4/cpu.cfs_period_us
100000
root@cr7-ubuntu:~# cat /sys/fs/cgroup/cpu/docker/99cdb60e28191871c456f736470c9a1d344b95c6bcdb39036926428378db55c4/cpu.cfs_quota_us
20000

很长的数字+字母为容器的id,可以使用docker inspect <容器名>命令来查看容器的id。

限制容器memory

root@cr7-ubuntu:~# docker run -it -m=1g nginx

上述命令创建并启动了一个 nginx 容器,并且限制内存为 1G。然后我们查看该容器对应的cgroup控制组 memory.limit_in_bytes 文件的内容,可以看到内存限制值正好为 1G:

root@cr7-ubuntu:~# cat /sys/fs/cgroup/memory/docker/2e5f7db4b9a08b08471b3fcf9f71cb14396fb081db3cdc25140714ae037f2136/memory.limit_in_bytes
1073741824
目录
相关文章
|
消息中间件 监控 算法
深入理解Linux进程管理与优化:原理、调度和资源控制详解
深入理解Linux进程管理与优化:原理、调度和资源控制详解
285 0
|
7月前
|
Shell Linux 调度
cgroup 资源控制介绍
cgroup 资源控制介绍
|
4月前
|
存储 Kubernetes 数据中心
在K8S中,同⼀个Pod内不同容器哪些资源是共用的,哪些资源是隔离的?
在K8S中,同⼀个Pod内不同容器哪些资源是共用的,哪些资源是隔离的?
|
5月前
|
算法 调度 UED
操作系统中的进程调度策略及其对系统性能的影响
本文深入探讨了操作系统中进程调度的多种策略,包括先来先服务、短作业优先、优先级调度、轮转与多级队列等,并分析了它们对系统性能的具体影响。通过比较不同调度算法的效率和公平性,本文旨在为系统管理员提供选择合适调度策略的依据,以优化系统的整体表现。
|
资源调度 Linux Shell
docker-资源限制:如何通过 Cgroups 机制实现资源限制?
我们知道使用不同的 Namespace,可以实现容器中的进程看不到别的容器的资源,但是有一个问题你是否注意到?
220 0
|
SQL 监控 数据库
GPDB-内核特性-资源组内存管理机制-1
GPDB-内核特性-资源组内存管理机制-1
103 0
|
SQL
GPDB-内核特性-资源组内存管理机制-2
GPDB-内核特性-资源组内存管理机制-2
94 0
|
资源调度 测试技术 Apache
YARN中的CPU资源隔离-CGroups
YARN中集成了CGroups的功能,使得NodeManger可以对container的CPU的资源使用进行控制,比如可以对单个container的CPU使用进行控制,也可以对NodeManger管理的总CPU进行控制。
9827 0
|
Linux 调度 数据中心
Linux cgroup资源隔离各个击破之 - io隔离
Linux Cgroup blkio子系统的用法. blkio子系统支持两种IO隔离策略 .1. cfq io调度器,支持按权重分配IO处理的时间片,从而达到IO调度和限制的目的,权重取值范围100-1000。通过以下两个文件进行配置。 blkio.weight
11277 0
|
Kubernetes Cloud Native 应用服务中间件
如何合理使用 CPU 管理策略,提升容器性能?
CPU Burst、拓扑感知调度是阿里云容器服务 ACK 提升应用性能的两大利器,它们解决了不同场景下的 CPU 资源管理,可以共同使用。点击下文,查看详情!
如何合理使用 CPU 管理策略,提升容器性能?