《Docker进阶与实战》——2.3节Cgroup介绍

本节书摘来自华章社区《Docker进阶与实战》一书中的第2章,第2.3节Cgroup介绍,作者华为Docker实践小组,更多章节内容可以访问云栖社区“华章社区”公众号查看

2.3 Cgroup介绍
2.3.1 Cgroup是什么
Cgroup是control group的简写,属于Linux内核提供的一个特性,用于限制和隔离一组进程对系统资源的使用,也就是做资源QoS,这些资源主要包括CPU、内存、block I/O和网络带宽。Cgroup从2.6.24开始进入内核主线,目前各大发行版都默认打开了Cgroup特性。
从实现的角度来看,Cgroup实现了一个通用的进程分组的框架,而不同资源的具体管理则是由各个Cgroup子系统实现的。截止到内核4.1版本,Cgroup中实现的子系统及其作用如下:
devices:设备权限控制。
cpuset:分配指定的CPU和内存节点。
cpu:控制CPU占用率。
cpuacct:统计CPU使用情况。
memory:限制内存的使用上限。
freezer:冻结(暂停)Cgroup中的进程。
net_cls:配合tc(traffic controller)限制网络带宽。
net_prio:设置进程的网络流量优先级。
huge_tlb:限制HugeTLB的使用。
perf_event:允许Perf工具基于Cgroup分组做性能监测。
在Cgroup出现之前,只能对一个进程做一些资源控制,例如通过sched_setaffinity系统调用限定一个进程的CPU亲和性,或者用ulimit限制一个进程的打开文件上限、栈大小等。另外,使用ulimit可以对少数资源基于用户做资源控制,例如限制一个用户能创建的进程数。而Cgroup可以对进程进行任意的分组,如何分组是用户自定义的,例如安卓的应用分为前台应用和后台应用,前台应用是直接跟用户交互的,需要响应速度快,因此前台应用对资源的申请需要得到更多的保证。为此安卓将前台应用和后台应用划分到不同的Cgroup中,并且对放置前台应用的Cgroup配置了较高的系统资源限额。
从1.6版本开始,Docker也支持ulimit,读者可以查阅相关Docker文档及Linux用户手册。

2.3.2 Cgroup的接口和使用
Cgroup的原生接口通过cgroupfs提供,类似于procfs和sysfs,是一种虚拟文件系统。以下用一个实例演示如何使用Cgroup。
(1)挂载cgroupfs
命令如下:

# mount –t cgroup –o cpuset cpuset /sys/fs/cgroup/cpuset

首先必须将cgroupfs挂载起来,这个动作一般已经在启动时由Linux发行版做好了。可以把cgroupfs挂载在任意一个目录上,不过标准的挂载点是/sys/fs/cgroup。
实际上sysfs里面只有/sys/fs/cgroup目录,并且sysfs是不允-许用户创建目录的,这里可以将tmpfs挂载上去,然后在tmpfs上创建目录。

(2)查看cgroupfs
# ls /sys/fs/cgroup/cpuset
cgroup.clone_children  cpuset.memory_pressure
cgroup.procs           cpuset.memory_pressure_enabled
cgroup.sane_behavior   cpuset.memory_spread_page
cpuset.cpu_exclusive   cpuset.memory_spread_slab
cpuset.cpus            cpuset.mems
cpuset.effective_cpus  cpuset.sched_load_balance
cpuset.effective_mems  cpuset.sched_relax_domain_level
cpuset.mem_exclusive   notify_on_release
cpuset.mem_hardwall    release_agent
cpuset.memory_migrate  tasks

可以看到这里有很多控制文件,其中以cpuset开头的控制文件都是cpuset子系统产生的,其他文件则由Cgroup产生。这里面的tasks文件记录了这个Cgroup的所有进程(包括线程),在系统启动后,默认没有对Cgroup做任何配置的情况下,cgroupfs只有一个根目录,并且系统所有的进程都在这个根目录中,即进程pid都在根目录的tasks文件中。

意 实际上现在大多数Linux发行版都是采用的systemd,而systemd使用了Cgroup,所以在这些发行版上,当系统启动后看到的cgroupfs是有一些子目录的。

(3)创建Cgroup

# mkdir /sys/fs/cgroup/cpuset/child
通过mkdir创建一个新的目录,也就创建了一个新的Cgroup。
(4)配置Cgroup
# echo 0 > /sys/fs/cgroup/cpuset/child/cpuset.cpus
# echo 0 > /sys/fs/cgroup/cpsuet/child/cpuset.mems
接下来配置这个Cgroup的资源配额,通过上面的命令,就可以限制这个Cgroup的进程只能在0号CPU上运行,并且只会从0号内存节点分配内存。
(5)使能Cgroup
# echo 
$$
 > /sys/fs/cgroup/cpuset/child/tasks

最后,通过将进程id写入tasks文件,就可以把进程移动到这个Cgroup中。并且,这个进程产生的所有子进程也都会自动放在这个Cgroup里。这时,Cgroup才真正起作用了。

$$ 表示当前进程。另外,也可以把pid写入cgroup.procs中,两者的区别是写入tasks只会把指定的进程加到child中,写入cgroup.procs则会把这个进程所属的整个线程组都加到child中。 **2.3.3 Cgroup子系统介绍** 对实际资源的分配和管理是由各个Cgroup子系统完成的,下面介绍几个主要的子系统。 1. cpuset子系统 cpuset可以为一组进程分配指定的CPU和内存节点。cpuset一开始是用在高性能计算(HPC)上的,在NUMA架构的服务器上,通过将进程绑定到固定的CPU和内存节点上,来避免进程在运行时因跨节点内存访问而导致的性能下降。当然,现在cpuset也广泛用在了kvm和容器等场景上。 cpuset的主要接口如下。 cpuset.cpus:允许进程使用的CPU列表(例如0~4,9)。 cpuset.mems:允许进程使用的内存节点列表(例如0~1)。 2. cpu子系统 cpu子系统用于限制进程的CPU占用率。实际上它有三个功能,分别通过不同的接口来提供。 CPU比重分配。这个特性使用的接口是cpu.shares。假设在cgroupfs的根目录下创建了两个Cgroup(C1和C2),并且将cpu.shares分别配置为512和1024,那么当C1和C2争用CPU时,C2将会比C1得到多一倍的CPU占用率。要注意的是,只有当它们争用CPU时cpu share才会起作用,如果C2是空闲的,那么C1可以得到全部的CPU资源。 CPU带宽限制。这个特性使用的接口是cpu.cfs_period_us和cpu.cfs_quota_us,这两个接口的单位是微秒。可以将period设置为1秒,将quota设置为0.5秒,那么Cgroup中的进程在1秒内最多只能运行0.5秒,然后就会被强制睡眠,直到进入下一个1秒才能继续运行。 实时进程的CPU带宽限制。以上两个特性都只能限制普通进程,若要限制实时进程,就要使用cpu.rt_period_us和cpu.rt_runtime_us这两个接口了。使用方法和上面类似。 3. cpuacct子系统 cpuacct子系统用来统计各个Cgroup的CPU使用情况,有如下接口。 cpuacct.stat:报告这个Cgroup分别在用户态和内核态消耗的CPU时间,单位是USER_HZ。USER_HZ在x86上一般是100,即1 USER_HZ等于0.01秒。 cpuacct.usage:报告这个Cgroup消耗的总CPU时间,单位是纳秒。 cpuacct.usage_percpu:报告这个Cgroup在各个CPU上消耗的CPU时间,总和也就是cpuacct.usage的值。 4. memory子系统 memory子系统用来限制Cgroup所能使用的内存上限,有如下接口。 memory.limit_in_bytes:设定内存上限,单位是字节,也可以使用k/K、m/M或者g/G表示要设置数值的单位,例如

# echo 1G > memory.limit_in_bytes

默认情况下,如果Cgroup使用的内存超过上限,Linux内核会尝试回收内存,如果仍然无法将内存使用量控制在上限之内,系统将会触发OOM,选择并“杀死”该Cgroup中的某个进程。

memory.memsw.limit_in_bytes:设定内存加上交换分区的使用总量。通过设置这个值,可以防止进程把交换分区用光。
memory.oom_control:如果设置为0,那么在内存使用量超过上限时,系统不会“杀死”进程,而是阻塞进程直到有内存被释放可供使用时;另一方面,系统会向用户态发送事件通知,用户态的监控程序可以根据该事件来做相应的处理,例如提高内存上限等。
memory.stat:汇报内存使用信息。
  1. blkio子系统
    blkio子系统用来限制Cgroup的block I/O带宽,有如下接口。

blkio.weight:设置权重值,范围在100到1000之间。这跟cpu.shares类似,是比重分配,而不是绝对带宽的限制,因此只有当不同的Cgroup在争用同一个块设备的带宽时才会起作用。

blkio.weight_device:对具体的设备设置权重值,这个值会覆盖上述的blkio.weight。例如将Cgroup对/dev/sda的权重设为最小值:

echo 8:0 100 > blkio.weight_device

blkio.throttle.read_bps_device:对具体的设备,设置每秒读磁盘的带宽上限。例如对/dev/sda的读带宽限制在1MB/秒:

echo "8:0 1048576" > blkio.throttle.read_bps_device

blkio.throttle.write_bps_device:设置每秒写磁盘的带宽上限。同样需要指定设备。
blkio.throttle.read_iops_device:设置每秒读磁盘的IOPS上限。同样需要指定设备。
blkio.throttle.write_iops_device:设置每秒写磁盘的IOPS上限。同样需要指定设备。

blkio子系统有两个重大的缺陷。一是对于比重分配,只支持CFQ调度器。另一个缺陷是,不管是比重分配还是上限限制,都只支持Direct-IO,不支持Buffered-IO,这使得blkio cgroup的使用场景很有限,好消息是Linux内核社区正在解决这个问题。

6. devices子系统
devices子系统用来控制Cgroup的进程对哪些设备有访问权限,有如下接口。
devices.list。只读文件,显示目前允许被访问的设备列表。每个条目都有3个域,分别为:
类型:可以是a、c或b,分别表示所有设备、字符设备和块设备。
设备号:格式为major:minor的设备号。
权限:r、w和m的组合,分别表示可读、可写、可创建设备结点(mknod)。
例如“a *:* rmw”,表示所有设备都可以被访问。而“c 1:3 r”,表示对1:3这个字符设备(即/dev/null)只有读权限。
devices.allow。只写文件,以上面描述的格式写入该文件,就可以允许相应的设备访问权限。
上一篇:scp ssh java 实例


下一篇:Visual Basic 二十五周年,微软是否应该开源?