目录
为什么需要限制作业的最大内存?
不少LSF用户都可能碰到这样的情况:提交到LSF的作业或程序,有时会出现内存泄漏,或者有意想不到的内存分配情况,使得其它进程因无法分配到足够的内存而出现异常。尽管有的客户也采用了bsub -R “rusage[mem=$number]”来为作业预留内存,以保证计算节点有足够的内存来启动和运行作业,但是用户往往对作业程序使用的内存预估不足,结果作业实际所耗内存远远大于预留内存,导致计算节点运行缓慢,甚至对系统造成影响,引发宕机。
站在一个普通的LSF用户角度,如果能通过LSF控制作业所能使用的内存大小,那么就算作业进程有内存泄漏也不会对系统造成影响(可以设置内存使用上限,当到达这个上限值后LSF自动将作业杀掉)。
站在一个LSF系统管理员的角度,如果能通过LSF限制作业所能使用的内存量,不管用户对自己的作业使用内存评估是否准确,即使作业使用内存大大超过了预留内存,都能将它们对系统的影响降到最低,从而保证整个系统的稳定性。
那么LSF内部是如何实现对作业的内存限制呢?
在不使用Linux cgroup的情况下(后文介绍Linux cgroup),LSF有两种强制内存限制的方法:
-
由操作系统强制执行每个进程的内存限制。在这种情况下,LSF将配置的内存限制传递给操作系统,由操作系统通过setrlimit()系统调用来进行内存限制。
-
由LSF强制执行每个作业的内存限制。LSF获取作业所有进程消耗的内存之和,以确定作业是否已达到所指定的内存限制。当作业所有进程的总内存超过内存限制时,LSF会依次发送以下信号来终止作业进程:SIGINT、SIGTERM和SIGKILL。
LSF内存限制如何配置?
LSF有关内存限制的设置,有两个参数:
-
LSB_JOB_MEMLIMIT,该参数用来设置内存限制是由操作系统强制执行还是由LSF强制执行。当LSB_JOB_MEMLIMIT设为Y时,由LSF强制执行(作业级);当LSB_JOB_MEMLIMIT设为N或未定义时,由操作系统强制执行(进程级)。
-
LSB_MEMLIMIT_ENFORCE,使能这个参数可以同时启用LSF强制执行(作业级)和操作系统强制执行(进程级)。
这两个参数的关系说明如下:
参数名 | 参数值 | LSF强制执行(作业级) | 操作系统强制执行(进程级) |
LSB_JOB_MEMLIMIT | Y|y | Enabled | Disabled |
N|n 或未定义 | Disabled | Enabled | |
LSB_MEMLIMIT_ENFORCE | Y|y | Enabled | Enabled |
基于Linux cgroup的内存限制
LSF还支持通过Linux cgroup来进行作业的内存限制。如果想通过Linux cgroup进行内存强制,需要在lsf.conf文件中配置:
-
LSB_RESOURCE_ENFORCE="memory"
-
LSF_PROCESS_TRACKING = Y
-
LSF_LINUX_CGROUP_ACCT = Y
配置上述参数后,如果作业进程占用的内存超过了限制,Linux cgroup内存子系统会自动终止该作业。
LSF内存限制应用举例
LSF可以通过在作业级别(bsub的-M选项)、队列级别(在lsb.queues里设置MEMLIMIT参数)或者应用级别(在lsb.applications里设置MEMLIMIT参数)设置内存限制。下面我们以作业级别为例进行介绍。
示例1:提交一个作业,指定内存限制为100K。
$ bsub -M 100 sleep 1h
Job <20894> is submitted to default queue <normal>.
说明:在默认情况下,LSF资源限制大小单位为KB,在lsf.conf可以配置LSF_UNIT_FOR_LIMITS参数来设置其它单位(MB、GB、TB、PB或EB)。
通过bjobs可以查看作业指定的内存限制(MEMLIMIT输出):
$ bjobs -l
Job <20894>, User <usr1>, Project <default>
, Status <RUN>, Queue <normal>, Command <sleep 1h>, Share
group charged </usr1>
Mon Nov 1 18:01:41: Submitted from host <host1>, CWD </tmp>;
Mon Nov 1 18:01:41: Started 1 Task(s) on Host(s) <host2>, Allocated 1 Slot(s) on Host(s) <host2>, Execution Home </home/usr1>, Exe
cution CWD </tmp>;
Mon Nov 1 18:01:48: Resource usage collected.
MEM: 1 Mbytes; SWAP: 1 Mbytes; NTHREAD: 4
PGID: 17883; PIDs: 17883 15945 15947
MEMLIMIT
100 K
MEMORY USAGE:
MAX MEM: 2 Mbytes; AVG MEM: 2 Mbytes
……
由于我们目前并没有启用前述LSF参数,默认情况下LSF将通过操作系统来进行强制内存限制。操作系统会给作业进程设置最大驻留内存限制(max resident set)。通过刚才的bjobs输出,我们知道作业进程PID是17883,进而可以通过下面这条命令查看该进程的操作系统限制:
$ cat /proc/17883/limits
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size unlimited unlimited bytes
Max core file size unlimited unlimited bytes
Max resident set 102400 unlimited bytes
Max processes 513391 513391 processes
Max open files 7777 7777 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 513391 513391 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
可以发现,上面输出中的Max resident set值是102400,即100 KB,与我们提交作业时bsub -M指定的值一致。
值得注意的是,操作系统内存强制通常允许进程最终运行到完成,换句话说,这种方式并不能确保作业内存不超过预设值,因此上面这个例子的作业可以正常完成。
接下来,我们通过LSF参数来设置更严格的内存限制。
示例2:在lsf.conf文件中配置LSB_JOB_MEMLIMIT = y,执行badmin hrestart all使其生效,查看生效后的配置命令如下:
$ badmin showconf sbd | grep LIMIT
LSB_JOB_MEMLIMIT = y
我们再次提交相同作业,仍然指定内存限制为100K。
$ bsub -M 100 sleep 1h
Job <20895> is submitted to default queue <normal>.
这个作业会因为内存超限而被LSF强制杀掉:作业exit,bhist有如下输出:
TERM_MEMLIMIT: job killed after reaching LSF memory usage limit
$ bhist -l 20895
Job <20895>, User <usr1>, Project <default>, Command <sleep 1h>
Mon Nov 1 18:03:16: Submitted from host <host1>, to Queue <normal>, CWD </tmp>;
Mon Nov 1 18:03:17: Dispatched 1 Task(s) on Host(s) <host2>, Allocated 1 Slot (s) on Host(s) <host2>, Effective RES_REQ <select[type ==local] order[r15s:pg] >;
Mon Nov 1 18:03:17: Starting (Pid 17720);
Mon Nov 1 18:03:17: Running with execution home </home/usr1>, Execution CWD </tmp>, Execution Pid <17720>;
Mon Nov 1 18:03:21: Exited with exit code 130. The CPU time used is 0.0 seconds;
Mon Nov 1 18:03:21: Completed <exit>; TERM_MEMLIMIT: job killed after reaching LSF memory usage limit;
MEMLIMIT
100 K
MEMORY USAGE:
MAX MEM: 2 Mbytes; AVG MEM: 2 Mbytes
在lsf.conf中配置LSB_MEMLIMIT_ENFORCE = Y,也可以达到同样的效果,这里不再赘述。
我们再来看一个Linux cgroup的例子。
示例3:基于Linux cgroup内存子系统的内存限制。
按照前面介绍的方法配置后,可以通过如下命令查看参数:
$ badmin showconf sbd | egrep "LSB_RESOURCE_ENFORCE | LSF_PROCESS_TRACKING | LSF_LINUX_CGROUP_ACCT"
LSB_RESOURCE_ENFORCE = memory
LSF_PROCESS_TRACKING = Y
LSF_LINUX_CGROUP_ACCT = Y
我们提交一个吃内存的程序,并指定内存限制为100M:
$ bsub -M 100M ./eatmem_perhost
Job <21102> is submitted to default queue <normal>.
启用Linux cgroup后,可以在系统的/sys/fs/cgroup/memory/lsf目录树下查看每个作业的内存限制。这是我们刚提交的这个作业的cgroup目录,内存限制值会被记录到memory.limit_in_bytes文件里:
$ ls /sys/fs/cgroup/memory/lsf/lsf10/job.21103.6077.1635835590/
memory.failcnt memory.kmem.tcp.failcnt memory.max_usage_in_bytes memory.numa_stat memory.usage_in_bytes
memory.force_empty memory.kmem.tcp.limit_in_bytes memory.memsw.failcnt memory.oom_control memory.use_hierarchy
memory.kmem.failcnt memory.kmem.tcp.max_usage_in_bytes memory.memsw.limit_in_bytes memory.pressure_level
memory.kmem.limit_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.max_usage_in_bytes memory.soft_limit_in_bytes
memory.kmem.max_usage_in_bytes memory.kmem.usage_in_bytes memory.memsw.usage_in_bytes memory.stat
memory.kmem.slabinfo memory.limit_in_bytes memory.move_charge_at_immigrate memory.swappiness
$ cat /sys/fs/cgroup/memory/lsf/lsf10/job.21103.6077.1635835590/memory.limit_in_bytes
104857600
可以看到,memory.limit_in_bytes记录的值是104857600,即100M。
该作业运行一段时间后,由于不断占用内存,最终达到预设的100M内存限制,自动被Linux cgroup强制杀掉,bjobs和bhist会输出相应信息:
$ bjobs -l 21103
Job <21103>, User <usr1>, Project <default>,
Status <EXIT>, Queue <normal>, Command <./eatmem_perhost
105 10 10>, Share group charged </usr1>
Tue Nov 2 14:46:30: Submitted from host <host1>, CWD </tmp>;
Tue Nov 2 14:46:30: Started 1 Task(s) on Host(s) <host2>, Allocated 1 Slot(s)on Host(s) <host2>, Execution Home </home/usr1>, Exe
cution CWD </tmp>;
Tue Nov 2 14:46:31: Exited with exit code 137. The CPU time used is 0.1 second
s.
Tue Nov 2 14:46:31: Completed <exit>; TERM_MEMLIMIT: job killed after reaching LSF memory usage limit.
MEMLIMIT
102400 K
MEMORY USAGE:
MAX MEM: 100 Mbytes; AVG MEM: 33 Mbytes
$ bhist -l 21103
Job <21103>, User <usr1>, Project <default>, Command <./eatmem_perhost 105 10 10>
Tue Nov 2 14:46:30: Submitted from host <host1>, to Queue <normal>, CWD </tmp>;
Tue Nov 2 14:46:30: Dispatched 1 Task(s) on Host(s) <host2>, Allocated 1 Slot(s) on Host(s) <host2>, Effective RES_REQ <select[type ==local] order[r15s:pg] >;
Tue Nov 2 14:46:30: Starting (Pid 6071);
Tue Nov 2 14:46:30: Running with execution home </home/usr1>, Execution CWD </tmp>, Execution Pid <6071>;
Tue Nov 2 14:46:31: Exited with exit code 137. The CPU time used is 0.1 second
s;
Tue Nov 2 14:46:31: Completed <exit>; TERM_MEMLIMIT: job killed after reaching LSF memory usage limit;
MEMLIMIT
102400 K
MEMORY USAGE:
MAX MEM: 100 Mbytes; AVG MEM: 33 Mbytes
Summary of time in seconds spent in various states by Tue Nov 2 14:46:31
PEND PSUSP RUN USUSP SSUSP UNKWN TOTAL
0 0 1 0 0 0 1
上面3个示例表明,LSF可以在作业达到内存限制时杀掉作业,从而避免作业因占用内存过大而导致的OOM和宕机问题。配合之前介绍的bsub -R "rusage[mem=xxx]"、loadSched/loadStop等设置,可以实现比较好的内存控制效果。如果您对于LSF内存控制有任何问题,也欢迎给公众号留言。
欢迎关注下方微信公众号【HPC常青园】,共同交流HPC集群管理经验和最佳实践。如果您有关于HPC集群的具体需求,欢迎邮件沟通交流:hpc@ivyent.cn。