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 cgroup
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)
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 ~]# ls
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
[root@localhost ~]# cd cgroup-test/
[root@localhost cgroup-test]# ls
cgroup.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
└── tasks
2 directories, 17 files
[root@localhost cgroup-test]# cd cgroup1
[root@localhost cgroup1]# ls
cgroup.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 tasks
2097
2126
[root@localhost cgroup1]# cat /proc/2097/cgroup
12:name=cgroup-test:/cgroup1
11:pids:/
10:hugetlb:/
9:cpuset:/
8:freezer:/
7:net_prio,net_cls:/
6:devices:/user.slice
5: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 memory
cgroup 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 1
stress: 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_bytes
cgroup.event_control memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchy
cgroup.procs memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_release
cgroup.sane_behavior memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level release_agent
memory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytes tasks
memory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.stat
memory.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]# ls
cgroup.clone_children memory.kmem.max_usage_in_bytes memory.limit_in_bytes memory.numa_stat memory.use_hierarchy
cgroup.event_control memory.kmem.slabinfo memory.max_usage_in_bytes memory.oom_control notify_on_release
cgroup.procs memory.kmem.tcp.failcnt memory.memsw.failcnt memory.pressure_level tasks
memory.failcnt memory.kmem.tcp.limit_in_bytes memory.memsw.limit_in_bytes memory.soft_limit_in_bytes
memory.force_empty memory.kmem.tcp.max_usage_in_bytes memory.memsw.max_usage_in_bytes memory.stat
memory.kmem.failcnt memory.kmem.tcp.usage_in_bytes memory.memsw.usage_in_bytes memory.swappiness
memory.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_bytes
104857600
[root@localhost test-limit-memory]# echo $$ > tasks
[root@localhost test-limit-memory]# cat tasks
2097
2184
[root@localhost test-limit-memory]# echo $$
2097
[root@localhost test-limit-memory]# stress --vm-bytes 200m --vm-keep -m 1
stress: 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.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if 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.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// 异步重新调用main函数,之后可以执行添加cgroup操作,此时必须知道自己的pid
// 在重新调用中来执行真正的command
if 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()
}