以下内容适用于CenOS7
平台
缘起
试想下,你是否遇到以下场景:
- 同一主机运行多个程序,由于某个程序异常(循环引用等)导致系统
CPU
占用300%+
(可能更多),从而影响其他服务正常运行(具体体现为:调用超时或卡
) - 同一主机运行多个程序,由于某个程序异常(未分页查询大表)导致操作系统
OOM
,操作系统无法正常运行,从而影响其他服务正常运行。此时只能通过重启宿主机的方式修复。 - 同一主机运行多个程序,由于某个程序异常(批量下载/上传文件)导致系统带宽耗尽,从而影响其他服务正常运行(具体体现为:网络传输慢)
- ...
以上几个场景,有个共同点:未对程序的运行时资源进行配额限制。
那么如何解决呢?
针对java
程序,可以配置jvm
参数,指定内存大小,从而达到限额的作用。
那怎么限制程序可使用的
CPU
、IO
资源呢?
docker 或许是个不错的选择
docker方式的弊
由于docker
本身存在一定的使用成本,比如你不得不做以下事情:
- 安装
docker
运行时 - 把你的程序做成镜像
通过docker
实现程序配额限制的话,事情变得复杂起来了。
那么还有没有其他的方式呢?
有,我们可以利用容器的的核心技术--cgroups
(控制组),对资源进行限制。
但cgroup 使用起来又很麻烦。
基于上述痛点,easyctl 引入了boot app-with-cgroups
指令来处理程序配额问题。
easyctl 对程序计算资源限额的利:
-
easyctl 精简易用,
4M
左右的二进制安装包,对环境零依赖。 - 足够简单地进行配额限制
- 对原有程序无侵入
接下来我们聊聊具体实现。
easyctl配额限制实现原理
核心原理基于linux cgroups
(containerd cgroups 封装实现),流程如下:
- 通过
boot-app.app-name
(服务名称)字段创建控制组 - 通过
boot-app.resources.limits
(资源限制)字段创建控制组子系统(内存、cpu、io等) - 通过
boot-app.boot-cmd
(启动命令)字段执行启动程序指令,并获取进程id
- 将进程
id
添加至控制组内
使用说明
-
前置条件: 安装easyctl
-
版本支持:v0.7.14-alpha以上
-
验证性内容参考-测试说明部分
-
适用平台:
-
CentOS7
-
CentOS6
由于内核版本原因,不建议使用
-
-
可限制配额资源:
- 内存
- cpu
- 网络
- 磁盘
参数说明
-
boot-app.app-name
: 服务名称,用于关联控制组名称。同一主机上该字段不可重复。 -
boot-app.boot-cmd
: 程序启动命令 -
boot-app.resources.limits.cpu
:CPU
限额,可申请vCore
数量,正整数字段。(设置为0或不设置,表示不限制) -
boot-app.resources.limits.memory
:CPU
限额,可申请vCore
数量,可选单位:GB
、MB
。(设置为0或不设置,表示不限制)
开始使用
1.生成配置文件
$ easyctl boot app-with-cgroups
INFO[0000] 生成配置文件样例, 请携带 -c 参数重新执行 -> config.yaml
2.调整配置
vi config.yaml
,调整以下参数
-
boot-app.app-name
: 服务名称,用于关联控制组名称。同一主机上该字段不可重复。 -
boot-app.boot-cmd
: 程序启动命令 -
boot-app.resources.limits.cpu
:CPU
限额,可申请vCore
数量,正整数字段。(设置为0或不设置,表示不限制) -
boot-app.resources.limits.memory
:CPU
限额,可申请vCore
数量,可选单位:GB
、MB
。(设置为0或不设置,表示不限制)
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_us
与cpu.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
的情况下,若申请2
个vCore
,两个线程将均分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
最多使用2vCore
(cpu
线程)
boot-app:
- app-name: replace_to_your_program_name
boot-cmd: replace_to_your_program_boot_command
resources:
limits:
cpu: 2
限制内存最大
8GB
、cpu
最多使用2vCore
(cpu
线程)
boot-app:
- app-name: replace_to_your_program_name
boot-cmd: replace_to_your_program_boot_command
resources:
limits:
cpu: 2
memory: 8GB
后记
本功能实现初衷: 以实际需求驱动学习容器原理,并通过简单的应用程序落地实践。
网友评论