Shawn摘要

k8s 官方对cgropu v2的介绍

https://kubernetes.io/docs/concepts/architecture/cgroups/

cgroup v2 is the next version of the Linux cgroup API. cgroup v2 provides a unified control system with enhanced resource management capabilities.
cgroup v2 是 Linux cgroup API 的下一个版本。 cgroup v2提供了统一的控制系统,增强了资源管理能力。

cgroup v2 offers several improvements over cgroup v1, such as the following:
cgroup v2 相对于 cgroup v1 进行了多项改进,例如:

  • Single unified hierarchy design in API
    API中单一统一的层次结构设计

  • Safer sub-tree delegation to containers
    更安全的子树委托给容器

  • Newer features like Pressure Stall Information
    压力失速信息等新功能

  • Enhanced resource allocation management and isolation across multiple resources

    增强的资源分配管理和跨多个资源的隔离

    • Unified accounting for different types of memory allocations (network memory, kernel memory, etc)
      统一核算不同类型的内存分配(网络内存、内核内存等)
    • Accounting for non-immediate resource changes such as page cache write backs
      考虑非立即资源更改,例如页面缓存写回

查看当前 linux 上的 cgroup 版本

1
stat -fc %T /sys/fs/cgroup/

如果是 cgroup v2 会输出 cgroup2fs

如果是 cgropu v1 会输出 tmpfs

Linux Cgroups

Linux Cgroups 的全称是 Linux Control Group。它最主要的作用,就是限制一个进程组能够使用的资源上限,包括CPU、内存、磁盘、网络带宽等等。

在Linux中,Cgroups 给用户暴露出来的操作接口是文件系统,即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。

可以看到,在/sys/fs/cgroup下面有很多诸如cpuset,cpu,memory这样的子目录,也叫子系统。这些都是我这台机器当前可以被cgroups进行限制的资源种类,在最新的cgroup v2,则是在一个目录下统一了进行限制的资源种类。而在子系统对应的资源种类下,你就可以看到该类资源具体可以被限制的方法。比如,对CPU子系统来说,我们就可以看到如下几个配置文件

1
2
3
$ ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

而这样的配置文件又如何使用呢?

cgroup v2 可以直接在 cgroup 目录下创建一个目录,这个目录就称为一个“控制组”。操作系统会在新创建的 container 目录下,自动生成该子系统对应的资源文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
# /sys/fs/cgroup
mkdir container
ls /sys/fs/cgroup/container
cgroup.controllers cgroup.type cpu.stat hugetlb.1GB.rsvd.max io.stat memory.oom.group pids.events
cgroup.events cpu.idle cpu.uclamp.max hugetlb.2MB.current io.weight memory.pressure pids.max
cgroup.freeze cpu.max cpu.uclamp.min hugetlb.2MB.events memory.current memory.stat rdma.current
cgroup.kill cpu.max.burst cpu.weight hugetlb.2MB.events.local memory.events memory.swap.current rdma.max
cgroup.max.depth cpu.pressure cpu.weight.nice hugetlb.2MB.max memory.events.local memory.swap.events
cgroup.max.descendants cpuset.cpus hugetlb.1GB.current hugetlb.2MB.rsvd.current memory.high memory.swap.high
cgroup.procs cpuset.cpus.effective hugetlb.1GB.events hugetlb.2MB.rsvd.max memory.low memory.swap.max
cgroup.stat cpuset.cpus.partition hugetlb.1GB.events.local io.max memory.max misc.current
cgroup.subtree_control cpuset.mems hugetlb.1GB.max io.pressure memory.min misc.max
cgroup.threads cpuset.mems.effective hugetlb.1GB.rsvd.current io.prio.class memory.numa_stat pids.current

如果在后台执行一条脚本

1
2
while : ; do : ; done &
[1] 1242421

这样执行一个死循环,可以把CPU吃到100%,根据他的输出,我们可以看到这个脚本在后台运行的进程号(PID)是1242421。

可以用top命令来确认一下CPU有没有被打满

1
2
3
4
5
6
7
8
9
10
11
12
13
top - 20:17:13 up 43 days,  6:30,  2 users,  load average: 0.45, 0.14, 0.04
Tasks: 257 total, 2 running, 251 sleeping, 0 stopped, 4 zombie
%Cpu(s): 0.2 us, 5.2 sy, 7.5 ni, 86.9 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
MiB Mem : 16008.0 total, 9279.5 free, 3315.9 used, 3781.0 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 12692.1 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1242421 root 25 5 15512 5264 1312 R 100.0 0.0 0:33.12 zsh
15 root 20 0 0 0 0 I 0.3 0.0 44:17.15 rcu_sched
809 root 20 0 1678752 35040 12208 S 0.3 0.2 77:33.25 proxima
995 root 20 0 261668 28240 14188 S 0.3 0.2 11:09.49 tuned
1885 root 20 0 715824 17920 2496 S 0.3 0.1 145:15.07 modd
2501 root 20 0 1946508 137220 58716 S 0.3 0.8 96:08.09 prometheus

而此时,我们可以通过查看container 目录下的文件,看到 container 控制组里的CPU还没有任何限制

1
2
cat cpu.max
max 100000

接下来,我们可以通过修改这些文件来设置限制

1
echo "50000 100000" > cpu.max

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

1
echo 1242421 > /sys/fs/cgroup/container/cgroup.procs

我们可以用top命令查看一下

1
2
3
4
5
6
7
8
9
10
11
12
top - 20:21:57 up 43 days,  6:35,  2 users,  load average: 0.99, 0.79, 0.35
Tasks: 256 total, 2 running, 250 sleeping, 0 stopped, 4 zombie
%Cpu(s): 0.2 us, 3.1 sy, 3.4 ni, 93.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
MiB Mem : 16008.0 total, 9277.7 free, 3317.5 used, 3781.2 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 12690.5 avail Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1242421 root 25 5 15512 5460 1508 R 50.0 0.0 5:11.03 zsh
5138 root 20 0 763316 57960 32964 S 0.7 0.4 101:21.99 travel-rpc
8900 root 20 0 7963760 1.0g 24972 S 0.7 6.4 322:17.96 java
809 root 20 0 1678752 34716 12208 S 0.3 0.2 77:33.60 proxima
2251 root 20 0 1521496 171928 111160 S 0.3 1.0 50:19.36 grafana

除CPU子系统外,Cgroups的每一个子系统都有其独有的资源限制能力,比如:

  • blkio,为块设备设定I/O限制,一般用于磁盘等设备;
  • cpuset,为进程分配单独的CPU核和对应的内存节点
  • memory,为进程设定内存使用的限制。

cgroup v1和cgropu v2的区别是什么?

目录结构区别

cgroup v2

1
2
3
4
5
6
7
8
# /sys/fs/cgroup
ls
cgroup.controllers cgroup.threads dev-hugepages.mount io.prio.class proc-sys-fs-binfmt_misc.mount user.slice
cgroup.max.depth container dev-mqueue.mount io.stat sys-fs-fuse-connections.mount
cgroup.max.descendants cpu.pressure init.scope memory.numa_stat sys-kernel-config.mount
cgroup.procs cpuset.cpus.effective io.cost.model memory.pressure sys-kernel-debug.mount
cgroup.stat cpuset.mems.effective io.cost.qos memory.stat sys-kernel-tracing.mount
cgroup.subtree_control cpu.stat io.pressure misc.capacity system.slice

cgroup v1

1
2
3
$ ls /sys/fs/cgroup/cpu
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

cgroup v1cpu,memory...每个资源都有一个单独的目录,而cgroup v2则是创建一个单独的目录,将所有的资源(cpu,memory)都集中在一起。

1
2
3
4
root@ubuntu:/sys/fs/cgroup/cpu$ mkdir container
root@ubuntu:/sys/fs/cgroup/cpu$ ls container/
cgroup.clone_children cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.procs cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks

配置文件名的区别

cgroup v2

进入到新创建的 container 目录

1
2
3
4
5
6
7
8
9
10
11
# /sys/fs/cgroup/container
cgroup.controllers cgroup.type cpu.stat hugetlb.1GB.rsvd.max io.stat memory.oom.group pids.events
cgroup.events cpu.idle cpu.uclamp.max hugetlb.2MB.current io.weight memory.pressure pids.max
cgroup.freeze cpu.max cpu.uclamp.min hugetlb.2MB.events memory.current memory.stat rdma.current
cgroup.kill cpu.max.burst cpu.weight hugetlb.2MB.events.local memory.events memory.swap.current rdma.max
cgroup.max.depth cpu.pressure cpu.weight.nice hugetlb.2MB.max memory.events.local memory.swap.events
cgroup.max.descendants cpuset.cpus hugetlb.1GB.current hugetlb.2MB.rsvd.current memory.high memory.swap.high
cgroup.procs cpuset.cpus.effective hugetlb.1GB.events hugetlb.2MB.rsvd.max memory.low memory.swap.max
cgroup.stat cpuset.cpus.partition hugetlb.1GB.events.local io.max memory.max misc.current
cgroup.subtree_control cpuset.mems hugetlb.1GB.max io.pressure memory.min misc.max
cgroup.threads cpuset.mems.effective hugetlb.1GB.rsvd.current io.prio.class memory.numa_stat pids.current

cpu.max:用于限制 CPU 使用。

  1. 查看当前 CPU 限制

    1
    cat /sys/fs/cgroup/cpu.max

    如果结果是 max 100000,表示没有限制。

  2. 设置 CPU 限制

​ 例如,要限制某个 cgroup 使用不超过 50% 的 CPU:

1
echo "50000 100000" > /sys/fs/cgroup/cpu.max

这表示在每 100ms 的周期内,最多使用 50ms 的 CPU 时间。

cgroup v1

cfs_periodcfs_quota这两个参数需要组合使用,可以用来限制进程在长度为cfs_period的一段时间内,只能被分配到总量为cfs_quota的CPU时间

1
2
3
4
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/container/cpu.cfs_period_us
100000

通过查看container目录下的文件,看到container控制组里的CPU quota还没有任何限制(即-1),CPU period 则是默认的100ms(100000 us)

把被限制的进程的PID写入container组里的tasks文件,上面的设置就会对该进程生效了

1
$ echo 226 > /sys/fs/cgroup/cpu/container/tasks 

在 cgroup v2 中,tasks 文件已被移除,使用的是 cgroup.procs 文件来管理进程。以下是如何在 cgroup v2 中操作的步骤:

  1. 创建 cgroup

    1
    mkdir /sys/fs/cgroup/unified/my_cgroup
  2. 将进程加入 cgroup

    1
    echo 226 > /sys/fs/cgroup/unified/my_cgroup/cgroup.procs