ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Slurm 20.02.3 slurmrestd 实战:JWT认证与REST API接口调用指南

Slurm 20.02.3 slurmrestd 实战:JWT认证与REST API接口调用指南

1. 为什么要在 Slurm 20.02.3 上折腾 slurmrest

集群用久了,命令行那套squeue、sbatch、scontrol确实顺手,但一旦要把作业调度能力接进内部平台、监控面板或者自动化流水线,靠subprocess去拼命令行、再正则解析文本输出,维护成本会高得离谱。Slurm 从 20.02 版本开始正式把slurmrestd这个 REST API 守护进程纳入发行版,20.02.3 是这条线上一个相对稳定的补丁版本,它让外部程序可以用标准 HTTP 接口去查询节点、提交作业、管理分区,而不用再和命令行文本格式死磕。

这篇内容面向的是刚接触 slurmrest 的运维和平台开发同学。我会把 20.02.3 这个特定版本下 slurmrestd 的启动方式、认证机制、核心接口调用、以及实际踩过的坑讲清楚。需要先说明一点:Slurm 的 REST API 在 20.02 到 21.08 之间经历过比较大的演进,20.02.3 属于早期形态,接口路径、认证头、数据格式和后来的版本差异不小,网上很多教程其实是针对 21.08 之后的,直接照搬会各种报错。所以这篇专门锁定 20.02.3,把这一版的真实用法还原出来。

先给一个整体认知:slurmrestd 是一个独立的守护进程,它自己不直接读slurm.conf去操作集群,而是通过 Slurm 的C 库接口(libslurm)与slurmctld通信。这意味着它必须运行在一个能访问到slurmctld的节点上,并且编译时链接的 Slurm 库版本要和集群一致。20.02.3 里它支持两种认证方式:JWT和SOCKET,前者适合跨网络的 HTTP 调用,后者适合本机 Unix socket 调用。理解这两条路径的区别,是后面所有操作的基础。

我见过不少人在这一步就卡住:装好了 slurmrestd,curl一发请求就返回 401 或者直接连接被拒,然后开始怀疑人生。其实多数情况不是接口写错了,而是认证没配对,或者守护进程根本没监听在你以为的地址上。下面我会按“先跑起来、再调通、最后用顺”的顺序展开,每一步都给出这一版特有的细节。

2. slurmrestd 在 20.02.3 里的启动方式与监听配置

2.1 二进制从哪来、依赖什么

20.02.3 的 slurmrestd 通常随 Slurm 主包一起安装,但有些发行版的打包会把它拆到单独的包里。你可以先用which slurmrestd确认,如果没有,检查一下是不是漏装了带 REST 支持的组件。它运行时会依赖libslurm、libslurmdb,以及 HTTP 解析相关的库(这一版内部用的是 libhttp_parser 一类的组件)。用ldd $(which slurmrestd)看一眼动态链接,能快速判断依赖是否齐全。

一个容易被忽略的点:slurmrestd 链接的 Slurm 库版本必须和slurmctld完全一致。20.02.3 的协议在 minor 版本间是兼容的,但如果你在 20.02.3 的集群上跑了一个 20.11 编译的 slurmrestd,通信时可能因为数据结构变化而出现莫名其妙的解析错误。所以最稳妥的做法是:slurmrestd 和 slurmctld 用同一份源码、同一套编译参数构建。

2.2 两种监听模式:TCP 与 Unix Socket

这一版 slurmrestd 支持通过-参数指定监听地址,格式是host:port或者unix:/path/to/socket。举几个实际会用到的例子:

# 监听所有网卡的 6820 端口,走 TCP slurmrestd 0.0.0.0:6820 # 只监听本机回环,安全性更高 slurmrestd 127.0.0.1:6820 # 走 Unix socket,适合同机调用 slurmrestd unix:/var/run/slurmrestd.socket

这里有个 20.02.3 特有的行为:如果你不指定认证方式,它默认会尝试用SOCKET认证,也就是依赖 Unix socket 的对端身份。但如果你监听的是 TCP,SOCKET 认证就没法用了,必须显式启用 JWT。很多人启动时不加参数,然后从另一台机器curl,结果一直 401,就是因为这个默认行为。

启动时还可以用-a指定认证插件,用-t指定超时,用-v提高日志级别。调试阶段我强烈建议加上-vv,它会把每个请求的解析过程和认证结果打到 stderr,排查问题时能省掉大量猜测。

2.3 用 systemd 托管时的几个关键字段

生产环境一般会用 systemd 拉起 slurmrestd。20.02.3 官方没有自带 unit 文件,需要自己写。下面是一个经过验证的模板:

[Unit] Description=Slurm REST daemon After=slurmctld.service Requires=slurmctld.service [Service] Type=simple User=slurm Group=slurm Environment=SLURM_JWT=daemon ExecStart=/usr/sbin/slurmrestd -a rest_auth/jwt -vv 0.0.0.0:6820 Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target

几个要点:After和Requires保证它等slurmctld起来后再启动;User必须是能读取 Slurm 配置和认证密钥的账号;Environment=SLURM_JWT=daemon是这一版启用 JWT 认证的关键环境变量,少了它 JWT 插件不会加载。我踩过一次坑:unit 里忘了设这个变量,服务看着是起来了,但所有 JWT 请求都返回“认证插件未启用”,查了半天才发现是环境变量的问题。

3. JWT 认证在 20.02.3 中的生成与使用细节

3.1 这一版的 JWT 和后来版本有什么不同

20.02.3 的 JWT 实现还比较原始。它用的签名算法是 HS256,密钥来自slurm.conf里AuthAltTypes和AuthAltParameters的配置,或者由scontrol token命令动态生成。和 21.08 之后引入的jwt_key文件、RS256 支持相比,这一版没有非对称签名,密钥是共享的,所以密钥文件的权限管理格外重要,一旦泄露,任何人都能伪造合法 token。

生成 token 最直接的方式是用scontrol token:

scontrol token username=root lifespan=3600

它会输出类似SLURM_JWT=eyJhbGciOi...的一行。这里的username是 token 归属的用户,lifespan是有效期(秒)。注意:这个命令需要在能访问slurmctld的节点上执行,因为它要向控制器申请。生成的 token 直接拿来做 HTTP 请求的 Bearer 头即可。

3.2 把 token 塞进请求头的正确姿势

拿到 token 后,请求头这样写:

TOKEN=$(scontrol token username=root lifespan=3600 | cut -d= -f2) curl -H "X-SLURM-USER-TOKEN: $TOKEN" \ -H "X-SLURM-USER-NAME: root" \ http://127.0.0.1:6820/slurm/v0.0.36/ping

注意两个头:X-SLURM-USER-TOKEN放 token,X-SLURM-USER-NAME放用户名。20.02.3 里这两个头是必须同时提供的,只给 token 不给用户名,服务端无法确定以哪个用户身份执行操作,会直接拒绝。这一点和后来某些版本只用 token 就能推断用户名的行为不一样,是这一版特有的要求。

还有一个细节:token 里的用户名和请求头里的X-SLURM-USER-NAME必须一致,否则会返回权限错误。我见过有人用 root 生成的 token,却在头里写了普通用户名,结果一直 403,排查时完全没往这个方向想。

3.3 token 过期与刷新策略

lifespan设得太短,自动化脚本跑一半就失效;设得太长,安全性又打折扣。我的经验是:交互式调试用 3600 秒,后台服务用 86400 秒并配合定期刷新。刷新逻辑很简单,就是在 token 快过期时重新调一次scontrol token,把新值替换掉。如果你的服务是用某个固定账号跑的,可以在启动脚本里先申请一个长有效期 token,写进环境变量,再由服务读取。

需要提醒的是,20.02.3 没有提供 token 吊销接口,一旦签发,在有效期内始终可用。所以不要把 token 硬编码进代码仓库,也不要在日志里打印完整 token。我一般会在日志里只打前 8 位和后 8 位,中间用星号代替,既方便排查又不泄露。

4. 核心接口的调用逻辑与数据格式

4.1 接口路径里的版本号不是随便写的

20.02.3 的 REST 路径形如/slurm/v0.0.36/...,这个v0.0.36是API 数据版本,不是 Slurm 版本号。它对应的是这一版 Slurm 内部数据结构的版本标识。如果你把路径写成/slurm/v0.0.35/或者干脆不写版本,服务端会返回 404 或版本不匹配错误。所以第一步永远是确认当前 slurmrestd 支持的版本号,可以用:

curl -H "X-SLURM-USER-TOKEN: $TOKEN" \ -H "X-SLURM-USER-NAME: root" \ http://127.0.0.1:6820/slurm/v0.0.36/ping

返回{"ping": "pong"}就说明版本号对了、认证也通了。这个 ping 接口是我建议每个人第一个去调的,它能一次性验证三件事:网络通不通、认证对不对、版本号准不准。

4.2 查询类接口:节点、分区、作业

查询节点用/slurm/v0.0.36/nodes,查询分区用/slurm/v0.0.36/partitions,查询作业用/slurm/v0.0.36/jobs。这几个接口都支持在路径后加具体名称,比如/slurm/v0.0.36/nodes/node01只查某个节点。返回的是 JSON,字段名和scontrol show的字段基本能对应上,但结构是嵌套的,需要花点时间熟悉。

举个实际例子,查所有节点并只看状态和 CPU 数:

curl -s -H "X-SLURM-USER-TOKEN: $TOKEN" \ -H "X-SLURM-USER-NAME: root" \ http://127.0.0.1:6820/slurm/v0.0.36/nodes \ | python3 -c " import sys, json d = json.load(sys.stdin) for n in d['nodes']: print(n['name'], n['state'], n['cpus']) "

这里有个 20.02.3 的特点:state字段返回的是字符串数组而不是单个字符串,因为一个节点可能同时处于多个状态(比如IDLE和DRAIN)。解析时要注意它是 list,直接当字符串用会出错。我第一次写解析脚本时就栽在这,后来统一用",".join(n['state'])处理才稳。

4.3 提交作业:POST 请求的 body 结构

提交作业用POST /slurm/v0.0.36/job/submit,body 是 JSON。20.02.3 的字段命名和sbatch参数有对应关系,但用的是 JSON 的驼峰或下划线风格,需要对照文档。一个最小可用的提交示例:

curl -s -X POST \ -H "X-SLURM-USER-TOKEN: $TOKEN" \ -H "X-SLURM-USER-NAME: root" \ -H "Content-Type: application/json" \ -d '{ "script": "#!/bin/bash\nhostname\nsleep 10\n", "job": { "name": "rest_test", "partition": "debug", "tasks": 1, "cpus_per_task": 1 } }' \ http://127.0.0.1:6820/slurm/v0.0.36/job/submit

注意script字段里换行要用\n,整个脚本作为字符串传入。job对象里放调度参数。返回里会带job_id,拿到后可以用/slurm/v0.0.36/job/{job_id}去查状态。这一版的提交接口对字段校验比较严格,写错一个字段名可能整个请求被拒,但错误信息还算清楚,会告诉你哪个字段不合法。

4.4 取消与控制作业

取消作业用DELETE /slurm/v0.0.36/job/{job_id},这个比较直观。控制作业(比如 hold、release)在 20.02.3 里支持得有限,部分操作需要通过提交时带参数或者用scontrol配合完成。如果你的场景强依赖运行时控制,建议先确认这一版是否覆盖了你需要的操作,不要想当然地以为所有scontrol功能都有 REST 对应。

5. 实测中反复出现的坑与排查链路

5.1 401 与 403:先分清是认证还是授权

401 通常是 token 缺失、过期或格式不对;403 是认证过了但权限不够。排查顺序应该是:先用 ping 接口确认认证链路通不通,通了再去看具体接口的权限。如果 ping 都 401,问题一定在 token 或请求头;如果 ping 通了但某个接口 403,那多半是X-SLURM-USER-NAME对应的用户在 Slurm 里没有相应权限。

我遇到过一次很隐蔽的情况:token 是用 root 生成的,请求头也写了 root,但 slurmrestd 进程是以slurm用户跑的,而slurm用户没有读取某个配置的权限,导致它在解析用户身份时失败,返回了一个看起来像认证失败的 401。后来把 slurmrestd 的运行用户调整到有足够权限的账号才解决。所以守护进程的运行身份也是排查链路里必须确认的一环。

5.2 版本号写错导致的 404

前面提过路径里的v0.0.36,但实际部署时这个数字可能因为编译选项不同而有差异。最可靠的办法是看 slurmrestd 启动日志,它会在启动时打印支持的 API 版本。如果日志级别不够,用-vv重启一次就能看到。不要凭记忆写版本号,每次部署新环境都确认一遍。

5.3 JSON 解析失败与字段类型陷阱

20.02.3 对 JSON 的解析比较严格,多余字段、类型不匹配都会导致 400。常见的有:把数字写成字符串、把数组写成单值、字段名大小写错误。排查时把-vv日志打开,它会打印出解析失败的具体位置。另外前面提到的state是数组这一点,在写解析代码时一定要按数组处理,否则你的监控脚本会在某些节点进入多状态时突然崩溃。

5.4 长连接与超时设置

slurmrestd 默认的超时可能不适合大批量查询。如果你要一次性拉取上千个作业的信息,请求可能在中途超时。这一版可以用-t参数调整超时,也可以在客户端设置合理的curl --max-time。我的做法是:查询类请求超时设 30 秒,提交类设 60 秒,并在客户端做重试,重试时注意幂等性——提交作业的重试可能导致重复提交,需要配合业务层的去重逻辑。

6. 把 slurmrest 接进实际工作流的经验

6.1 封装一层薄客户端比直接裸调靠谱

直接在业务代码里拼 URL 和 JSON,短期看省事,长期是灾难。我的做法是封装一个薄客户端,把版本号、认证头、超时、重试都收进去,业务层只调client.submit_job(...)、client.get_nodes()这样的方法。这样当 Slurm 升级、API 版本变化时,只需要改客户端一处。20.02.3 到后续版本的接口变动不小,这层封装能帮你把迁移成本降到最低。

6.2 监控场景下的轮询频率

用 REST 做集群监控时,轮询频率要控制好。slurmrestd 每次请求都会去slurmctld取数据,频率太高会给控制器带来额外压力。我的经验是:节点和分区状态 30 秒一次足够,作业列表 10 到 15 秒一次,单个作业状态按需查询。如果集群规模大,考虑用slurmdbd的历史数据接口做聚合,而不是频繁打实时接口。

6.3 日志与审计

slurmrestd 的访问日志默认比较简单,建议在它前面放一层反向代理,由代理记录完整的请求日志和响应码。这样既能做审计,又能在出问题时快速定位是哪个调用方、哪个接口出的错。token 在日志里要做脱敏,这一点前面强调过,不再重复。

6.4 这一版值不值得用在生产

说实话,20.02.3 的 slurmrest 功能还比较基础,接口覆盖面和稳定性都不如后来的版本。如果你的集群短期内不打算升级,用它做只读查询和简单提交是可行的;但如果要做复杂的作业编排、精细的权限控制,建议评估升级到更新的 Slurm 版本。我个人的做法是:在 20.02.3 上先用 REST 把监控和简单自动化跑通,积累经验,等升级窗口到了再平滑迁移到新接口。这样既不耽误当前需求,也为后续演进留了余地。

最后分享一个我在调试时常用的小技巧:把scontrol token和curl包成一个 shell 函数,每次调用自动申请新 token 并带上正确的头,省去手动复制的麻烦。这个函数我放在~/.bashrc里,调试效率提升非常明显。函数大概长这样:

slurm_api() { local path="$1"; shift local token token=$(scontrol token username=root lifespan=600 | cut -d= -f2) curl -s -H "X-SLURM-USER-TOKEN: $token" \ -H "X-SLURM-USER-NAME: root" \ "$@" "http://127.0.0.1:6820/slurm/v0.0.36/${path}" }

用的时候slurm_api ping、slurm_api nodes就行,token 每次自动刷新,不用担心过期。这个习惯帮我在排查认证类问题时省了大量时间,推荐你也试试。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进