如何限制进程的资源使用

语言: CN / TW / HK

问题背景

如何避免文件上报-制作大文件 问题背景中提到:agent会上报信息,甚至会上传文件。

如果能够限制agent进程对系统资源的使用,是不是可能对"信息上报"造成影响。比如限制agent进程每秒只能读1字节的文件内容、限制agent上报文件带宽在1字节每秒、限制agent只能使用很少的cpu分片等等。

本文记录对"限制资源使用"的实践:

  • 怎么限制进程的网络带宽

  • 怎么限制进程的io读速度

  • 怎么限制进程的cpu使用率

"对进程资源的限制"有很多使用场景,比如我看到有人问 怎么控制crawlergo爬虫的cpu和内存使用率 [1] ,再比如 hids本身也需要对cpu、内存等使用做限制。

agent进程带宽限速

  • 为什么限速?

    如果能让agent进程每秒只能传输很少的字节数,就有可能拖慢agent上报信息的进度,从而干扰安全检测。重点是"网速变慢"能保证"agent心跳"能正常传到server,不至于在server端看到agent异常。

  • 在linux主机上怎么给进程限流呢?

    调研总结有两种方式:

    cgroups + tc命令:可以实现对指定的进程限速
    iptables:可以实现对指定的tcp通信限速
    

使用iptables对进程限速

  • 怎么用iptables对进程限速?

    可以使用iptables的limit模块来完成。

    根据 man iptables-extensions 手册可知:limit模块使用"令牌桶算法"实现。

    --limit-burst 参数指定初始令牌数, --limit 参数指定补充令牌的速率。

    举个例子,下面命令可以对目标ip"1.2.3.4"端口为5001的tcp通信做限速:桶最大值也是初始值就是10个tcp包,每秒新增1个tcp包,所以发包速率峰值基本上可以认为是"10个包/每秒";当客户端每秒有100个包要发出去时,基本上到后面发包速度会基本限制在"1个包/每秒";

    [[email protected] ~]# iptables -A OUTPUT -d 1.2.3.4/32 -p tcp -m limit --limit 1/sec --limit-burst 10 --dport 5001 -j ACCEPT
    [[email protected] ~]# iptables -A OUTPUT -d 1.2.3.4/32 -p tcp --dport 5001 -j DROP
  • 实践验证限速效果

    准备两台机器:

    * `instance-fj5pftdp`发包
    * `1.2.3.4`收包 (为了我避免暴露自己的虚机ip,这里用1.2.3.4代替)

    在发包机器上 使用iptables限制通信,然后传输大文件,并使用tcpdump观察包速率

    第一步:设置iptables,指令如上

    第二步:传输大文件

    * 使用`truncate -s 1G bigfil`创建"稀疏文件"
    * 使用`nc 1.2.3.4 5001 < bigfile`传输文件

    第三步:使用tcpdump观察包速率:

    [[email protected] ~]# tcpdump -i eth0 'port 5001 and ip dst 1.2.3.4'
    22:52:00.743321 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [S], seq 2314647925, win 27200, options [mss 1360,sackOK,TS val 2324847321 ecr 0,nop,wscale 7], length 0
    22:52:00.780074 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], ack 1736746942, win 213, options [nop,nop,TS val 2324847358 ecr 3661297505], length 0
    22:52:00.780253 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 0:2696, ack 1, win 213, options [nop,nop,TS val 2324847358 ecr 3661297505], length 2696
    22:52:00.780516 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 2696:5392, ack 1, win 213, options [nop,nop,TS val 2324847359 ecr 3661297505], length 2696
    22:52:00.780533 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 5392:8088, ack 1, win 213, options [nop,nop,TS val 2324847359 ecr 3661297505], length 2696
    22:52:00.780538 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 8088:8192, ack 1, win 213, options [nop,nop,TS val 2324847359 ecr 3661297505], length 104
    22:52:00.780571 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 8192:10888, ack 1, win 213, options [nop,nop,TS val 2324847359 ecr 3661297505], length 2696
    22:52:00.780587 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 10888:12236, ack 1, win 213, options [nop,nop,TS val 2324847359 ecr 3661297505], length 1348
    22:52:00.816636 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 12236:14932, ack 1, win 213, options [nop,nop,TS val 2324847395 ecr 3661297542], length 2696
    22:52:00.816674 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [.], seq 14932:17628, ack 1, win 213, options [nop,nop,TS val 2324847395 ecr 3661297542], length 2696
    22:52:02.513394 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 17628:18976, ack 1, win 213, options [nop,nop,TS val 2324849092 ecr 3661297578], length 1348
    22:52:02.786393 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 18976:20324, ack 1, win 213, options [nop,nop,TS val 2324849365 ecr 3661299275], length 1348
    22:52:04.485382 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 20324:21672, ack 1, win 213, options [nop,nop,TS val 2324851064 ecr 3661299548], length 1348
    22:52:04.759396 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 21672:23020, ack 1, win 213, options [nop,nop,TS val 2324851338 ecr 3661301247], length 1348
    22:52:06.457405 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 23020:24368, ack 1, win 213, options [nop,nop,TS val 2324853036 ecr 3661301521], length 1348
    22:52:07.205354 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 24368:25716, ack 1, win 213, options [nop,nop,TS val 2324853784 ecr 3661303219], length 1348
    22:52:07.953399 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 25716:27064, ack 1, win 213, options [nop,nop,TS val 2324854532 ecr 3661303967], length 1348
    22:52:09.651384 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 27064:28412, ack 1, win 213, options [nop,nop,TS val 2324856230 ecr 3661304715], length 1348
    22:52:09.924396 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 28412:29760, ack 1, win 213, options [nop,nop,TS val 2324856503 ecr 3661306413], length 1348
    22:52:11.621372 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 29760:31108, ack 1, win 213, options [nop,nop,TS val 2324858200 ecr 3661306686], length 1348
    22:52:11.894385 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 31108:32456, ack 1, win 213, options [nop,nop,TS val 2324858473 ecr 3661308383], length 1348
    22:52:13.593387 IP instance-fj5pftdp.56634 > 1.2.3.4.commplex-link: Flags [P.], seq 32456:33804, ack 1, win 213, options [nop,nop,TS val 2324860172 ecr 3661308656], length 1348

    可以看到最开始 22:52:00 通过了10个包,然后大概每秒可以通过1个包。

agent进程io读限速

  • 在linux主机上怎么给进程io读写限速呢?

    可以通过cgroup实现:cgroup是一种文件系统,可以用来限制cpu、内存、io等资源使用;cgroup也是docker的核心原理之一。

    cgroup有v1、v2两个版本,其中v1 只能限制"直接io",v2版本对内核版本要求在4.5以上(似乎)。而hids等应用程序读文件大部分应该都是"缓冲io",所以无法用v1版本的cgroup限制。我也只实验验证v1版本的对"直接io"的限制。

    如果想要了解更多"直接io"和"缓冲io"相关概念,可以参考 23 | 基础篇:Linux 文件系统是怎么工作的? [2] 这篇。

    关于cgroup的介绍和使用可以参考网上的很多文章,比如 Linux资源管理之cgroups简介 [3]man手册 [4]

    "利用cgroup对进程io读写做限速"可以参考 Using cgroups to limit I/O [5] ,这一篇文章有完整的实验过程。此小节我只记录"需要注意的细节"和文章没有提到的内容。

细节

  • 确认配置

    根据 cgroup-v1文档 [6] 手册说明,需要先确认内核是否支持cgroup io限速

    比如确认如下选项是否开启

    [[email protected] ~]# cat /boot/config-$(uname -r)|grep -i CONFIG_BLK_CGROUP
    CONFIG_BLK_CGROUP=y
    [[email protected] ~]# cat /boot/config-$(uname -r)|grep -i CONFIG_BLK_DEV_THROTTLING
    CONFIG_BLK_DEV_THROTTLING=y
  • dd命令需要添加 oflag=direct 参数和bs参数必须是512(一个扇区的大小)的倍数

    因为cgroup v1 只能限制"直接io",所以dd需要添加 oflag=direct 参数。

    而使用"直接io",磁盘io需要扇区对齐,也就是每次写入的字节大小必须是一个扇区大小的倍数。

    [[email protected] ~]# dd if=/dev/zero of=/tmp/file2 bs=512 count=1 oflag=direct
    ...
    512字节(512 B)已复制,0.00518424 秒,98.8 kB/秒
    [[email protected] ~]# dd if=/dev/zero of=/tmp/file2 bs=511 count=1 oflag=direct // 非512倍数时,写入报错
    dd: 写入"/tmp/file2" 出错: 无效的参数
    ...
    [[email protected] ~]# dd if=/dev/zero of=/tmp/file2 bs=511 count=1 // "缓冲io"(非"直接io")时,不需要用户对齐
    ...
    511字节(511 B)已复制,0.000360275 秒,1.4 MB/秒

    如果想要了解更多"磁盘io对齐"相关知识,可以参考 存储基础 —— 磁盘 IO 为什么总叫你对齐? 这篇。

  • 实验过程中遇到的报错

    在向配置中"写正在使用的分区"和"不正确的设备号"都会提示"无效的参数"报错,如下:

    似乎不能写正在使用的分区"/dev/vda1"

    [[email protected] ~]# df -h
    ...
    /dev/vda1 79G 71G 4.6G 94% /
    [[email protected] ~]# cat /proc/partitions
    major minor #blocks name

    253 0 83886080 vda
    253 1 83885039 vda1

    [[email protected] test1]# echo '253:1 100' > blkio.throttle.read_bps_device // 253对应/dev/vda1,似乎因为已经被挂载使用,所以不能被写入配置
    -bash: echo: 写错误: 无效的参数
    [[email protected] test1]# echo '253:0 100' > blkio.throttle.read_bps_device

    不能限制"字符设备",只能限制"块设备"。当写入"字符设备的设备号"到配置时,就会报错

    [[email protected] ~]# ll /dev|grep "5,"
    crw------- 1 root root 5, 1 Apr 28 21:51 console
    crw-rw-rw- 1 root tty 5, 2 Oct 22 13:45 ptmx
    crw-rw-rw- 1 root tty 5, 0 Oct 12 13:06 tty
    [[email protected] test1]# echo '5:1 100' > blkio.throttle.read_bps_device // 5(主设备号)代表了"字符设备"
    -bash: echo: 写错误: 无效的参数

限制cpu使用

  • 怎么限制cpu使用?

    同样使用cgroup。

测试-使用cgroup限制cpu使用

  • 利用 stress 命令创建高cpu占用的"测试进程"

    [[email protected] test]# stress -c 1 &
    [1] 27924
    [[email protected] test]# stress: info: [27924] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

    [[email protected] test]# ps aux|grep stress
    root 27924 0.0 0.0 7312 424 pts/65 S 23:02 0:00 stress -c 1
    root 27925 96.0 0.0 7312 96 pts/65 R 23:02 0:10 stress -c 1 // 实际工作的进程

    验证cpu占用,可以看出来单核cpu占用100%

    [[email protected] test]# top -b -p 27925
    ...
    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    27925 root 20 0 7312 96 0 R 100.0 0.0 2:32.58 stress
  • 利用 cgroup 限制"测试进程"的使用

    [[email protected] ~]# mkdir /sys/fs/cgroup/cpu/test-cpu-limit
    [[email protected] ~]# echo 27925 > /sys/fs/cgroup/cpu/test-cpu-limit/tasks // 进程号是27925
    [[email protected] ~]# echo 10000 > /sys/fs/cgroup/cpu/test-cpu-limit/cpu.cfs_quota_us // quota = 10ms
    [[email protected] ~]# echo 50000 > /sys/fs/cgroup/cpu/test-cpu-limit/cpu.cfs_period_us // period = 50ms

    限制cpu使用率是20%(10ms/50ms)

    查看是否限制stress进程

    [[email protected] ~]# top -b -p 27925
    ...

    PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
    27925 root 20 0 7312 96 0 R 20.0 0.0 9:30.61 stress

    可以看到限制成功

总结

使用cgroup限制cpu、io等是很简单的。

因为用cgroup"限制网络流量"没有测试成功,所以也就没有记录。

本文提到的手段没有在真实的对抗中实践过,仅仅是我自己的研究,欢迎与我交流。

参考资料

[1]

怎么控制crawlergo爬虫的cpu和内存使用率: https://github.com/Qianlitp/crawlergo/issues/22

[2]

23 | 基础篇:Linux 文件系统是怎么工作的?: https://time.geekbang.org/column/article/76876

[3]

Linux资源管理之cgroups简介: https://tech.meituan.com/2015/03/31/cgroups.html

[4]

man手册: https://man7.org/linux/man-pages/man7/cgroups.7.html

[5]

Using cgroups to limit I/O: https://andrestc.com/post/cgroups-io/

[6]

cgroup-v1文档: https://www.kernel.org/doc/Documentation/cgroup-v1/blkio-controller.txt

[7]

怎么查看内核配置: https://qastack.cn/superuser/287371/obtain-kernel-config-from-currently-running-linux-system