简介:openLava 4.0 是一套面向集群调度场景的开源作业管理源码包,适合运维工程师、集群管理员、以及希望理解分布式调度器工作原理的开发者学习使用。该项目虽已停止官网维护,但 IBM Spectrum LSF 即由 openLava 演化而来,研究源码能帮助梳理商业调度器的关键设计;它提供作业提交、队列排队、资源匹配、任务分发等完整链路,是学习 HPC 批处理很好的样本。压缩包共 501 个文件,整体仅 1.65MB:约 250 个 C 源文件与 65 个头文件组成核心调度逻辑,44 个 .in 模板、若干 Makefile.am、m4 宏脚本保留 autotools 构建体系,便于修改后重新编译;另有大量 .1、.5、.8 格式的手册页,覆盖 bsub、bjobs、bqueues、lsload 等常用命令。同时附带面向 MPICH、OpenMPI 的启动器封装,以及 README、ChangeLog 等说明文档,从源码到命令手册结构完整。已有 1602 人浏览学习,对想对照源码理解 LSF 家族命令接口与任务分发流程的读者,是一份体积精炼、结构清晰的入门范本。
1. 为什么还在用 HPC 调度器:openlava 这台「旧电脑」的价值
在容器编排成为默认选项的今天,很多人觉得集群管理应该交给 K8s,但面对一批跑分子动力学、芯片验证或者气象模式的老代码,openlava 4.0 这个不到几兆的源码包依然有它的位置。它本质上是一个轻量级作业调度器,安装完占用的内存比一个深度学习训练脚本还小,却能帮你把几十台服务器按队列排队、按资源限制分配,完成 HPC 场景下最核心的集群管理。
这个版本是开源 LSF 分支的延续,命令接口保持 LSF 风格,所以从老集群迁移过来几乎不用改脚本。它解决的具体问题是:当你有一堆同质化计算节点,需要公平调度、优先级管理、限制核数和内存,又不想引入重型的商业调度器时,openlava 是性价比最高的选择。适合中小实验室、超算中心的外围集群,以及只想快速把裸机组织成可用集群的人。
2. 从源码包到可用集群:openlava 4.0 编译安装全流程
2.1 编译前的依赖检查与 configure 参数
openlava 4.0 采用传统的 configure + make 构建方式,不像现代一些调度器自带打包好的二进制。这既是好事也是坏事:好处是你能把调度器装到任意老系统上,比如 CentOS 7、Ubuntu 16.04 甚至更早;坏处是很多新手在编译阶段就卡住,报错信息还特别隐晦。我一般建议先把基础依赖装齐,再动 configure。
我自己的测试环境是一台最普通的 4 核物理机,装的是 Ubuntu 20.04,openlava 4.0 对操作系统没有强约束,只要 gcc 版本能过 make。需要准备的包包括编译工具链、ncurses、libxml2 和 tcl 开发库。这里有一个容易忽略的点:openlava 的编译脚本默认去找 libxml2,如果系统里只有运行时库没有 dev 包,configure 会半路失败。
sudo apt-get install -y build-essential libncurses5-dev libxml2-dev tcl-dev libssl-dev说明一下:libssl 是给内部加密用的,如果不需要可以去掉,但某些版本在生成 lim 时需要。上面的包顺序别打乱,build-essential 负责 gcc 和 make,ncurses 负责 lsadmin 终端界面,libxml2 是定义根服务器的库,tcl 用于某些扩展命令。你可以用dpkg -l | grep libxml2验证 dev 包是否装好,如果只看到 libxml2.so 而没有带 .h 头文件,说明还缺东西。
configure 的参数要用得克制。openlava 4.0 不像 nginx 那样有几十个可调项,真正影响落地的只有 prefix 和 conf 文件安装路径。
./configure --prefix=/opt/openlava --with-confdir=/opt/openlava/etc--prefix 是安装根目录,--with-confdir 是配置目录。如果不指定 confdir,默认会装在 /etc 下,和用户文件混在一起,后面升级或者多版本切换会很痛苦。我一般把两者统一到 /opt/openlava 下,这样整个调度器就是一个自包含目录,备份时 tar 一下就行。
2.2 安装与初始化:把调度器跑起来
依赖和 configure 都通过后,make 过程几乎不会出问题。需要注意的是一定要加 -j 参数,因为代码量不大,但单线程编译要等几分钟,加上能省不少时间。
make -j$(nproc) make install这里$(nproc)会自动读取当前 CPU 核数,如果你在虚拟机里只分到 1 核,那就老老实实 make。安装完成后检查一下 /opt/openlava 的目录结构,正常情况下会有 bin、sbin、etc、lib 等目录。如果发现缺少 bin/bsub 这类可执行文件,大概率是 configure 时找不到某个库,回去把依赖补齐重来。
接下来是要创建用户和初始化配置。openlava 的守护进程默认用 root 跑,但为了安全我建议给它建一个独立账户,并让所有作业以该用户身份运行。
useradd -r -s /bin/bash openlava mkdir -p /opt/openlava/etc cp /opt/openlava/conf/* /opt/openlava/etc/ chown -R openlava:openlava /opt/openlava注意,源码包里的 conf 目录是一整套示例配置,包括 lsf.cluster.openlava、lsf.conf、lsb.hosts、lsb.params 等。直接复制到 etc 下就能用,但一定要把配置文件名改对。以 lsf.cluster.openlava 为例,要按你实际集群名改成 lsf.cluster.mycluster,否则启动时找不到文件。
初始化还包括环境变量。我习惯把下面的代码放进 /etc/profile.d/openlava.sh,而不是写在 ~/.bashrc,这样所有用户登录后都能直接输 bsub。
export LSF_ENVDIR=/opt/openlava/etc export LSF_SERVER_HOSTS="node01 node02 node03" export PATH=/opt/openlava/bin:/opt/openlava/sbin:$PATH其中 LSF_ENVDIR 是关键,它决定了 lim 和 lsbatch 去哪个目录找配置。LSF_SERVER_HOSTS 是调度主机的列表,如果只有一台机器,写本机 hostname 即可。PATH 里注意把 openlava 的 bin 放在前面,避免和系统自带的旧版本命令冲突。
启动服务的顺序也有讲究。先启动 lim,再启动 master,最后启动每台计算节点上的 sbatchd。openlava 官方推荐用守护脚本,但我更喜欢手动启动,因为能直观看到日志。
source /etc/profile.d/openlava.sh /opt/openlava/sbin/lim -f /opt/openlava/sbin/lsbatch -f /opt/openlava/sbin/sbatchd -f-f 表示前台运行并打印日志,当你看到lsbatch: started或类似字样的输出,说明 master 已经起来了。注意 lim 必须在 lsbatch 之前,因为在启动 master 时要去读 LSF_ENVDIR 下的配置,如果 lim 没起来,lsbatch 会一直 retry。sbatchd 是每台计算节点上的执行代理,也是最后起来的,它和 master 握手成功后,你才能用bhosts看到节点状态。
这里有一类常见误区:很多人只启动了 lim 和 lsbatch,然后 bsub 提交作业发现一直 PEND,看 bhosts 又是 ok,其实是被计算节点上的 sbatchd 没启动卡住了。可以到 /tmp/openlava.log 或 syslog 里找this host is not a slave之类的信息。
2.3 用 lsid 和 badmin 验证安装
安装完成后别着急提交作业,先用几个命令确认调度器确实正常运行。第一件事是 lsid,它返回当前 openlava 版本和集群名。如果返回正常,说明 lim 和 master 都活着。如果提示 cannot connect,先检查 LSF_ENVDIR 和 lim 进程。
# 查看调度器版本与集群名 lsid # 查看管理员操作入口 badmin -helplsid 的输出里有一行My cluster name is mycluster,这行文字必须和你 lsf.cluster 里配置的 ClusterName 一致,不一致时虽然不至于崩溃,但作业的节点归属会乱掉。badmin 是管理命令的入口,常用的有badmin hrestart重启节点域,badmin reconfig重读配置文件。
再检查主机状态:
bhosts -l这个命令会列出所有计算节点,状态列显示 ok 表示资源可用。如果显示 unavail 或 unreach,多半是网络或 sbatchd 问题。我见过最典型的是节点防火墙没开 6881 端口(openlava 默认的作业提交端口),导致 sbatchd 握手失败。你可以逐个节点启动 sbatchd 时加 -v 看详细日志。
至此,一个最小的 openlava 集群已经能跑了。但要想真正用于生产,还要配置队列和资源限制,这就是下一章的内容。
3. 集群管理三板斧:队列、主机与作业提交参数详解
3.1 用 lsb.hosts 规划队列边界
lsb.hosts 是 openlava 集群管理的核心文件。它定义了队列、主机组、资源组和默认的作业限制。很多人第一次打开它,看到里面一堆 DefaultQueue、Queue、HOSTS 关键词就发懵,其实结构很简单:先声明主机组,再声明队列。
# 从 conf 示例复制后,按下面格式重写 Begin HostGroup NAME cpu_hosts node01 node02 node03 NAME gpu_hosts gpu01 gpu02 End HostGroup Begin Queue QUEUE normal PRIORITY 30 USERS all HOSTS cpu_hosts MaxCPUS 16 # 每个节点最多分配16核 JOBFP /home/app/user/%U STYLE ALLJOBS End Queue逻辑说明:HostGroup 段先把主机分成一个个组,Queue 段里用 HOSTS 选项绑定到组。这样做的意义是,你可以在同一个集群里划分出普通 CPU 队列和 GPU 队列,队列之间隔离资源,互不抢占。PRIORITY 数字越大优先级越高,normal 队列的 30 高于低优先级队列的 10,那 low 队列里的作业就只能在 normal 空闲时跑。
这里我提一个参数容易踩坑的点:MaxCPUS 的语义是“每个节点最多分配给本队列的核数”,不是整个队列的总核数。如果你在 32 核节点上写 MaxCPUS 16,那么队列最多占一半核,剩下的留给别的队列或本地任务。如果写成 MaxCPUS 100,则等价于不限制。新手经常把这个值和后面 bsub 的 -n 混淆,导致明明申请了 8 核,实际运行只分配到 1 核。
3.2 bsub:提交作业的常见参数与示例
bsub 是用户接触最多的命令。它的参数虽然多,但日常高频的只有十几个。我以一个可复用的提交动作为例:
# 向 normal 队列提交一个需要4核16G的作业 bsub -q normal -n 4 -R "rusage[mem=4096,swap=0]" -M 16G -o %J.log -e %J.err -J "demo_job" /bin/bash run_sim.sh-q 指定队列,-n 申请核数,-R 是资源约束表达式,其中 rusage[mem=4096] 表示每核保留 4096MB 内存,这里 4 核就会占满 16G。-M 限制作业总内存。-o 和 -e 是输出和错误文件,%J 会自动替换为作业 ID。最容易被忽视的是 -R 里的 swap=0,它能防止作业把内存 swap 到磁盘,对计算密集场景很重要。
如果提交的是 GPU 作业,可以写成:
bsub -gpu "num=1:mode=shared" -q gpu_q /bin/bash train.py-gpu 是 openlava 对 GPU 队列的扩展参数,num 是申请块数,mode=shared 表示允许和其他作业共享显存。这些参数在 lsb.params 里需要开启 GPU 支持,否则会报 unknown resource。我在第一次用这个选项时也翻车了,报错信息没提示是哪个文件的问题,最后核对才发现是 lsb.params 里少了一行GPU_CFG=Y。
3.3 用 bjobs/bqueues/bhosts 三件套观察集群状态
提交之后,排查问题的路径就变成三个命令:bjobs 看作业,bqueues 看队列,bhosts 看节点。我习惯先用 bhosts 看资源,再用 bqueues 看队列分配,最后用 bjobs -l 看单个作业的详细调度信息。
# 简洁看一下当前所有作业 bjobs # 看队列统计 bqueues -l normal # 看某个作业的完整状态与调度理由 bjobs -l 12345 | grep -E "STATUS|SCHEDULING"bjobs 输出的状态列通常是 PEND、RUN、DONE、EXIT。PEND 不一定是坏事,可能是依赖等待;但如果长时间 PEND,就要用 bjobs -l 去看 SCHEDULING 那一栏。它通常会写pending for: waiting for available slot或new job。后者往往是配置问题,前者是资源不足。
bqueues -l 能把队列的并发限制、用户限制、调度周期都显示出来。生产环境中一般要定期检查 MAX_LIMIT 和 JOB_LIMIT 这两个值有没有被误设。我就见过有人把队列的 JOB_LIMIT 设成 10,结果所有作业全排队,bhosts 显示节点还空着,那一次排查花了半小时才意识到是队列限制在作怪。
这三个命令都支持 -l 长格式,也支持 -o 自定义输出字段。例如bjobs -o "jobid name status"可以只打印指定列,非常适合写脚本自动巡检。建议大家在正常状态时先各跑一遍长格式,熟悉字段含义,这样遇到异常时才能立刻看出哪个数值不对劲。
4. 避坑手册:openlava 日常运维的六个翻车现场
4.1 bhosts 有节点但 bsub 提示 no available host
现象:bhosts 里节点状态是 ok,但 bsub 提交作业直接报No available host。
原因:最常见的有三种。一是队列绑定的主机组里面没有包含该节点;二是节点的资源限制已满,比如 MaxCPUS 已经分配完;三是节点的 Type 是 restrict,被显式排除。前两种占了 90%。
解决:先bqueues -l查看队列绑定的主机组,确认你提交的作业是否匹配节点组。再bhosts -l <node>看该节点的 MaxCPUS、maxJobs 等实时值。如果确认是 restrict,用badmin hrestart <node>重新激活。
4.2 作业一直 PEND 但 bqueues 却显示有可用 slot
现象:bjobs 作业 PEND 好几天,bqueues 显示队列资源充足,节点 cpu 空闲。
原因:这不是资源不够,而是调度器把作业挂在某个依赖条件上。我踩过最坑的是bsub -w "ended(name1)"这种依赖,前一个作业如果 EXIT,后面的作业会永远等下去。另外,-H 挂起参数也是罪魁祸首,提交时填了 -H 会 hold 住任务。
解决:用bjobs -p 作业号查看 pending reason。如果显示 DONE/EXIT 依赖,用bkill 作业号杀掉重新提交,或者用btops修改 -w 条件。如果 -H 那个作业,用bswitch -HRELEASE 作业号释放。
4.3 所有作业都打到了同一台节点上
现象:集群 10 个节点负载不均,其中 1 个节点满载,其他节点完全空闲。
原因:默认调度策略是“最少作业优先”,但如果你所有节点的主机名在 lsb.hosts 里写成了同一个别名,或者节点实际不可达,调度器会对不可达节点降权,导致流量集中到一个节点。还有一种是资源定义里的 MEM,如果没配 mem 资源,调度器认为所有节点内存一样多,就不会按内存余量分布。
解决:检查 lsb.hosts 里的主机名是否和hostname以及 LSF_SERVER_HOSTS 一致。用bgroups查看节点是否真的在线。重新跑一遍badmin reconfig,刷新节点资源权重。
4.4 升级系统后 bsub 报 command not found
现象:重装或升级 OS 后,用户登录 shell 里输 bsub 提示 command not found,但检查 /opt/openlava/bin 目录里文件是存在的。
原因:环境变量丢失。/etc/profile.d 下的脚本在系统升级时被覆盖,或者用户的 PATH 被重置。另外有些时候是动态库路径没配置,bsub 找不到 liblsf.so。
解决:把下面内容重新写入 /etc/profile.d/openlava.sh 并 source:
export LSF_ENVDIR=/opt/openlava/etc export LSF_LIBDIR=/opt/openlava/lib export PATH=/opt/openlava/bin:/opt/openlava/sbin:$PATH export LD_LIBRARY_PATH=/opt/openlava/lib:$LD_LIBRARY_PATH如果还提示找不到库,用ldd $(which bsub)检查缺失的 so 文件,一般是 libxml2 或 tcl 库没装。这里有一个细节:LD_LIBRARY_PATH 要放在最后,避免覆盖系统库路径。
4.5 节点主机名解析混乱导致 sbatchd 无法启动
现象:sbatchd 启动后几秒就退出,或者持续重试握手失败。
原因:我遇到的情况是 /etc/hosts 里把节点主机名解析到了 127.0.0.1,openlava 的 lim 启动后去解析计算节点的 IP,拿到的全是 lo 接口地址,导致 master 和 sbatchd 根本不在一个网络域。另外,如果节点有多个网卡,openlava 默认选第一个 IP,选错也会连接失败。
解决:在 lsf.conf 里强制指定对外网卡:
LSF_IPADDR_SPEC=bind_ip_address LSF_BIND_IPADDR=192.168.1.10LSF_BIND_IPADDR 设置为主机的真实内网 IP。然后重启 lim 和 sbatchd。最后用pstack或tcpdump确认握手端口,不要凭感觉。
4.6 clock skew 引发作业运行异常
现象:作业运行没多久就被 kill,日志里出现clock skew too great或类似时间异常提示。
原因:节点间时间差超过 30 秒,openlava 认为这是时钟跳变下的异常进程,为避免分布式任务逻辑混乱而强制终止。这是分布式系统里的经典问题。
解决:在每台节点上配置 NTP 服务,或至少用 cron 周期同步时间:
ntpdate -u time.example.com同步后,用date -s手动校准也行。之后重启 lim、lsbatch、sbatchd,让所有进程刷新时钟缓存。这里有个小习惯:每次改集群时间后都要重启调度服务,否则可能出现“作业已结束但 bjobs 仍显示 RUN”的假象。
5. 资源利用率进阶:多集群协同与混合负载调度
5.1 单集群内混合资源调度:CPU/GPU 队列划分
很多集群虽然叫 HPC,但实际负载是 CPU 任务和 GPU 任务混跑。如果不加隔离,GPU 作业会占满所有节点的 CPU,CPU 密集作业反而排不上。前面 lsb.hosts 里我们已经有了 cpu_hosts 和 gpu_hosts 两拨节点,这一步还可以更进一步,给节点打上静态资源标签。
# 在 lsb.hosts 的 HostGroup 中增加资源标签 Begin HostGroup NAME gpu_nodes gpu01 gpu02 RESOURCE GPU 1 # 定义 GPU 资源 End HostGroup然后在队列定义里用 SELECT 选项筛选标签:
Begin Queue QUEUE gpu_q HOSTS gpu_nodes SELECT GPU=1 End Queue这样 bsub -q gpu_q 提交的作业只会落到带有 GPU 标签的节点,并且调度器在分配时还会校验 GPU 剩余量。如果节点上有 2 块卡,就可以通过修改 GPU 的数字来限制并发作业数量。这里的数字不是物理卡数,而是调度器内部的资源令牌数,需要和节点管理员确认好约定。
5.2 跨团队公平调度:设置 CPU 份额与优先级
多团队共用一套调度器时,很容易出现一个团队把资源占满、其他团队排队的情况。openlava 的资源控制粒度比想象的细,可以用用户组和份额去限制。
在 openlava 里做优先级控制主要靠三个参数:队列的 PRIORITY、用户组的 CPU 份额、以及作业的 nice 值。其中用户组的份额写在 lsb.users 里:
Begin UserGroup NAME team_a USERS alice bob End UserGroup Begin GroupLimit GROUP team_a LIMIT 80 End GroupLimitGroupLimit 的 80 是一个百分比,表示 team_a 最多占用集群 80% 的可用 CPU。这样做的好处是团队间资源隔离,坏处是阀门一旦开启,单个团队作业多的时候自己互相挤,需要和队列优先级联合使用。我还习惯配合 lsb.params 里的MBO_ACTIVE=Y打开多目标公平调度,否则 LIMIT 可能只在队列层面生效。
5.3 集群间互连:openlava 多集群配置
如果你的集群规模超过几百台,或者有异地机房,openlava 原生支持管理多个集群。配置起来不复杂,在 lsf.cluster 里定义集群名,然后启动远端守护进程。
# lsf.cluster.mycluster 中增加远端集群 ClusterName mycluster Begin ClusterConfig CLUSTER remote_cluster HOSTS remote01 remote02 End ClusterConfig这样做的价值是,本地作业可以通过 -m remote_cluster 指定提交到远端集群,或者用 rload 配置动态调度。注意多集群模式必须有主从节点之间的 TCP 信任关系,建议在 master 和 slave 之间配 SSH 免密登录。授权方面,要在远端集群的 lsf.cluster 里也加上本集群的 ClusterName,否则握手会拒绝。
5.4 与容器负载共存:临时把 openlava 作业桥接到 Docker
最后提一个我很常用的混搭法:openlava 调度时长任务,K8s 管微服务,中间留一个过渡。具体做起来就是,在 bsub 命令里直接调用 docker run,让调度器作为外层主管,docker 作为隔离环境:
bsub -q normal -n 4 -R "rusage[mem=4096]" -o %J.log docker run --rm -v /data:/data openlava_job:latest /data/run.sh这里的技巧是 -o 和 -e 由 openlava 捕获,docker 容器内不再单独写日志,这样能复用旧的 log 路径。容器跑完退出码会传递给调度器,bjobs 能正常显示 DONE/EXIT。这个小技巧在集群既有原生作业又有容器作业时非常有用,不用再去维护第二套调度。不过要注意,容器镜像要在所有计算节点上提前拉取好,否则每次作业都会卡在docker pull上。
6. 最后一个技巧:用脚本自动清洗僵尸作业
作业提交后长时间停在 PEND 状态,是集群运维里最常见的隐形杀手。特别是一些新手提交的作业参数写错,挂在队列里几天不动,既占着调度器的心跳资源,又让后排的合法作业跟着受罪。我写过一个简单粗暴的清洗脚本,定期杀掉超时挂起的作业。
#!/bin/bash # 清理 PEND 时间超过 3 小时的作业 threshold=180 for jobid in $(bjobs -p 2>/dev/null | awk 'NR>1{print $1}'); do pend_time=$(bjobs -o "PEND_TIME" $jobid 2>/dev/null | tail -1) minutes=$(echo $pend_time | awk -F: '{print $1*60+$2}') if [ "$minutes" -gt "$threshold" ]; then bkill -r $jobid logger -t openlava "Killed hung job $jobid PEND time $minutes min" fi done逻辑说明:bjobs -p列出所有 PEND 的作业,取第一列作业 ID。bjobs -o "PEND_TIME"输出该作业等待的时长,格式是 MM:SS,换算成分钟。超过阈值后bkill -r强制杀掉,并写一条日志用于追溯。注意 -r 是强制杀,对正在运行且有断点续算的作业要千万小心,所以我加了注释和日志。
实际落地时我把阈值设成 180 分钟,并用 cron 每半小时跑一次。还有一个细节:这个脚本只适用于调度器没有动态优先级调整的场合,如果你们用了bswin之类的时间窗口策略,PEND 时间长的作业可能有特殊优先权,直接杀会引发业务投诉。
从那以后,我每次部署 openlava 都会强制走一遍这个脚本加黑白名单逻辑,把核心任务和执行人加入白名单,其他作业超过阈值一律回收。这套东西虽然简单,但帮我避免过不少集群因僵尸作业被拖垮的事故。希望帮到你。
本文还有配套的精品资源,点击获取