Linux Cgoups提供了对一组进程以及将来子进程的资源限制、控制和统计的能力。资源包括CPU、内存、存储、网络等。

功能

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

    三个组件:Cgroups、subsystem、hierarchy

    subsystem:

  • blkio — 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。

  • cpu — 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
  • cpuacct — 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
  • cpuset — 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
  • devices — 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
  • freezer — 这个子系统挂起或者恢复 cgroup 中的任务。
  • memory — 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成内存资源使用报告。
  • net_cls — 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
  • net_prio — 这个子系统用来设计网络流量的优先级
  • hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。

关系:

  • hierachy是cgroup的树形结构,cgroup是hierachy上的一个个节点
    • cgroup之间可以继承,体现在cgroup节点间的父子关系
  • 一个cgroup包含一组进程,一个进程可以作为多个cgroup的成员,但这些cgroup必须在不同的hierachy中
  • hierachy与subsystem是一对多的关系
  • hierachy下的所有cgroup都会受到hierachy相关联的subsystem的资源限制。

实现

cgroup以文件系统的方式来实现。
mount -t cgroup可以查看所有的hierarchy。

  1. [root@localhost mycontainer]# mount -t cgroup
  2. cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
  3. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
  4. cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
  5. cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
  6. cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)
  7. cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
  8. cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
  9. cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
  10. cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
  11. cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
  12. cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)

下面是创建了一个hierachy,自带一个根节点,然后创建两个子cgroup,然后将当前进程加入到一个子cgroup中。

  1. [root@localhost ~]# mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test/
  2. [root@localhost ~]# ls
  3. anaconda-ks.cfg cgroup-test go go1.11.5.linux-amd64.tar.gz libcgroup-tools-0.41-20.el7.x86_64.rpm tasks x-scripts.log
  4. [root@localhost ~]# cd cgroup-test/
  5. [root@localhost cgroup-test]# ls
  6. cgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
  7. [root@localhost cgroup-test]# mkdir cgroup1
  8. [root@localhost cgroup-test]# mkdir cgroup2
  9. [root@localhost cgroup-test]# tree
  10. .
  11. ├── cgroup1
  12. ├── cgroup.clone_children
  13. ├── cgroup.event_control
  14. ├── cgroup.procs
  15. ├── notify_on_release
  16. └── tasks
  17. ├── cgroup2
  18. ├── cgroup.clone_children
  19. ├── cgroup.event_control
  20. ├── cgroup.procs
  21. ├── notify_on_release
  22. └── tasks
  23. ├── cgroup.clone_children
  24. ├── cgroup.event_control
  25. ├── cgroup.procs
  26. ├── cgroup.sane_behavior
  27. ├── notify_on_release
  28. ├── release_agent
  29. └── tasks
  30. 2 directories, 17 files
  31. [root@localhost cgroup-test]# cd cgroup1
  32. [root@localhost cgroup1]# ls
  33. cgroup.clone_children cgroup.event_control cgroup.procs notify_on_release tasks
  34. [root@localhost cgroup1]# echo $$
  35. 2097
  36. [root@localhost cgroup1]# echo $$ >> tasks
  37. [root@localhost cgroup1]# cat tasks
  38. 2097
  39. 2126
  40. [root@localhost cgroup1]# cat /proc/2097/cgroup
  41. 12:name=cgroup-test:/cgroup1
  42. 11:pids:/
  43. 10:hugetlb:/
  44. 9:cpuset:/
  45. 8:freezer:/
  46. 7:net_prio,net_cls:/
  47. 6:devices:/user.slice
  48. 5:blkio:/
  49. 4:perf_event:/
  50. 3:cpuacct,cpu:/
  51. 2:memory:/
  52. 1:name=systemd:/user.slice/user-0.slice/session-1.scope

我们知道OS支持一系列的subsystem,并且默认已经为每个subsystem创建了一个默认的hierachy。
比如内存hierachy,就挂载在/sys/fs/cgroup/memory。
image.png
我们将试验在这个hierachy中,去限制内存使用。

  1. [root@localhost cgroup1]# mount | grep memory
  2. cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
  3. [root@localhost cgroup1]# cd /sys/fs/cgroup/memory
  4. [root@localhost memory]# stress --vm-bytes 200m --vm-keep -m 1
  5. stress: info: [2177] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
  6. ^C
  7. [root@localhost memory]# ls;
  8. cgroup.clone_children memory.kmem.limit_in_bytes memory.kmem.usage_in_bytes memory.move_charge_at_immigrate memory.usage_in_bytes
  9. cgroup.event_control memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchy
  10. cgroup.procs memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_release
  11. cgroup.sane_behavior memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level release_agent
  12. memory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytes tasks
  13. memory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.stat
  14. memory.kmem.failcnt memory.kmem.tcp.usage_in_bytes memory.memsw.usage_in_bytes memory.swappiness
  15. [root@localhost memory]# mkdir test-limit-memory
  16. [root@localhost memory]# cd test-limit-memory/
  17. [root@localhost test-limit-memory]# ls
  18. cgroup.clone_children memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchy
  19. cgroup.event_control memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_release
  20. cgroup.procs memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level tasks
  21. memory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytes
  22. memory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.stat
  23. memory.kmem.failcnt memory.kmem.tcp.usage_in_bytes memory.memsw.usage_in_bytes memory.swappiness
  24. memory.kmem.limit_in_bytes memory.kmem.usage_in_bytes memory.move_charge_at_immigrate memory.usage_in_bytes
  25. [root@localhost test-limit-memory]# echo "100m" > memory.limit_in_bytes
  26. [root@localhost test-limit-memory]# cat memory.limit_in_bytes
  27. 104857600
  28. [root@localhost test-limit-memory]# echo $$ > tasks
  29. [root@localhost test-limit-memory]# cat tasks
  30. 2097
  31. 2184
  32. [root@localhost test-limit-memory]# echo $$
  33. 2097
  34. [root@localhost test-limit-memory]# stress --vm-bytes 200m --vm-keep -m 1
  35. stress: info: [2185] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
  36. ^C

使用top命令可以看出,在限制内存100M后,stress进程只占用了4GB的2.5%内存。

Docker

docker run -m 128m
此时会在/sys/fs/cgroup/memory/docker/下创建一个cgroup,目录名为容器ID。
然后在/sys/fs/cgroup/memory/docker/container_id/memory.limit_in_bytes中写入限制的内存,比如128m。
并且会在/sys/fs/cgroup/memory/docker/container_id/tasks下写入容器init进程ID。

/proc/self

linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。

代码

其实是将上面的词创建一个内存cgroup的手动操作代码化

  1. const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"
  2. func main() {
  3. // 这里的/proc/self/exe 调用
  4. // 其中/proc/self指的是当前运行进程自己的环境,exec其实就是自己调用了自己,我们使用这种方式实现对创建出来的进程进行初始化
  5. if os.Args[0] == "/proc/self/exe" {
  6. fmt.Printf("current pid %d\n", syscall.Getpid())
  7. cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)
  8. cmd.SysProcAttr = &syscall.SysProcAttr{}
  9. cmd.Stdin = os.Stdin
  10. cmd.Stdout = os.Stdout
  11. cmd.Stderr = os.Stderr
  12. if err := cmd.Run(); err != nil {
  13. log.Fatal(err)
  14. }
  15. }
  16. cmd := exec.Command("/proc/self/exe")
  17. cmd.SysProcAttr = &syscall.SysProcAttr{
  18. Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET,
  19. }
  20. cmd.Stdin = os.Stdin
  21. cmd.Stdout = os.Stdout
  22. cmd.Stderr = os.Stderr
  23. // 异步重新调用main函数,之后可以执行添加cgroup操作,此时必须知道自己的pid
  24. // 在重新调用中来执行真正的command
  25. if err := cmd.Start(); err != nil {
  26. log.Fatal(err)
  27. os.Exit(1)
  28. } else {
  29. fmt.Printf("%v\n", cmd.Process.Pid)
  30. cgroupName := "test-memory-limit-in-go"
  31. os.Mkdir(path.Join(cgroupMemoryHierarchyMount, cgroupName), 0755)
  32. ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, cgroupName, "tasks"),
  33. []byte(strconv.Itoa(cmd.Process.Pid)), 0644)
  34. ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, cgroupName, "memory.limit_in_bytes"), []byte("100m"), 0644)
  35. }
  36. cmd.Process.Wait()
  37. }