通过easyctl为你的程序添加配额限制

2021-10-26  本文已影响0人  微凉哇

以下内容适用于CenOS7平台

缘起

试想下,你是否遇到以下场景:

  1. 同一主机运行多个程序,由于某个程序异常(循环引用等)导致系统CPU占用300%+(可能更多),从而影响其他服务正常运行(具体体现为:调用超时或
  2. 同一主机运行多个程序,由于某个程序异常(未分页查询大表)导致操作系统OOM,操作系统无法正常运行,从而影响其他服务正常运行。此时只能通过重启宿主机的方式修复。
  3. 同一主机运行多个程序,由于某个程序异常(批量下载/上传文件)导致系统带宽耗尽,从而影响其他服务正常运行(具体体现为:网络传输慢)
  4. ...

以上几个场景,有个共同点:未对程序的运行时资源进行配额限制。

那么如何解决呢?

针对java程序,可以配置jvm参数,指定内存大小,从而达到限额的作用。

那怎么限制程序可使用的CPUIO资源呢?

docker 或许是个不错的选择

docker方式的弊

由于docker本身存在一定的使用成本,比如你不得不做以下事情:

  1. 安装docker运行时
  2. 把你的程序做成镜像

通过docker实现程序配额限制的话,事情变得复杂起来了。

那么还有没有其他的方式呢?

有,我们可以利用容器的的核心技术--cgroups(控制组),对资源进行限制。

cgroup 使用起来又很麻烦。

基于上述痛点,easyctl 引入了boot app-with-cgroups指令来处理程序配额问题。

easyctl 对程序计算资源限额的利:

  1. easyctl 精简易用,4M左右的二进制安装包,对环境零依赖。
  2. 足够简单地进行配额限制
  3. 对原有程序无侵入

接下来我们聊聊具体实现。

easyctl配额限制实现原理

核心原理基于linux cgroupscontainerd cgroups 封装实现),流程如下:

  1. 通过boot-app.app-name(服务名称)字段创建控制组
  2. 通过boot-app.resources.limits(资源限制)字段创建控制组子系统(内存、cpu、io等)
  3. 通过boot-app.boot-cmd(启动命令)字段执行启动程序指令,并获取进程id
  4. 将进程id添加至控制组内

使用说明

参数说明

开始使用

1.生成配置文件

$ easyctl boot app-with-cgroups
INFO[0000] 生成配置文件样例, 请携带 -c 参数重新执行 -> config.yaml

2.调整配置

vi config.yaml,调整以下参数

boot-app:
  - app-name: eureka-app
    boot-cmd: nohup /usr/bin/java -jar eureka.jar &> /dev/null &
    resources:
      limits:
        cpu: 2
        memory: 3GB

3.执行启动

$ easyctl boot app-with-cgroups -c config.yaml --debug

4.确认配额是否合法(OOM等会有Kill信息)

$ sudo journalctl -xef

测试说明

针对配额内容进行验证性测试

测试CPU限额

实现原理:基于控制组cpu.cfs_period_uscpu.cfs_quota_us实现对CPU的强限制

注: 高级/自定义设置建议使用原生cgroups

A.测试用例1: 配额1vCore申请1vCore

boot-app:
  - app-name: app1
    boot-cmd: stress --cpu 1 --vm 1 --vm-bytes 2G --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 1
        memory: 3GB

执行

easyctl boot app-with-cgroups -c config.yaml

top观测

PID    USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
221208 root      20   0    7312    100      0 R  99.7  0.0   0:29.00 stress
   290 root      20   0       0      0      0 S   0.3  0.0   0:00.01 ksoftirqd/56
...

证明限制生效

B.测试用例2: 配额1vCore申请2vCore

boot-app:
  - app-name: app1
    boot-cmd: stress --cpu 2 --vm 2 --vm-bytes 2G --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 1
        memory: 3GB

执行

easyctl boot app-with-cgroups -c config.yaml

top观测

PID    USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
222430 root      20   0    7312    100      0 R  49.5  0.0   0:03.34 stress
222428 root      20   0    7312    100      0 R  49.2  0.0   0:03.29 stress
...

配额为1vCore的情况下,若申请2vCore,两个线程将均分1vCore使用时间(50%

C.测试用例3: 配额2vCore申请1vCore

boot-app:
  - app-name: app1
    boot-cmd: stress --cpu 1 --vm 2 --vm-bytes 2G --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 2
        memory: 3GB

执行

easyctl boot app-with-cgroups -c config.yaml

top观测

PID    USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
223488 root      20   0    7312    100      0 R  99.7  0.0   0:07.42 stress
...

D.测试用例4: 配额4vCore申请4vCore

boot-app:
  - app-name: app1
    boot-cmd: stress --cpu 4 --vm 2 --vm-bytes 2G --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 4
        memory: 3GB

执行

easyctl boot app-with-cgroups -c config.yaml

top观测

PID    USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
224362 root      20   0    7312    100      0 R  99.7  0.0   0:09.68 stress
224364 root      20   0    7312    100      0 R  99.7  0.0   0:09.69 stress
224365 root      20   0    7312    100      0 R  99.7  0.0   0:09.69 stress
224366 root      20   0    7312    100      0 R  99.7  0.0   0:09.69 stress
...

E.测试用例5: 不限制cpu配额

boot-app:
  - app-name: app1
    boot-cmd: stress --cpu 4 --vm 2 --vm-bytes 2G --vm-hang 120 --timeout 120s
    resources:
      limits:
        memory: 3GB

执行

easyctl boot app-with-cgroups -c config.yaml

top观测

PID    USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
239180 root      20   0    7312    100      0 R 100.0  0.0   0:11.01 stress
239177 root      20   0    7312    100      0 R  99.7  0.0   0:11.00 stress
239181 root      20   0    7312    100      0 R  99.7  0.0   0:11.01 stress
239182 root      20   0    7312    100      0 R  99.7  0.0   0:11.01 stress
...

测试内存限额

实现原理:基于控制组memory.limit_in_bytes内存配置字段实现

注: 高级/自定义设置建议使用原生cgroups

A.测试用例1: 配额1GB申请512M

boot-app:
  - app-name: app1
    boot-cmd: stress --vm 1 --vm-bytes 512M --vm-hang 120 --timeout 120s
    resources:
      limits:
        memory: 1GB

执行

$ easyctl boot app-with-cgroups -c config.yaml --debug
[easyctl] localhost.localdomain | 2021-10-25T04:51:21-04:00 | info | 启动命令: stress --vm 1 --vm-bytes 512M --vm-hang 120 --timeout 120s, 进程id: 244051
[easyctl] localhost.localdomain | 2021-10-25T04:51:21-04:00 | info | 限制程序配额 -> CPU: 0核, 内存: 1GB
[easyctl] localhost.localdomain | 2021-10-25T04:51:21-04:00 | info | 创建cpu子系统: /sys/fs/cgroup/cpu/app1 memory子系统: /sys/fs/cgroup/memory/app1
[easyctl] localhost.localdomain | 2021-10-25T04:51:21-04:00 | debug | 0
[easyctl] localhost.localdomain | 2021-10-25T04:51:21-04:00 | debug | Quota: 0 Period: 100000
[weiliang@localhost ~]$ ps -ef|grep 244051
root     244051      1  0 04:51 pts/0    00:00:00 stress --vm 1 --vm-bytes 512M --vm-hang 120 --timeout 120s
root     244052 244051 99 04:51 pts/0    00:00:11 stress --vm 1 --vm-bytes 512M --vm-hang 120 --timeout 120s
root     244053 244051  1 04:51 pts/0    00:00:00 stress --vm 1 --vm-bytes 512M --vm-hang 120 --timeout 120s
...

运行正常 ,证明限制生效

B.测试用例2: 配额1GB申请2GB

boot-app:
  - app-name: app1
    boot-cmd: stress --vm 1 --vm-bytes 2GB --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 1
        memory: 1GB

执行,并查询进程

$ easyctl boot app-with-cgroups -c config.yaml --debug
[easyctl] localhost.localdomain | 2021-10-25T05:01:52-04:00 | info | 启动命令: stress --vm 1 --vm-bytes 2G --vm-hang 120 --vm-stride 64 --timeout 120s, 进程id: 244246
[easyctl] localhost.localdomain | 2021-10-25T05:01:52-04:00 | info | 限制程序配额 -> CPU: 0核, 内存: 1GB
[easyctl] localhost.localdomain | 2021-10-25T05:01:52-04:00 | info | 创建cpu子系统: /sys/fs/cgroup/cpu/app1 memory子系统: /sys/fs/cgroup/memory/app1
[easyctl] localhost.localdomain | 2021-10-25T05:01:52-04:00 | debug | 0
[easyctl] localhost.localdomain | 2021-10-25T05:01:52-04:00 | debug | Quota: 0 Period: 100000
$ ps -ef|grep 244246
weiliang  244250 244014  0 05:02 pts/0    00:00:00 grep --color=auto 244246

进程启动失败。此时查看系统日志,由于OOM已被kill掉了

$ journalctl -xef
...
Oct 25 05:01:52 localhost.localdomain kernel: [ pid ]   uid  tgid total_vm      rss nr_ptes swapents oom_score_adj name
Oct 25 05:01:52 localhost.localdomain kernel: [244246]     0 244246     1828      107       8        0             0 stress
Oct 25 05:01:52 localhost.localdomain kernel: [244247]     0 244247   526117   262128     521        0             0 stress
Oct 25 05:01:52 localhost.localdomain kernel: Memory cgroup out of memory: Kill process 244247 (stress) score 971 or sacrifice child
Oct 25 05:01:52 localhost.localdomain kernel: Killed process 244247 (stress) total-vm:2104468kB, anon-rss:1048388kB, file-rss:124kB, shmem-rss:0kB
...

C.测试用例3: 配额1GB申请1GB

boot-app:
  - app-name: app1
    boot-cmd: stress --vm 1 --vm-bytes 1G --vm-hang 120 --timeout 120s
    resources:
      limits:
        cpu: 1
        memory: 1GB

执行,并查询进程

$ easyctl] localhost.localdomain | 2021-10-25T05:28:34-04:00 | info | 启动命令: stress --vm 1 --vm-bytes 1G --vm-hang 120 --vm-stride 64 --timeout 120s, 进程id: 244609
[easyctl] localhost.localdomain | 2021-10-25T05:28:34-04:00 | info | 限制程序配额 -> CPU: 0核, 内存: 1GB
[easyctl] localhost.localdomain | 2021-10-25T05:28:34-04:00 | info | 创建cpu子系统: /sys/fs/cgroup/cpu/app1 memory子系统: /sys/fs/cgroup/memory/app1
[easyctl] localhost.localdomain | 2021-10-25T05:28:34-04:00 | debug | 0
[easyctl] localhost.localdomain | 2021-10-25T05:28:34-04:00 | debug | Quota: 0 Period: 100000
$ ps -ef|grep 244609
root     244609      1  0 05:28 pts/0    00:00:00 stress --vm 1 --vm-bytes 1G --vm-hang 120 --vm-stride 64 --timeout 120s
root     244612 244609  5 05:28 pts/0    00:00:00 stress --vm 1 --vm-bytes 1G --vm-hang 120 --vm-stride 64 --timeout 120s

最佳实践

不限制cpu,限制内存最大8GB

boot-app:
  - app-name: replace_to_your_program_name
    boot-cmd: replace_to_your_program_boot_command
    resources:
      limits:
        memory: 8GB

不限制内存,限制cpu最多使用2vCorecpu线程)

boot-app:
  - app-name: replace_to_your_program_name
    boot-cmd: replace_to_your_program_boot_command
    resources:
      limits:
        cpu: 2

限制内存最大8GBcpu最多使用2vCorecpu线程)

boot-app:
  - app-name: replace_to_your_program_name
    boot-cmd: replace_to_your_program_boot_command
    resources:
      limits:
        cpu: 2
        memory: 8GB

后记

本功能实现初衷: 以实际需求驱动学习容器原理,并通过简单的应用程序落地实践。

上一篇 下一篇

猜你喜欢

热点阅读