docker cgroup技术之cpu和cpuset
在centos7的/sys/fs/cgroup下面可以看到与cpu相关的有cpu,cpuacct和cpuset 3个subsystem。cpu用于对cpu使用率的划分;cpuset用于设置cpu的亲和性等,主要用于numa架构的os;cpuacct记录了cpu的部分信息。对cpu资源的设置可以从2个维度考察:cpu使用百分比和cpu核数目。前者使用cpu subsystem进行配置,后者使用cpuset subsystem进程配置。首先看cpu subsystem的用法
cpu subsystem
cgroup使用如下2种方式来对cpu进行调度
- 完全公平调度程序(CFS):按照比例进行cpu分配调度,具体实现可以参考CFS
- 实时调度程序(RT):与CFS类似,用于限制实时任务对cpu的获取,一般用不到。(注:Linux的进程分普通进程和实时进程,实时进程比普通进程的优先级高,由于其在进程死亡之前始终是活动进程,故占用cpu资源大)
cpu subsystem主要涉及5接口:cpu.cfs_period_us,cpu.cfs_quota_us,cpu.shares,cpu.rt_period_us,cpu.rt_runtime_us
cfs_quota_us为-1,表示使用的CPU不受cgroup限制。cfs_quota_us的最小值为1ms(1000),最大值为1s,参见CFS Bandwidth Control
cpu.cfs_period_us用于设置cpu带宽(bandwidth),单位为微秒us。cpu.cfs_quota_us设置cpu.cfs_period_us周期内cgroup可使用的cpu。多核场景下,如配置cpu.cfs_period_us=10000,而cfs_quota_us=20000,表示该cgroup可以完全使用2个cpu。较大的cfs_period_us可以提高吞吐量(可以为CPU密集型任务提供更多运行时间)。cfs_period_us表示一个CPU的宽度,系统上可用的总的CPU宽度为:(cpus on the host) * (cpu.cfs_period_us)。当出现如下条件时,cpu.stat中的nr_throttled统计会+1。
- 周期(period)内使用的CPU达到quota的CPU
- 父cgroup中使用的CPU达到其quota的CPU
首先在/sys/fs/cgroup/cpu下面新建一个cgroup,将cpu周期设置为100000,cgroup在单个周期中占用时长为50000,即单个cpu的50%
# cat cpu.cfs_period_us 100000 # echo 50000 > cpu.cfs_quota_us # bash # cat tasks # echo $$ 40768 # echo $$ > cgroup.procs
# while true; do a=a+1;done
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 40768 root 20 0 116872 3644 1808 R 50.3 0.4 0:19.75 bash
下例中将cpu周期设置为100000,cgroup在单个周期中占用时长为300000,即该cgroup可以完全占用3个cpu(当前环境4 cpu)。
启动一个bash执行while true; do a=a+1;done并将该进程加入到cgroup.procs,使用top命令可以看到1个cpu使用率已经达到100%
top - 13:20:06 up 19:24, 7 users, load average: 3.21, 2.03, 0.95 Tasks: 252 total, 3 running, 249 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.0 us, 0.3 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75412 free, 547276 used, 373208 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138856 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41416 root 20 0 116872 3644 1808 R 99.7 0.4 5:41.19 bash
(新shell中)再启动一个bash执行while true; do a=a+1;done,将该进程加入到cgroup.procs,使用top命令可以看到2个cpu使用率已经达到100%
top - 13:22:51 up 19:27, 7 users, load average: 1.42, 1.65, 0.98 Tasks: 252 total, 3 running, 249 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.3 us, 0.0 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75124 free, 547544 used, 373228 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138252 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41416 root 20 0 116872 3644 1808 R 100.0 0.4 8:26.67 bash 41528 root 20 0 116872 3652 1808 R 100.0 0.4 4:22.60 bash
(新shell中)再启动一个bash执行while true; do a=a+1;done,将该进程加入到cgroup.procs,使用top命令可以看到3个cpu使用率已经达到100%
top - 13:25:58 up 19:30, 7 users, load average: 2.28, 1.88, 1.18 Tasks: 251 total, 4 running, 247 sleeping, 0 stopped, 0 zombie %Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75412 free, 547584 used, 372900 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138228 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41528 root 20 0 116872 3652 1808 R 100.0 0.4 7:29.10 bash 41416 root 20 0 116872 3644 1808 R 99.7 0.4 11:33.12 bash 41593 root 20 0 116784 3368 1648 R 99.7 0.3 2:30.04 bash
(新shell中)再启动一个bash执行while true; do a=a+1;done,将该进程加入到cgroup.procs,此时有4个进程同时消耗cpu,但总体消耗限制在3个cpu,如下图中,每个bash消耗的cpu约75%
top - 13:26:49 up 19:31, 7 users, load average: 2.95, 2.12, 1.30 Tasks: 251 total, 5 running, 246 sleeping, 0 stopped, 0 zombie %Cpu0 : 74.8 us, 0.0 sy, 0.0 ni, 25.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 74.8 us, 0.0 sy, 0.0 ni, 25.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 : 74.2 us, 0.0 sy, 0.0 ni, 25.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 75.7 us, 0.0 sy, 0.0 ni, 24.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 75536 free, 547460 used, 372900 buff/cache KiB Swap: 2097148 total, 1651216 free, 445932 used. 138352 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 41593 root 20 0 116784 3368 1648 R 75.7 0.3 3:16.76 bash 41528 root 20 0 116872 3652 1808 R 74.8 0.4 8:15.67 bash 41416 root 20 0 116872 3644 1808 R 74.1 0.4 12:19.74 bash 41654 root 20 0 116784 3368 1648 R 74.1 0.3 1:41.05 bash
cpu.cfs_quota_us和cpu.cfs_period_us以绝对比例限制cgroup的cpu,而cpu.shares以相对比例限制cgroup的cpu。
在/sys/fs/cgroup/cpu/下创建2个cgroup:test1和test2,设置test1的cpu.shares=50,test2的cpu.shares=200,则意味着test1在cpu竞争下最多可以使用所有cpu的20%(50/(50+200)*100%=20%),而test2在cpu竞争下最多可以使用所有cpu的80%(不考虑系统基本进程占用)。为方便验证,将系统的cpu设置为1个。创建2个bash进程分别加入2个cgroup后执行while true; do a=a+1;done
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9683 root 20 0 116872 3644 1808 R 80.0 0.4 0:16.77 bash 9629 root 20 0 116872 3644 1808 R 20.0 0.4 0:09.73 bash
使用cpu.shares需要注意的是,对cpu的相对比例是在cpu竞争的条件下,如果一个cgroup使用的相对比例是50%,但实际仅使用了10%,那么多余的cpu会被回收,给其他cgroup使用,参见CPU
当一个 cgroup 中的任务处于闲置状态且不使用任何 CPU 时间时,剩余的时间会被收集到未使用的 CPU 循环全局池中。其它 cgroup 可以从这个池中借用 CPU 循环
下例中test1 cgroup设定50,test2 cgroup设定200,但test1中运行的进程非常消耗cpu,而test2中运行的进程仅使用很小一部分cpu,且sleep操作会导致其进程进入sleep状态
Test1 cgroup # echo $$ 9629 [root@ test1]# cat cpu.shares 50 [root@ test1]# while true; do a=a+1;done Test2 cgroup # echo $$ 9683 [root@ test2]# cat cpu.shares 200 [root@ test2]# while true; do sleep 1 ;done
查看cpu占用,可以看到test1中的进程占用了99.3%的cpu,而其相对比例为20%
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 9629 root 20 0 116872 3660 1808 R 99.3 0.4 9:32.48 bash
cpuset subsystem
cpuset主要是为了NUMA(非均匀访问存储模型)使用的,NUMA技术将CPU划分成不同的组(Node),每个Node由多个CPU组成,并且有独立的本地内存、I/O等资源(硬件上保证)。可以使用numactl查看当前系统的node清空,如下面表示系统只有一个node,含cpu 0-3,内存大小约1G
# numactl -H available: 1 nodes (0) node 0 cpus: 0 1 2 3 node 0 size: 1023 MB node 0 free: 70 MB node distances: node 0 0: 10
可以使用dmesg | grep -i numa命令查看当前系统是否开启了numa下·
numa的基本架构如下,当cpu访问直接attach的内存时(local access)时会有较大效率,而访问其他cpu attach的内存(remote access)会导致效率下降。
Numa内存分配策略有一下四种,一般采用默认方式
- 缺省default:总是在本地节点分配(当前进程运行的节点上)。
- 绑定bind:强制分配到指定节点上。
- 交叉interleavel:在所有节点或者指定节点上交叉分配内存。
- 优先preferred:在指定节点上分配,失败则在其他节点上分配。
numa场景下可能会出现一个性能问题,NUMA架构的CPU,The MySQL “swap insanity” problem and the effects of the NUMA architecture和A brief update on NUMA and MySQL。发生性能的主要原因是因为more策略下可能会发生swap,即总是在本地节点分配内存,当本地内存不足时会发生swap,可以尝试使用如下方式进行
- 设置numa interleave=all,意味着整个进程的内存是均匀分布在所有的node之上,进程可以以最快的方式访问本地内存
- 使用mlock方式申请内存,这样这段内存不会使用swap
- 使用mmap的MAP_POPULATE,预先分配匿名页,后续访问此内存时不会发生缺页
- 调节系统的vm.swappiness,对于数据库应用服务器,设置为0,可以提高物理内存的使用率,进而提高数据库服务的响应性能
默认方式下,进程总是使用本地节点进程内存分配,可以使用numastat查看内存分配情况
# numastat node0 numa_hit 6711656 numa_miss 0 numa_foreign 0 interleave_hit 19532 local_node 6711656 other_node 0
cpuset调用sched_setaffinity来设置进程的cpu亲和性,调用mbind和set_mempolicy来设置内存的亲和性。可以通过查看/proc/$pid/status查看当前进程cpu和mem的亲和性。cpuset使用中应该遵循以下3点
- 子cpuset的cpu和memory node必须是父cgoup的子集
- 除非父cgroup标记了exclusive,否则子cgoup无法标记该flag
- 如果cgroup的cpu或memory标记了exclusive,那么该cgroup的cpu不能与兄弟cgroup有重合,且父子之间必须重合(参见第一条)
如下例中,在/sys/fs/cgroup/cpuset中创建2个cgroup,按照如下步骤,可以看出,当test1和test2有重合时,设置cpuset失败
# rmdir test1 [root@ cpuset]# mkdir test1 [root@ cpuset]# mkdir test2 [root@ cpuset]# echo 1 > test1/cpuset.cpu_exclusive [root@ cpuset]# echo 1 > test2/cpuset.cpu_exclusive [root@ cpuset]# echo 0,1 > test1/cpuset.cpus [root@ cpuset]# echo 1,2 > test2/cpuset.cpus -bash: echo: write error: Invalid argument [root@ cpuset]# echo 2 > test2/cpuset.cpus
cpuset.cpu_exclusive:包含标签(0
或者 1
),它可以指定:其它 cpuset 及其父、子 cpuset 是否可共享该 cpuset 的特定 CPU。默认情况下(0
),CPU 不会专门分配给某个 cpuset 。
上面介绍了设置该标志后兄弟cpuset之间的cpuset.cpus不能有重合,但父子cpuset之间是必须重合的。cpu_exclusive标记并不能实现完全的cpu隔离(不隶属于cgroup管辖的进程默认拥有所有的cpu权限),如下例中启动了6个消耗cpu的bash进程,仅对其中一个bash进程进行了cpuset的exclusive,可以看到exclusive并不能保证cpu的隔离,只用于保证不于其他兄弟cpuset定义的cpus重叠。核隔离可以使用内核启动参数isolcpus,隔离的cpu不会对其进行负载均衡操作。
Tasks: 243 total, 7 running, 236 sleeping, 0 stopped, 0 zombie %Cpu0 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 68208 free, 654032 used, 273656 buff/cache KiB Swap: 2097148 total, 1928188 free, 168960 used. 92388 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 74809 root 20 0 116652 3300 1656 R 86.0 0.3 1:27.86 bash 74753 root 20 0 116652 3300 1656 R 66.4 0.3 1:57.41 bash 74890 root 20 0 116652 3304 1656 R 66.4 0.3 0:11.07 bash 74081 root 20 0 116740 3572 1808 R 66.1 0.4 3:23.12 bash 70206 root 20 0 116784 3120 1376 R 62.5 0.3 1:38.89 bash 72544 root 20 0 116784 3556 1800 R 52.2 0.4 1:57.57 bash
cpuset.memory_spread_page用于设定文件系统缓冲是否应在该 cpuset 的内存节点中均匀分布,cpuset.memory_spread_slab用于设定slab缓冲(如inode和dentries)是否应在该 cpuset 的内存节点中均匀分布,默认否。该策略在将(大的)数据文件分布到多个node时可以提升性能(平均分布)。
cpuset.sched_load_balance和cpuset.sched_relax_domain_level与cpu负载均衡有关。linux使用sched domains(调度域)为单位进行负载均衡。当sched_load_balance设置为enable时,会在该cpuset中的cpu上进行负载均衡,否则不会在该cpuset中的cpu上进行负载均衡(不同cpuset中重叠的cpu上可能也会有负载均衡)。当root cpuset的sched_load_balance为enable时,会在所有的cpu上进行负载均衡,此时会忽略所有子cpuset中对该值的设置,因此只有在root cpuset disable之后,子cpuset才能生效。cpu负载均衡会影响系统性能,在以下两种情况下可以不需要该功能:
- 大型系统中存在很多cpu,如果对单独进程分配了独立的cpu,此时无需使用cpu负载均衡
- 实时系统上需要减少cpu的损耗,此时可以不适用负载均衡
cpuset.sched_relax_domain_level表示 kernel 应尝试平衡负载的 CPU 宽度范围,仅当cpuset.sched_load_balance enable时生效。一般无需改动。
cpuset.memory_migrate包含一个标签(0
或者 1
),用来指定当 cpuset.mems
的值更改时,是否应该将内存中的页迁移到新节点。
总结:
使用cpu subsystem可以在cpu时间上限制进程,而使用cpuset可以在cpu/mem number上限制进程。但如果cpu和cpuset不匹配时应该如何处理?如下例中,在cpuset中限制该cgroup中的进程只能运行在2号核上,但在cpu中该cgroup的进程最多可以使用2个核
# mkdir cpuset/cpusettest # mkdir cpu/cputest # cd cpuset/cpusettest # echo 0 > cpuset.mems # echo 2 > cpuset.cpus # cd cpu/cputest # echo 1000 > cpu.cfs_period_us # echo 2000 > cpu.cfs_quota_us
启动3个bash执行while true; do a=a+1;done,并将其pid加入到cpu和cpuset的cgroup.procs中,观察top命令可以看到3个bash进程仅占用了2号核,每个cpu占用率都约等于33%。由此可知,cpu中规定了进程可以使用的cpu的上限,但并不一定能达到上限
%Cpu0 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu1 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu2 :100.0 us, 0.0 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st %Cpu3 : 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 995896 total, 68408 free, 647088 used, 280400 buff/cache KiB Swap: 2097148 total, 1928444 free, 168704 used. 92768 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 72544 root 20 0 116784 3556 1800 R 33.9 0.4 4:19.03 bash 70206 root 20 0 116784 3120 1376 R 33.2 0.3 3:58.17 bash 74753 root 20 0 116652 3300 1656 R 33.2 0.3 5:05.08 bash
TIPS:
- 在设置cpuset时必须首先设置cpuset.cpus和cpuset.mems,否则可能出现"No space left on device"的错误
- 可以使用docker inspect DOCKERID来查看该容器挂载的cgroup路径
参考:
本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/10281469.html