Linux Cgoups提供了对一组进程以及将来子进程的资源限制、控制和统计的能力。资源包括CPU、内存、存储、网络等。
功能
- Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
- Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
- Accounting: 一些审计或一些统计,主要目的是为了计费。
-
三个组件: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。
[root@localhost mycontainer]# mount -t cgroupcgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_cls,net_prio)cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpu,cpuacct)
下面是创建了一个hierachy,自带一个根节点,然后创建两个子cgroup,然后将当前进程加入到一个子cgroup中。
[root@localhost ~]# mount -t cgroup -o none,name=cgroup-test cgroup-test ./cgroup-test/[root@localhost ~]# lsanaconda-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[root@localhost ~]# cd cgroup-test/[root@localhost cgroup-test]# lscgroup.clone_children cgroup.event_control cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks[root@localhost cgroup-test]# mkdir cgroup1[root@localhost cgroup-test]# mkdir cgroup2[root@localhost cgroup-test]# tree.├── cgroup1│ ├── cgroup.clone_children│ ├── cgroup.event_control│ ├── cgroup.procs│ ├── notify_on_release│ └── tasks├── cgroup2│ ├── cgroup.clone_children│ ├── cgroup.event_control│ ├── cgroup.procs│ ├── notify_on_release│ └── tasks├── cgroup.clone_children├── cgroup.event_control├── cgroup.procs├── cgroup.sane_behavior├── notify_on_release├── release_agent└── tasks2 directories, 17 files[root@localhost cgroup-test]# cd cgroup1[root@localhost cgroup1]# lscgroup.clone_children cgroup.event_control cgroup.procs notify_on_release tasks[root@localhost cgroup1]# echo $$2097[root@localhost cgroup1]# echo $$ >> tasks[root@localhost cgroup1]# cat tasks20972126[root@localhost cgroup1]# cat /proc/2097/cgroup12:name=cgroup-test:/cgroup111:pids:/10:hugetlb:/9:cpuset:/8:freezer:/7:net_prio,net_cls:/6:devices:/user.slice5:blkio:/4:perf_event:/3:cpuacct,cpu:/2:memory:/1:name=systemd:/user.slice/user-0.slice/session-1.scope
我们知道OS支持一系列的subsystem,并且默认已经为每个subsystem创建了一个默认的hierachy。
比如内存hierachy,就挂载在/sys/fs/cgroup/memory。
我们将试验在这个hierachy中,去限制内存使用。
[root@localhost cgroup1]# mount | grep memorycgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)[root@localhost cgroup1]# cd /sys/fs/cgroup/memory[root@localhost memory]# stress --vm-bytes 200m --vm-keep -m 1stress: info: [2177] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd^C[root@localhost memory]# ls;cgroup.clone_children memory.kmem.limit_in_bytes memory.kmem.usage_in_bytes memory.move_charge_at_immigrate memory.usage_in_bytescgroup.event_control memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchycgroup.procs memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_releasecgroup.sane_behavior memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level release_agentmemory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytes tasksmemory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.statmemory.kmem.failcnt memory.kmem.tcp.usage_in_bytes memory.memsw.usage_in_bytes memory.swappiness[root@localhost memory]# mkdir test-limit-memory[root@localhost memory]# cd test-limit-memory/[root@localhost test-limit-memory]# lscgroup.clone_children memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchycgroup.event_control memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_releasecgroup.procs memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level tasksmemory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytesmemory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.statmemory.kmem.failcnt memory.kmem.tcp.usage_in_bytes memory.memsw.usage_in_bytes memory.swappinessmemory.kmem.limit_in_bytes memory.kmem.usage_in_bytes memory.move_charge_at_immigrate memory.usage_in_bytes[root@localhost test-limit-memory]# echo "100m" > memory.limit_in_bytes[root@localhost test-limit-memory]# cat memory.limit_in_bytes104857600[root@localhost test-limit-memory]# echo $$ > tasks[root@localhost test-limit-memory]# cat tasks20972184[root@localhost test-limit-memory]# echo $$2097[root@localhost test-limit-memory]# stress --vm-bytes 200m --vm-keep -m 1stress: info: [2185] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd^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的手动操作代码化
const cgroupMemoryHierarchyMount = "/sys/fs/cgroup/memory"func main() {// 这里的/proc/self/exe 调用// 其中/proc/self指的是当前运行进程自己的环境,exec其实就是自己调用了自己,我们使用这种方式实现对创建出来的进程进行初始化if os.Args[0] == "/proc/self/exe" {fmt.Printf("current pid %d\n", syscall.Getpid())cmd := exec.Command("sh", "-c", `stress --vm-bytes 200m --vm-keep -m 1`)cmd.SysProcAttr = &syscall.SysProcAttr{}cmd.Stdin = os.Stdincmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Fatal(err)}}cmd := exec.Command("/proc/self/exe")cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET,}cmd.Stdin = os.Stdincmd.Stdout = os.Stdoutcmd.Stderr = os.Stderr// 异步重新调用main函数,之后可以执行添加cgroup操作,此时必须知道自己的pid// 在重新调用中来执行真正的commandif err := cmd.Start(); err != nil {log.Fatal(err)os.Exit(1)} else {fmt.Printf("%v\n", cmd.Process.Pid)cgroupName := "test-memory-limit-in-go"os.Mkdir(path.Join(cgroupMemoryHierarchyMount, cgroupName), 0755)ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, cgroupName, "tasks"),[]byte(strconv.Itoa(cmd.Process.Pid)), 0644)ioutil.WriteFile(path.Join(cgroupMemoryHierarchyMount, cgroupName, "memory.limit_in_bytes"), []byte("100m"), 0644)}cmd.Process.Wait()}
