文章导读
本文记录学习cgroup的一些历程,由浅入深,从面到点的介绍了cgroup的相关知识,并以kubernetes使用cpu,memory cgroup为切入点进行分析,破除对于cgroup技术的恐惧心理,让cgroup能够为我所用。本文包含以下内容 :
为什么需要cgroup
cgroup的使用方法
cgroup的子系统介绍
cgroup v1的局限性
cgroup v2
查看cgroup的一些方法
systemd的slice,sope,unit介绍
kubelet使用cgroup的一些坑
cgroup的需求来源
计算机的硬件资源是有限的,如cpu,memory,io,network等,然而软件对于资源的需求是贪婪的。按照我的理解,cgroup就是来实现优先保证高优先级应用的资源需求,并确保应用对于资源的使用不会超量。所以cgroup就是限制进程的资源使用,并对资源的使用情况做统计。
说出来一些具体的事项,才能更好的理解cgroup。
DPDK绑定cpu,提高程序的运行效率,这就可以用到cpuset。
之前面试时,面试官问到监控工具对于系统有没有什么影响,如执行ansible,top,pidstat,vmstat等等工具。现在就有一些思路了,可以使用cgroup来限制这些监控程序的资源使用,来保证系统的稳定性。
一个节点上多个pod,共享cpu资源,就可以用cgroup来限制pod的cpu使用情况,设置某个pod最多可以用到5C等。
付费用户可以使用更多内存资源(资源统计),就可以使用cgroup 的memory来限制普通用户的资源使用。
某些程序会有bug,有内存泄露,也可以利用cgroup的memory来限制资源使用,不会对系统造成太大的影响。
以上都是强行编造....
cgroup子系统分类
以5.4的内核为例,cgroup包括cpu,memory,blokio,network...,这里的每一个文件夹都表示cgroup的一个子系统,这里只以cpu,memory为例进行介绍。
root@iZt4n1u8u50jg1r5n6myn2Z:~# mount -t tmpfs |grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
root@iZt4n1u8u50jg1r5n6myn2Z:~# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
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/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/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
root@iZt4n1u8u50jg1r5n6myn2Z:~# ls /sys/fs/cgroup/
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids rdma systemd unified
root@iZt4n1u8u50jg1r5n6myn2Z:~# uname -a
Linux iZt4n1u8u50jg1r5n6myn2Z 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
root@iZt4n1u8u50jg1r5n6myn2Z:~#
这里请注意一个细节,这里有三层挂载,一个是sysfs,挂载点是/sys/,一个是tmpfs内存文件系统,挂载点是/sys/fs/cgroup,另一个是子系统的挂载,如cpu挂载点是/sys/fs/cgroup/cpu。三层挂载就是三个文件系统,一个是kernfs,一个是tmpfs,一个是cgroup文件系统。即使是基于内存的文件系统,也都有superblock,dentry,inode等这些vfs的概念。
cgroup的使用
常规使用
1、创建cgroup子系统的子目录
2、设置资源配额
3、将需要限制的进程号写入子目录
以cpu限额为例,限制当前shell最多使用1C。这里有个知识点,tasks和cgroup.procs有什么区别呢?按照官方文档的描述,将pid写入cgroup.procs,则该pid所在的线程组及该pid的子进程等都会自动加入到cgroup中。将pid写入tasks,则只限制该pid。
root@iZt4n1u8u50jg1r5n6myn2Z:~# mkdir /sys/fs/cgroup/cpu/myshell -p
root@iZt4n1u8u50jg1r5n6myn2Z:~# echo 100000 > /sys/fs/cgroup/cpu/myshell/cpu.cfs_quota_us
root@iZt4n1u8u50jg1r5n6myn2Z:~# echo $$ > /sys/fs/cgroup/cpu/myshell/cgroup.procs
cg工具集
cgcreate创建,cgdelete删除,cgget查询,cgset设置,cgexec执行等
root@iZt4n1u8u50jg1r5n6myn2Z:~# cgcreate -g cpu:mycg
root@iZt4n1u8u50jg1r5n6myn2Z:~# cgset -r cpu.cfs.cfs_quota_us=10000 /mycg
root@iZt4n1u8u50jg1r5n6myn2Z:~# cgexec -g cpu:mycg df -h -t ext4
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 40G 29G 9.0G 77% /
root@iZt4n1u8u50jg1r5n6myn2Z:~# cgdelete -g cpu:mycg
systemd
通过systemd的接口设置服务的配额,与上诉两种方式的使用原理是一样的。systemcg-top的展示也不错。
root@iZt4n1u8u50jg1r5n6myn2Z:~# systemctl set-property sshd.service CPUShares=2048
root@iZt4n1u8u50jg1r5n6myn2Z:~# cat /sys/fs/cgroup/cpu/system.slice/ssh.service/cpu.shares
2048
root@iZt4n1u8u50jg1r5n6myn2Z:~# systemd-cgtop
Control Group Tasks %CPU Memory Input/s Output/s
/ 206 - 841.2M - -
assist - - 3.1M - -
docker 1 - 11.7M - -
docker/cbf77eadcd1bd5b4810eceeee1faa643a7d05bce9ca45d60f01813d15251c135 1 - 11.7M - -
system.slice 135 - 584.0M - -
system.slice/AssistDaemon.service 8 - 2.6M - -
system.slice/accounts-daemon.service 3 - 3.0M - -
system.slice/aegis.service 28 - 130.3M - -
system.slice/aliyun.service 9 - 15.0M - -
system.slice/atd.service 1 - 516.0K - -
system.slice/chrony.service 2 - 1.8M - -
kubelet使用systemd的cgroup驱动目录层级
这里kubelet使用systemd是指kubelet调用systemd的cgroup接口去管理cgroup,systemd管理cgroup与上面的几种cgroup的方法本质上是一致的
这里有三个slice。
kubepods.slice 所有pod的资源配额,下面还有pod级别的配额,容器级别的配额等。
system.slice systemd管理的服务的资源配额
user.slice ?
[root@10 ~]# ls /sys/fs/cgroup/cpu
cgroup.clone_children cpuacct.stat cpuacct.usage_percpu cpuacct.usage_sys cpu.cfs_quota_us cpu.shares notify_on_release tasks
cgroup.procs cpuacct.usage cpuacct.usage_percpu_sys cpuacct.usage_user cpu.rt_period_us cpu.stat release_agent user.slice
cgroup.sane_behavior cpuacct.usage_all cpuacct.usage_percpu_user cpu.cfs_period_us cpu.rt_runtime_us kubepods.slice system.slice
# pod级别配额
[root@10 ~]# ls -l /sys/fs/cgroup/cpu/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod41c19abf_8aaa_4b4f_9b4a_512194662502.slice/
total 0
-rw-r--r--. 1 root root 0 Nov 28 11:16 cgroup.clone_children
-rw-r--r--. 1 root root 0 Nov 28 11:16 cgroup.procs
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.stat
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_all
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_percpu
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_percpu_sys
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_percpu_user
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_sys
-r--r--r--. 1 root root 0 Nov 28 11:16 cpuacct.usage_user
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Nov 28 11:16 cpu.shares
-r--r--r--. 1 root root 0 Nov 28 11:16 cpu.stat
drwxr-xr-x. 2 root root 0 Nov 28 11:18 docker-095cb69032f07a3848e130014b2b574fda73818261ca3074065e3ae53b8f564d.scope
drwxr-xr-x. 2 root root 0 Nov 28 11:18 docker-f791c73f1fb7841079ea2ad89cee7c10f2926f6410f1ab2a01fbf8a65d9d1068.scope
# 容器级别配额
[root@10 ~]# ls -l /sys/fs/cgroup/cpu/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod41c19abf_8aaa_4b4f_9b4a_512194662502.slice/docker-095cb69032f07a3848e130014b2b574fda73818261ca3074065e3ae53b8f564d.scope/
total 0
-rw-r--r--. 1 root root 0 Nov 28 11:18 cgroup.clone_children
-rw-r--r--. 1 root root 0 Nov 28 11:18 cgroup.procs
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.stat
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_all
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_percpu
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_percpu_sys
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_percpu_user
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_sys
-r--r--r--. 1 root root 0 Nov 28 11:18 cpuacct.usage_user
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpu.cfs_period_us
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Nov 28 11:18 cpu.shares
-r--r--r--. 1 root root 0 Nov 28 11:18 cpu.stat
-rw-r--r--. 1 root root 0 Nov 28 11:18 notify_on_release
-rw-r--r--. 1 root root 0 Nov 28 11:18 tasks
查看cgroup个数
/proc/cgroups可以查看当前系统挂载了多少子系统,每个子系统的cgroup个数。
其中hierarchy一列是按照mount的顺序生成的一个编号。num_cgroups表示该子系统下有多少个cgroup目录,可以使用find命令查看。
内核函数入口 :proc_cgroupstats_show
[root@10 ~]# cat /proc/cgroups |column -t
#subsys_name hierarchy num_cgroups enabled
cpuset 7 308 1
cpu 4 310 1
cpuacct 4 310 1
blkio 5 310 1
memory 10 314 1
devices 6 310 1
freezer 13 308 1
net_cls 2 308 1
perf_event 12 308 1
net_prio 2 308 1
hugetlb 3 308 1
pids 8 310 1
rdma 11 1 1
misc 9 1 1
// 与上面/proc/cgroups的输出是一致的。
[root@10 ~]# find /sys/fs/cgroup/cpu/ -type d | wc -l
310
[root@10 ~]# find /sys/fs/cgroup/blkio/ -type d | wc -l
310
查看进程的所有cgroup信息
内核函数入口 :proc_cgroup_show
root@iZt4n1u8u50jg1r5n6myn2Z:~# cat /proc/38538/cgroup
12:net_cls,net_prio:/
11:devices:/system.slice/ssh.service
10:hugetlb:/
9:rdma:/
8:memory:/system.slice/ssh.service
7:pids:/system.slice/ssh.service
6:blkio:/system.slice/ssh.service
5:cpuset:/
4:freezer:/
3:cpu,cpuacct:/system.slice/ssh.service
2:perf_event:/
1:name=systemd:/system.slice/ssh.service
0::/system.slice/ssh.service
kubelet使用cgroup的坑
宿主机上除了跑pod的应用,还有其他系统组件,如sshd,kubelet,docker,containerd等服务,且系统组件的优先级应该比pod应用更高,所以可以通过kubelet的资源预留功能为系统组件预留一部分的cpu和memory资源。既可以为system预留资源,也可以单独为kubelet应用预留资源,这里只以system预留为例说明。
systemReserved 配置为系统组件的预留资源,cpu和memory
systemReservedCgroup 配置预留资源的cgroup路径
enforceNodeAllocatable 这里要配置上pods,systemReserved,高版本的kubernetes,如果这里不加上pods这个配置项,将导致预留项不生效。
验证cgroup预留资源是否生效的方法也很简单,以memory为例,kubepods的memorylimit + system的memorylimt = 宿主机的memory即表明预留资源生效。
cgroupv2
众所周知,cgroupv1无法限制bufferio的写入。下面将演示如何启用cgroupv2以及使用cgroupv2限制bufferio的写入。从使用体验上,cgroupv2比cgroupv1更简单。
关闭cgroupv1的内核入口函数 __setup("cgroup_no_v1=", cgroup_no_v1);
// 关闭cgroupv1
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# cat /boot/grub/grub.cfg | grep cgroup
linux /boot/vmlinuz-5.4.0-91-generic root=UUID=12e5697d-887b-4790-a160-75cf814081a6 ro vga=792 console=tty0 console=ttyS0,115200n8 net.ifnames=0 noibrs quiet cgroup_no_v1=all
// 创建cgroup
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# mkdir /sys/fs/cgroup/unified/iotest
// 使能io + memory
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# echo "+io +memory" > /sys/fs/cgroup/unified/cgroup.subtree_control
// 将当前进程挪到iotest的cgroup中
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# echo $$ >/sys/fs/cgroup/unified/iotest/cgroup.procs
// 设置最大写入速度10M/s
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# echo "252:0 wbps=10485760" > /sys/fs/cgroup/unified/iotest/io.max
root@iZt4n1u8u50jg1r5n6myn2Z:~/lugl# fio -iodepth=1 -rw=write -ioengine=libaio -bs=4k -size=300M -numjobs=1 -name=./fio.test
./fio.test: (g=0): rw=write, bs=(R) 4096B-4096B, (W) 4096B-4096B, (T) 4096B-4096B, ioengine=libaio, iodepth=1
fio-3.16
Starting 1 process
Jobs: 1 (f=1): [W(1)][47.8%][w=11.4MiB/s][w=2928 IOPS][eta 00m:12s]
