如何限制agent进程的资源使用

问题背景

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

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

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

  • 怎么限制进程的网络带宽
  • 怎么限制进程的io读速度
  • 怎么限制进程的cpu使用率

“对进程资源的限制”有很多使用场景,比如我看到有人问 怎么控制crawlergo爬虫的cpu和内存使用率,再比如 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个包/每秒”;

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

    准备两台机器:

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

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

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

    第二步:传输大文件

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [root@instance-fj5pftdp ~]# 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 文件系统是怎么工作的? 这篇。

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

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

细节

  • 确认配置

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

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

    1
    2
    3
    4
    [root@instance-fj5pftdp ~]# cat /boot/config-$(uname -r)|grep -i CONFIG_BLK_CGROUP
    CONFIG_BLK_CGROUP=y
    [root@instance-fj5pftdp ~]# 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需要扇区对齐,也就是每次写入的字节大小必须是一个扇区大小的倍数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@instance-fj5pftdp ~]# dd if=/dev/zero of=/tmp/file2 bs=512 count=1 oflag=direct
    ...
    512字节(512 B)已复制,0.00518424 秒,98.8 kB/秒
    [root@instance-fj5pftdp ~]# dd if=/dev/zero of=/tmp/file2 bs=511 count=1 oflag=direct // 非512倍数时,写入报错
    dd: 写入"/tmp/file2" 出错: 无效的参数
    ...
    [root@instance-fj5pftdp ~]# 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”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [root@instance-fj5pftdp ~]# df -h
    ...
    /dev/vda1 79G 71G 4.6G 94% /
    [root@instance-fj5pftdp ~]# cat /proc/partitions
    major minor #blocks name

    253 0 83886080 vda
    253 1 83885039 vda1

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

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

    1
    2
    3
    4
    5
    6
    [root@instance-fj5pftdp ~]# 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
    [root@instance-fj5pftdp test1]# echo '5:1 100' > blkio.throttle.read_bps_device // 5(主设备号)代表了"字符设备"
    -bash: echo: 写错误: 无效的参数

限制cpu使用

  • 怎么限制cpu使用?

    同样使用cgroup。

使用cgroup限制cpu使用

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

    1
    2
    3
    4
    5
    6
    7
    [root@instance-fj5pftdp test]# stress -c 1 &
    [1] 27924
    [root@instance-fj5pftdp test]# stress: info: [27924] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

    [root@instance-fj5pftdp 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%

    1
    2
    3
    4
    [root@instance-fj5pftdp 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限制”测试进程”的使用

    1
    2
    3
    4
    [root@instance-fj5pftdp ~]# mkdir /sys/fs/cgroup/cpu/test-cpu-limit
    [root@instance-fj5pftdp ~]# echo 27925 > /sys/fs/cgroup/cpu/test-cpu-limit/tasks // 进程号是27925
    [root@instance-fj5pftdp ~]# echo 10000 > /sys/fs/cgroup/cpu/test-cpu-limit/cpu.cfs_quota_us // quota = 10ms
    [root@instance-fj5pftdp ~]# echo 50000 > /sys/fs/cgroup/cpu/test-cpu-limit/cpu.cfs_period_us // period = 50ms

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

    查看是否限制stress进程

    1
    2
    3
    4
    5
    [root@instance-fj5pftdp ~]# 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”限制网络流量”没有测试成功,所以也就没有记录。

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

参考

怎么查看内核配置