Linux Cgroups
Linux Cgroups 是 Linux 内核中用来为进程设置资源限制的一个重要功能. Cgroups将进程进行分组, 然后对这一组进程进行统一的资源监控和限制。Cgroups当前有V1和V2版本,为了后续用于实现简单容器sdocker,这里只验证V1版本的cpu和memory子系统。
Linux可以通过如下命令来查看当前系统支持的cgroup子系统:
1 linux: # cat /proc/cgroups 2 #subsys_name hierarchy num_cgroups enabled 3 cpuset 11 1 1 4 cpu 2 78 1 5 cpuacct 2 78 1 6 blkio 3 78 1 7 memory 9 79 1 8 devices 4 78 1 9 freezer 8 1 1 10 net_cls 7 78 1 11 perf_event 5 1 1 12 net_prio 7 78 1 13 hugetlb 6 1 1 14 pids 10 86 1 15 linux: #
有的系统(debian8/suse12), cgroup.memory没有启用, 这时可能会影响到下面几个方面:
1. 在/sys/fs/cgroup/memory下建立目录失败, 提示readonly; 2. docker info里面也会有提示信息; 3. 使用kubeadm安装kubernetes时会提示错误;
解决办法, 在/etc/default/grub文件中增加如下选项(debian使用update_grub, suse使用grub2-mkconfig, 然后reboot):
1 linux: # cat /etc/default/grub | grep cgroup_enable 2 GRUB_CMDLINE_LINUX="cgroup_enable=memory" 3 linux: #
Cgroup.CPU
对于Cgroup.CPU,限制cpu利用率主要通过修改下面两个文件来实现:
1 /sys/fs/cgroup/cpu/cpu.cfs_quota_us 2 /sys/fs/cgroup/cpu/cpu.cfs_period_us
把cpu.cfs_quota_us / cpu.cfs_period_us(默认100000)的值作为可以使用的CPU的百分比。使用方法举例如下(摘录自附录网页):
1 Examples 2 -------- 3 1. Limit a group to 1 CPU worth of runtime. 4 5 If period is 250ms and quota is also 250ms, the group will get 6 1 CPU worth of runtime every 250ms. 7 8 # echo 250000 > cpu.cfs_quota_us /* quota = 250ms */ 9 # echo 250000 > cpu.cfs_period_us /* period = 250ms */ 10 11 2. Limit a group to 2 CPUs worth of runtime on a multi-CPU machine. 12 13 With 500ms period and 1000ms quota, the group can get 2 CPUs worth of 14 runtime every 500ms. 15 16 # echo 1000000 > cpu.cfs_quota_us /* quota = 1000ms */ 17 # echo 500000 > cpu.cfs_period_us /* period = 500ms */ 18 19 The larger period here allows for increased burst capacity. 20 21 3. Limit a group to 20% of 1 CPU. 22 23 With 50ms period, 10ms quota will be equivalent to 20% of 1 CPU. 24 25 # echo 10000 > cpu.cfs_quota_us /* quota = 10ms */ 26 # echo 50000 > cpu.cfs_period_us /* period = 50ms */ 27 28 By using a small period here we are ensuring a consistent latency 29 response at the expense of burst capacity.
针对Cgroup.CPU进行测试,对于如下的cpu密集型程序, 启动后从top中可以看到cpu占用100%:
1 linux: # cat cpu.c 2 int main(void) { 3 for (; ;); 4 5 return 0; 6 } 7 linux: #
1 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2 4033 root 20 0 4052 708 632 R 100.00 0.002 1:33.02 cpu
通过给cpu.cfs_quota_us赋值20000,同时把程序pid赋值给tasks文件,让程序只能使用1/5的cpu。
1 linux: # mkdir /sys/fs/cgroup/cpu/sdocker 2 linux: # mkdir /sys/fs/cgroup/cpu/sdocker/4033 3 linux: # echo 20000 > /sys/fs/cgroup/cpu/sdocker/4033/cpu.cfs_quota_us 4 linux: # echo 4033 > /sys/fs/cgroup/cpu/sdocker/4033/tasks
设置后立即生效,top可以看到进程cpu占用率在20%左右波动:
1 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2 4033 root 20 0 4052 708 632 R 20.202 0.002 1:57.80 cpu
退出程序并清理cgroup资源:
1 linux: # kill -9 4033 2 linux: # rmdir /sys/fs/cgroup/cpu/sdocker/4033/
Cgroup.Memory
/sys/fs/cgroup/memory下定义了Cgroup.Memory子系统的相关文件, 各文件含义如下:
1 cgroup.event_control #用于eventfd的接口 2 memory.usage_in_bytes #显示当前已用的内存字节数 3 memory.limit_in_bytes #设置/显示当前限制的内存额度, 当usage_in_bytes超限时, 如果memory.swappiness配置可使用swap, kernel会优先把内存数据转移到swap空间, 最后若转移swap失败, 则根据memory.oom_control判断是否触发oom 4 memory.failcnt #显示内存使用量达到限制值的次数, 当usage_in_bytes超限时, 会触发该值增加 5 memory.max_usage_in_bytes #历史内存最大使用量 6 memory.soft_limit_in_bytes #设置/显示当前限制的内存软额度 7 memory.stat #显示当前cgroup的内存使用情况 8 memory.use_hierarchy #设置/显示是否将子cgroup的内存使用情况统计到当前cgroup里面 9 memory.force_empty #触发系统立即尽可能的回收当前cgroup中可以回收的内存 10 memory.pressure_level #设置内存压力的通知事件,配合cgroup.event_control一起使用 11 memory.swappiness #设置和显示当前的swappiness 12 memory.move_charge_at_immigrate #设置当进程移动到其他cgroup中时,它所占用的内存是否也随着移动过去 13 memory.oom_control #设置/显示oom controls相关的配置, 默认0启用 14 memory.numa_stat #显示numa相关的内存
针对Cgroup.Memory进行测试,如下的测试代码通过不断分配内存来触发内存限制功能:
1 linux: # cat memory.cpp 2 #include <unistd.h> 3 4 #include <csignal> 5 #include <cstdlib> 6 #include <cstdio> 7 #include <cstring> 8 #include <vector> 9 using std::vector; 10 11 vector<int *> g_mem_pointer; 12 13 void sig_handler(int sig) { 14 printf(" %d handle ", sig); 15 for (auto p : g_mem_pointer) { 16 free(p); 17 } 18 19 exit(-1); 20 } 21 22 int main(void) { 23 unsigned total_mem = 0, chunk_size = 1024 * 1024; 24 25 signal(SIGTERM, sig_handler); 26 signal(SIGINT, sig_handler); 27 28 int *p; 29 while (1) { 30 if (NULL == (p = (int *)malloc(chunk_size))) { 31 printf("[-] malloc failed! "); 32 kill(getpid(), 15); 33 } 34 35 memset(p, 0xff, chunk_size); 36 g_mem_pointer.push_back(p); 37 total_mem += chunk_size; 38 printf("[+] malloc size: %u ", total_mem); 39 sleep(10); 40 } 41 42 return 0; 43 } 44 linux: #
设置内存限制6m到memory.limit_in_bytes,同时把进程pid设置到tasks文件, 一段时间后可以看到进程oom-kill.
测试发现进程实际打印分配的总内存远远大于设置的内存上限时, memory.usage_in_bytes中的数值才会慢慢趋近于memory.limit_in_bytes,即使设置memory.swappiness为0也如此;
1 linux:~ # mkdir /sys/fs/cgroup/memory/sdocker 2 linux:~ # mkdir /sys/fs/cgroup/memory/sdocker/5239 3 linux:~ # echo 6m > /sys/fs/cgroup/memory/sdocker/5239/memory.limit_in_bytes 4 linux:~ # cat /sys/fs/cgroup/memory/sdocker/5239/memory.limit_in_bytes 5 32768 6 linux:~ # echo 5239 > /sys/fs/cgroup/memory/sdocker/5239/tasks 7 linux:~ # rmdir /sys/fs/cgroup/memory/sdocker/5239
参考网址:
1 https://segmentfault.com/u/wuyangchun 2 https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt 3 https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt