Posted on 

cgroups I - usage

0x00 what is cgroup?

cgroups 是 Linux 内核的一个功能,用来限制,控制与分离一个进程组群的资源(如 CPU, 内存,网络,磁盘输入输出等).

0x01 The begin of cgroups

这个项目最早是由 Google 的工程师在 2006 年发起 (主要是 Paul Menage 和 Rohit Seth), 最早的名称为进程容器 (process containers). 在 2007 年时,因为在 Linux 内核中,容器 (container) 这个名词有许多不同的意义,为避免混乱,被重命名为 cgroup, 并且被合并到 2.6.24 版的内核中去 [^wiki].

0x02 feature

cgroup 的一个设计目标是为不同的应用情况提供统一的接口,从控制单一进程 (像 nice) 到操作系统层虚拟化 (像 opeNVZ,Linux-VServer,LXC).
cgroups 提供 [^access]:

  • 资源限制:组可以被设置不超过设定的内存限制;这也包括虚拟内存。
  • 优先化:一些组可能会得到大量的 CPU[5] 或磁盘输入输出通量。
  • 报告:用来衡量系统确实把多少资源用到适合的目的上。
  • 分离:为组分离命名空间,这样一个组不会看到另一个组的进程、网络连接和文件。
  • 控制:冻结组或检查点和重启动。

0x03 the position of cgroups in kernel

可以在下图看到其所在:

architectures
architectures

0x10 The struct of cgroups

0x11 term

  • *任务 (Tasks)*:就是系统的一个进程。
  • *控制组 (Control Group)*:一组按照某种标准划分的进程,比如官方文档中的 Professor 和 Student, 或是 WWW 和 System 之类的,其表示了某进程组.Cgroups 中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上。简单点说,cgroup 的呈现就是一个目录带一系列的可配置文件。
  • *层级 (Hierarchy)*:控制组可以组织成 hierarchical 的形式,既一颗控制组的树 (目录结构). 控制组树上的子节点继承父结点的属性。简单点说,hierarchy 就是在一个或多个子系统上的 cgroups 目录树。
  • *子系统 (Subsystem)*:一个子系统就是一个资源控制器,比如 CPU 子系统就是控制 CPU 时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制.Cgroup 的子系统可以有很多,也在不断增加中。

0x12 resource mangement

引用这个图片,尝试解释一下 cgroup 的资源划分的结构。不同颜色代表不同group对资源的划分,不过这个设计存在一些缺陷已经遭到Tejun Heo吐槽, 某个任务归类方式的多样性导致了多个Hierarchy的正交,导致了进程管理的复杂。多个子系统的之间的交叉使用导致了管理的复杂,不过在一些 cgroup 套件里面使用配置文件转移一部这个问题的复杂度。

cgroups2
cgroups2

0x13 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 系统进行限制,这是一个大页文件系统。

0x20 Base usage in CLI

0x21 确认系统

在使用systemd的系统里面hierarchy由其启动时自动创建,在rhel6系列中需要yum install libcgroup, 如果是Ubuntu系列的话较新的版本是自带了。
查看 cgroup 文件系统的挂载:

1
2
3
4
5
6
7
8
9
10
11
# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)

查看子系统:

1
2
3
4
5
6
7
8
9
10
# lssubsys
cpuset
cpu,cpuacct
memory
devices
freezer
net_cls
blkio
perf_event
hugetlb

0x22 使用案例 1

Linux 中把cgroups实现成了文件系统,所以对文件系统里面的特定文件进行操作就可以完成对cgroup的配置。

demo[^coolshell]:

1
2
3
4
5
6
int main(void)
{
int i = 0;
for(;;) i++;
return 0;
}

配置:首先在某个子系统下面建立一个目录,其目录里面会自动创建与其相关的文件 (文件名表示其意义); 其次置具体参数到文件名; 然后把要限制的进程 PID 放入task的文件。

1
2
3
4
5
# mkdir /sys/fs/cgroup/cpu/eleme
# ps uax | grep deadloop
root 4260 59.0 0.0 4164 352 pts/0 RN 23:12 3:03 ./deadloop
# echo "2000" >> /sys/fs/cgroup/cpu/eleme/cpu.cfs_quota_us
# echo "4260" >> /sys/fs/cgroup/cpu/eleme/tasks

配置前:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
4434 root      25   5    4164    356    280 R  92.0  0.0   0:04.18 deadloop

配置后:

PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
4434 root      25   5    4164    356    280 R   2.0  0.0   1:14.91 deadloop

0x23 使用案例 2

之所以单独记录了 netcls 的使用,是因为netcls相当于cgroup中其他的控制组使用起来不是很方便 (需要外部工具配合).

在 cgroup 中,通过 netcls 子系统使用识别符 (classid) 标记网络数据包,标记完成过后一般是以下两个操作。

  • 可用流量控制器 (tc) 从不同的cgroup中关联不同的优先级到数据包,这个场景多数是用于QOS相关。
  • 也可以用iptables来对这些具有这些特征 (-m cgroup) 流量做一些具体的操作,这个操作幅度比QOS的要大,根据iptables的不同table提供的-J有很多动作可以做。
  • Basic Queuing technology

在描述Linux QOS之前需要想象最基本的队列技术 - 普通队列 (FIFO), 而后对其有了点改进诞生了由 3 个队列一起工作 (pfifo), 由多个队列一起工作的 (Stochastic Fair Queuing) 和拿着令牌才能走包的 (token bucket filter)[^qdisc].
需要补充说明的是虽然 linux 也支持基于字节的队列技术bfifo, 但是bfifo在特性支持上要远逊色于pfifo.

pfifo
pfifo
接口默认队列技术的 pfifo_fast
pfifo(基于 packet 的 fifo) 默认是使用三个队列,能提供基本的优先级功能。
SFQ
SFQ
看上去公平的 sfq
sfq 的公平是由 hash 与轮序算法保证的。更多信息 [这里](http://wiki.mikrotik.com/wiki/Manual:Queue#SFQ)
tbf
tbf
拿着令牌才放行的 tbf

即使这样多数情况依然不够!

比如”A,B,C,D 四个服务,其中 A 是延迟敏感的视频会议,B 是吞吐量大的 bt 下载,C,D 普通的 web 流量”, 上面提供的这些功能或多或少只能满足一部分服务。这个时候一个层次化的划分队列被开发出来了,虽然 linux 也实现了其它的有类队列规则,但是他们远不如其中的 HTB(Hierarchical Token Bucket) 使用更加广泛 [^packet_flow].

HTB
HTB

htb 允许创建层次的队列结构与决定队列之间的关系 (父 - 子,子 - 子).

一般的使用步骤如下:

  • 1: 匹配和标记流量:将流量分类待使用,利用包含一个或者多个匹配参数来选择数据包构成一个 class

  • 2: 创建策略到标记的流量上:把具体的 class 放到特定的队列中并定义每个 class 携带的动作。

  • 3: 附加策略到到特定接口:附加策略到全局接口 (全局进,全局出,全局进出), 特定接口或者父队列。

  • htb demo[^htb]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# This line sets a HTB qdisc on the root of eth0, and it specifies that 
# the class 1:30 is used by default. It sets the name of the root as 1:, for future references.
tc qdisc add dev eth0 root handle 1: htb default 30

# This creates a class called 1:1, which is direct descendant of root (the parent is 1:),
# this class gets assigned also an HTB qdisc, and then it sets a max rate of 6mbits, with a burst of 15k
tc class add dev eth0 parent 1: classid 1:1 htb rate 6mbit burst 15k

# The previous class has this branches:

# Class 1:10, which has a rate of 5mbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 5mbit burst 15k

# Class 1:20, which has a rate of 3mbit
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 3mbit ceil 6mbit burst 15k

# Class 1:30, which has a rate of 1kbit. This one is the default class.
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 1kbit ceil 6mbit burst 15k

# Martin Devera, author of HTB, then recommends SFQ for beneath these classes:
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:20 handle 20: sfq perturb 10
tc qdisc add dev eth0 parent 1:30 handle 30: sfq perturb 10

HTB 使用信息 这里, 理论基础, 实现细节.

对前面的QOS有了了解,加上在 2014netfilter支持了cgroup特性,用户态需要安装新的 iptables, 而后可以matchcgroup相关的流量,这个时候net_cls才算能和netfilter一起工作 [^net_cls_doc].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

INTERFACE=eno16777736

# configuring tc:
tc qdisc add dev $INTERFACE root handle 10: htb

# creating traffic class 10:1 and configure the rate with 40mbit
tc class add dev $INTERFACE parent 10: classid 10:1 htb rate 40mbit

# filter special traffic
tc filter add dev $INTERFACE parent 10: protocol ip prio 10 handle 1: cgroup

# create new Hierarchy
if [ ! -d /sys/fs/cgroup/net_cls/0 ]; then
mkdir /sys/fs/cgroup/net_cls/0
fi

# send 0010:0001 to net_cls.classid
echo 0x00100001 > /sys/fs/cgroup/net_cls/0/net_cls.classid

# use iptables to match cgroup traffic and drop it.
iptables -A OUTPUT -m cgroup --cgroup 0x00100001 -j DROP

[^access]: redhat access
[^wiki]: wikipedia
[^coolshell]: coolshell
[^net_cls_doc]: cgroup net_cls doc
[^linux_tc]: Linux taffic control
[^linux_fw]: linux firewalls
[^packet_flow]: MikroTik RouterOS packet flow
[^qdisc]: classful qdiscs
[^htb]: HTB
[^example]: cgroup 使用范例