root@ecs-295280:~# mount | grep cgroup tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755) cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) 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/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb) cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma) root@ecs-295280:~#一般默认的挂载目录是在/sys/fs/cgroup 目录下,系统内核在开机时,会默认挂载cgroup目录。这样便能通过访问文件的方式对cgroup功能进行使用。
root@ecs-295280:~# ls /sys/fs/cgroup/ blkio cpu,cpuacct freezer net_cls perf_event systemd cpu cpuset hugetlb net_cls,net_prio pids unified cpuacct devices memory net_prio rdma拿cpu这个目录下的文件举例
root@ecs-295280:/sys/fs/cgroup/cpu# ls cgroup.clone_children cpuacct.usage_percpu_sys cpu.stat cgroup.procs cpuacct.usage_percpu_user ebpf-agent cgroup.sane_behavior cpuacct.usage_sys hostguard cpuacct.stat cpuacct.usage_user notify_on_release cpuacct.usage cpu.cfs_period_us release_agent cpuacct.usage_all cpu.cfs_quota_us tasks cpuacct.usage_percpu cpu.shares root@ecs-295280:/sys/fs/cgroup/cpu# ll -l在cpu子系统这个目录下,有两个文件cgroup.procs,tasks文件,它们都是用来管理cgroup中的进程。但是,它们的使用方式略有不同:
# 堆代码 duidaima.com root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_period_us 100000 root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_quota_us -1默认情况下,cpu.cfs_period_us是100000,单位是微秒,cpu.cfs_period_us代表了cpu运行一个周期的时长,100000代表了100ms,cpu.cfs_quota_us代表进程所占用的周期时长,-1代表不限制进程使用cpu周期时长,如果cpu.cfs_quota_us是50000(50ms)则代表在cpu一个调度周期内,该cgroup下的进程最多只能运行半个周期,如果达到了运行周期的限制,那么它必须等待下一个时间片才能继续运行了。
root@ecs-295280:/sys/fs/cgroup/cpu/test# pwd /sys/fs/cgroup/cpu/test设置cpu.cfs_quota_us为一个时间片的一半,设置tasks,把当前进程加入到cgroup中
root@ecs-295280:/sys/fs/cgroup/cpu/test# cat cpu.cfs_quota_us 50000 root@ecs-295280:/sys/fs/cgroup/cpu/test# sh -c "echo $$ > tasks" root@ecs-295280:/sys/fs/cgroup/cpu/test# cat tasks 65961 66314在当前shell 界面,通过stress对cpu进行压力测试。我的虚拟机是一个核,我这里直接通过stress对这一个cpu核进行压测。
root@ecs-295280:/sys/fs/cgroup/cpu/test# stress --cpu 1 --timeout 60启动另一个终端,查看cpu占用情况
Tasks: 94 total, 2 running, 92 sleeping, 0 stopped, 0 zombie %Cpu(s): 51.9 us, 0.0 sy, 0.0 ni, 48.1 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 1982.9 total, 451.3 free, 193.4 used, 1338.2 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 1597.9 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 66333 root 20 0 3856 100 0 R 50.3 0.0 0:06.00 stress 1 root 20 0 102780 12420 8236 S 0.0 0.6 0:05.93 systemd可以看到的是cpu占用率在达到百分之50时就不上去了,这正是由于stress进程是bash进程的子进程,继承了bash进程的cgroup,所以cpu使用率受到了限制。
root@ecs-295280:/sys/fs/cgroup/memory# mkdir test root@ecs-295280:/sys/fs/cgroup/memory# cd test/ root@ecs-295280:/sys/fs/cgroup/memory/test# pwd /sys/fs/cgroup/memory/test然后把当前进程加进去
root@ecs-295280:/sys/fs/cgroup/memory/test# sh -c "echo $$ > tasks" root@ecs-295280:/sys/fs/cgroup/memory/test# cat tasks 65961 66476设置最大使用内存,memory目录下限制最大使用内存需要设置memory.limit_in_bytes 这个文件,默认情况下,它是一个大的离谱的值,我们将它改为100M
root@ecs-295280:/sys/fs/cgroup/memory/test# cat memory.limit_in_bytes 9223372036854771712 root@ecs-295280:/sys/fs/cgroup/memory/test# vim memory.limit_in_bytes root@ecs-295280:/sys/fs/cgroup/memory/test# cat memory.limit_in_bytes 104857600这个时候通过stress 对内存进行压力测试,我们限制了100M,但是如果stress要求分配200M内存,看看能正常分配吗?
root@ecs-295280:/sys/fs/cgroup/memory/test# stress --vm-bytes 200m --vm-keep -m 1 stress: info: [66533] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd stress: FAIL: [66533] (415) <-- worker 66534 got signal 9 stress: WARN: [66533] (417) now reaping child worker processes stress: FAIL: [66533] (451) failed run completed in 0s
可以看到的是,程序崩溃了,原因则是由于发生了oom,因为内存已经被我们限制到了100M,通过test目录下的memory.oom_control文件可以看到发生oom的次数。
// 堆代码 duidaima.com oom_kill_disable 0 under_oom 0 oom_kill 1oom_kill 为1代表发生oom后,进程被kill掉的次数。在简单看完cgroup如何对cpu和内存进行限制以后,看看golang代码如何实现。
cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err = cmd.Start() if err != nil { fmt.Println(err) } containerName := os.Args[2] if err := cgroups.ConfigDefaultCgroups(cmd.Process.Pid, containerName); err != nil { log.Error("config cgroups fail %s", err) } if err := network.ConfigDefaultNetworkInNewNet(cmd.Process.Pid); err != nil { log.Error("config network fail %s", err) } cmd.Wait() cgroups.CleanCgroupsPath(containerName)在前面代码的基础上,启动子进程后,父进程把子进程pid添加到一个新的cgroup中,cgroups.ConfigDefaultCgroups方法用于实现对cgroup的控制,以容器名作为cgroup子系统的目录,然后当子进程容器执行完毕后,通过cgroups.CleanCgroupsPath去对cgroup相关目录进行清理。
func CleanCgroupsPath(containerName string) error { output, err: = exec.Command("cgdelete", "-r", fmt.Sprintf("memory:%s/%s", dockerName, containerName)).Output() if err != nil { log.Error("cgdelete fail err=%s output=%s", err, string(output)) } output, err = exec.Command("cgdelete", "-r", fmt.Sprintf("cpu:%s/%s", dockerName, containerName)).Output() if err != nil { log.Error("cgdelete fail err=%s output=%s", err, string(output)) } return nil }清理cgroup的方式我用了cgdelete 命令 删除掉容器cgroup的配置,直接remove删除会出现删除失败情况。