跳转至

Slurm 26.05 主备集群部署 SOP

实现体:工程根(Python 部署工程)。第 5 章操作序列用于人工阅读与复核, 以实现体为准;幂等、备份、回滚均由实现体保证,文档中的 shell 片段不承担错误处理。 日常使用与故障处置见同目录 RUNBOOK.md;本次部署排障过程见 INCIDENT-20260918-slurm-26.05部署排障.md。

目录

一、背景

原 Slurm 集群运行在 CentOS 7 与 k8s 共存的环境(21.08 → 26.05),节点为 k8s-master-01、k8s-node-01~03, 部署工具只支持单主、配置模板硬编码、健康保障仅有一份 SOP 而无安装逻辑。

本次将部署工具改造为通用安装部署脚本,并在新环境(Rocky Linux 9.8,5 台独立 VM)执行实际部署。 新增三项能力开关:

需求 开关 说明
主备集群 slurm.ha.enabled 多控制节点 + NFS 共享 StateSaveLocation
应用特殊配置 slurm.custom_config.enabled 以 slurm_conf_old/ 配置为模板做环境变量替换
集群健康保障 slurm.health_ensure / --health-ensure 机制A 内置 HealthCheck + 机制B 巡检自愈

首次部署完成后,又针对实测暴露的两个问题做了修复(作为本次变更的收尾):

  1. 共享存储写入慢:slurmctld 持续告警 Saving to StateSaveLocation took ~0.6s
  2. 两条启动告警:Configured MailProg is invalid、cannot create http_parser context

再之后又补齐了用户可用性层面的三项能力(第二轮改造)。起因是实测截图暴露两个问题: root 执行 sinfo 报 command not found,普通 LDAP 用户 su - cy01 报 user cy01 does not exist。

需求 开关 说明
命令 PATH slurm.path_env.enabled 写 /etc/profile.d 片段,登录用户(含 root)直接用 sinfo / squeue / sbatch,不必写全路径
LDAP 用户可用 user_mgmt.system_auth.enabled / --node-env sssd + authselect,全部节点经 NSS/PAM 解析并认证 LDAP 用户,su - <user> / ssh <user>@<node> 可用
共享家目录 home_share.enabled 各节点 /home 挂载同一份 NFS 导出,用户在任一节点看到同一份家目录

第三轮改造补齐软件环境栈能力:引入 Lmod 环境模块体系(EDA 行业标准做法), 把工具版本、许可证与路径从"写死在用户脚本里"改为"按需 module load"。

需求 开关 说明
环境模块(Lmod) module_env.enabled / --module-env Lmod 软件栈 + 工具家族分层的 modulefiles + Slurm 集成(TaskProlog 透传)

二、目标

变更完成后应达到的可验证状态:

  1. 5 节点集群就绪:3 个计算节点 idle,scontrol ping 返回 primary 与 backup 均 UP。
  2. 主备成型:slurm.conf 含两条 SlurmctldHost,StateSaveLocation 位于 NFS 共享目录, 备控 slurm-master-02 上 slurmctld 同为 active。
  3. 特殊配置生效:EDA 四分区(interactive / compile / regression / long)、 4 级 QoS 与账户层级已建立,Prolog/Epilog 钩子脚本已落位;License TRES 项按选择剔除。
  4. 记账可用:sacct 可查作业历史,sacctmgr show qos 返回 4 条。
  5. 健康保障就绪:3 个计算节点 HealthCheck 退出码 0;主控 slurm-cluster-health.timer 为 active 且 在 slurmctld 重启后仍保持 active。
  6. 共享存储写入延迟 ≤ 50ms,slurmctld 无 Saving to StateSaveLocation 告警。
  7. 启动日志中不再出现 MailProg is invalid 与 http_parser 相关错误。
  8. 幂等达标:部署与健康保障各自复跑一次,第二次零变更、全跳过。
  9. 命令可直接用:5 台节点任意登录 shell 中 command -v sinfo 返回 /usr/local/slurm/bin/sinfo。
  10. LDAP 用户全节点可用:5 台节点 getent passwd cy01 返回 cy01:*:4001:4000:..., 且 su - cy01 -c 'id -u' 输出 4001(即 csh 与家目录都到位)。
  11. 家目录共享:5 台节点 findmnt -o SOURCE /home 均为 192.168.31.249:/data/nfs/home, 且 findmnt -o OPTIONS /home 中含 soft(选项与 yaml 一致)。
  12. 上述三项复跑一次 --node-env 同样零变更、全跳过;且主部署器复跑仍为零变更, 证明新增能力未破坏既有幂等。
  13. 环境模块就绪:5 台节点登录 shell 可用 module --version(Lmod 8.7.65), module avail 列出 /opt/modulefiles/Core 下的家族入口与 slurm/<版本>。
  14. 模块按层次生效:module load synopsys 后 module avail 出现 vcs/2023.12; 同族互斥(再 module load cadence 自动替换 synopsys);跨族可共存(pdk + synopsys)。
  15. 工具守卫有效:工具未安装时 module load vcs/2023.12 明确报错"工具尚未安装或路径待配置", 不会"加载成功但命令找不到"。
  16. Slurm 集成有效:作业脚本内 source /etc/profile.d/zzz_site_lmod.sh 后可用 module; 且既有 EDA TaskProlog(task_prolog_eda.sh 注入的 EDA_JOB_ID / EDA_JOB_NODELIST / EDA_WORKDIR)经透传后仍在作业环境中生效。
  17. 环境模块复跑 --module-env 与主部署器 dry-run 均零变更、全跳过。

三、前置条件

项目 要求 核实方式
节点 192.168.31.16~20,Rocky Linux 9.8,systemd 252,cgroup v2 stat -fc %T /sys/fs/cgroup → cgroup2fs
登录 部署机 root 对 5 台免密(密钥认证) ssh -o BatchMode=yes root@<ip> hostname
编译节点 192.168.31.18,8 核 / 15G / ≥50G 空闲 ssh root@192.168.31.18 "nproc; free -g; df -h /"
目标系统 RHEL 系 9.x(rhel/rocky/almalinux/centos)、x86_64 由 pre_check 自动校验(os_check),不符即报错退出;/etc/os-release 不存在也会拦下
节点规格 cpus / memory_mb 可留空,由部署器探测真机 留空时用 nproc + /proc/meminfo 探测;auto_detect.memory_ratio 控制 RealMemory 占物理内存比例(默认 0.95)
出网 download.schedmd.com 与 dnf 源可达 curl -sI https://download.schedmd.com/slurm/
NFS 宿主机 192.168.31.249 导出 /data/nfs/slurm-state(async) 见第 5.6 节;exportfs -v 应含该行
数据库 MariaDB 可达且 database.password 已填写 config/slurm_deploy.yaml,权限 600
运行环境 工程内 venv:.venv,依赖见 requirements.txt .venv/bin/python -c "import paramiko, yaml"
LDAP(--user-mgmt 需) 控制节点需装 openldap-clients;到 LDAP 的 389 端口可达;bind 凭据可用 由 pre_check 自动校验,不符合即汇总列出并退出
LDAP(--node-env 需) 5 台节点到 LDAP 389 端口均可达;bind 凭据可用;LDAP 中用户带 uidNumber/gidNumber 同理由 pre_check 自动校验;库中缺 uidNumber 时应改 system_auth.id_mapping: true
NFS(家目录) 宿主机 192.168.31.249 已导出 /data/nfs/home(async) showmount -e 192.168.31.249 应含 /data/nfs/home
NFS(modulefiles) 宿主机 192.168.31.249 已导出 /data/nfs/modulefiles(async) showmount -e 192.168.31.249 应含该行
出网(EPEL) Lmod 不在 Rocky 的 baseos/appstream 内(那里只有 Tcl 版 environment-modules);epel-release 由 Rocky 的 extras 仓库提供,装完后需能拉取 EPEL 元数据 dnf install -y epel-release && dnf list --available Lmod 有结果
SELinux Rocky 9 默认 Enforcing,NFS 家目录需放通 部署器自动 setsebool -P use_nfs_home_dirs on,无需手工
用户 loginShell LDAP 中用户 loginShell 若为 /bin/csh,节点须有该 shell system_auth.extra_packages: ["tcsh"](Rocky 9 默认不含 csh)
端口 6817 / 6818 / 6819 未被占用 ss -lntp \| grep -E '6817\|6818\|6819'
防火墙 firewalld 若运行,需放通集群端口 部署器自动处理,无需手工
软件源 Rocky 9 的 -devel 包位于 CRB 仓库(默认 disabled) 部署器按需启用,无需手工

凭据(database.password、database.admin_password)只存放在 config/slurm_deploy.yaml, 该文件权限 600 且已加入 .gitignore;本文档不记录任何凭据原文,需要时从该文件读取。

执行前检查:

cd /home/rain/Documents/slurm_ins

# 1. 依赖就绪
.venv/bin/python -c "import paramiko, yaml; print('deps ok')"

# 2. 5 台免密
for ip in 16 17 18 19 20; do ssh -o BatchMode=yes root@192.168.31.$ip hostname; done

# 3. NFS 可达
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/192.168.31.249/2049' && echo "NFS reachable"

# 4. 数据库端口可达
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/192.168.31.10/3306' && echo "DB reachable"

# 5. 家目录导出已存在(`--node-env` 需要)
showmount -e 192.168.31.249 | grep '/data/nfs/home' && echo "home export ok"

# 6. modulefiles 导出已存在(`--module-env` 需要)
showmount -e 192.168.31.249 | grep '/data/nfs/modulefiles' && echo "modulefiles export ok"

四、影响范围

主机 角色 主要变更
.16 slurm-master-01 slurmctld(主) + slurmdbd 装 munge/nfs-utils/mariadb 客户端、挂载 NFS、解压产物、建服务、起 slurmdbd/slurmctld
.17 slurm-master-02 slurmctld(备) 同上,不含 slurmdbd;共享同一 StateSaveLocation
.18 slurm-work-01 slurmd + 编译节点 额外装构建依赖、下载源码编译、分发产物
.19 / .20 slurmd 同 .18(不含编译)
  • 是否停服:本次为全新安装,5 台初始均无 Slurm、无业务,不停服。
  • 窗口期:编译阶段(8 核)约 8 分钟,其间占用 .18 的 CPU 与磁盘 IO;其余节点无感。
  • 再次执行的影响:实现体先做状态巡检,一致即跳过;仅在检测到漂移时重启服务(约 10–20 秒调度中断)。
  • 有作业时的约束:若在已有业务的集群上执行,须先确认 squeue 为空,且不得对有作业的节点做变更。
  • 不可逆项:/usr/local/slurm-26.05 会被覆盖(实现体先备份);/etc/hosts 追加标记段; /etc/fstab 追加 NFS 条目;宿主机 /etc/exports 追加一行(改动前已备份,见第 7 章)。

第二轮改造(2026-09-21)的实际变更

主机 主要变更
.16 ~ .20(全部 5 台) 装 sssd / sssd-ldap / oddjob / oddjob-mkhomedir / tcsh;写 /etc/sssd/sssd.conf(600);authselect select sssd with-mkhomedir;启用 sssd 与 oddjobd;/home 挂载 NFS 并写 fstab;setsebool -P use_nfs_home_dirs on;写 /etc/profile.d/slurm-path.sh
宿主机 192.168.31.249(dell-R620,即部署机本机) 新建 /data/nfs/home 并新增 NFS 导出(async,改前已备份 /etc/exports)
  • 是否停服:不停任何 Slurm 服务,调度不中断。执行前 squeue 为空,作业面影响为零。
  • 窗口期:5 台各约 4–5 分钟(主要是 dnf 装包),全流程串行约 9 分钟。
  • 被改写的系统配置:/etc/nsswitch.conf、/etc/pam.d/*(由 authselect 接管)、/etc/fstab、 /etc/sssd/sssd.conf(新增文件)。变更前均已备份到各节点 /var/backups/slurm/node_env_<时间戳>/。
  • 不可逆项:无数据删除动作;/home 被 NFS 覆盖挂载(挂载前该目录为空目录,无内容被遮蔽)。
  • 回滚:--node-env --rollback 可卸载三项能力;PAM/NSS 若需还原须从上述备份目录人工覆盖(见 7.5)。

第三轮改造(2026-09-21~22)的实际变更

主机 主要变更
宿主机 192.168.31.249 新建 /data/nfs/modulefiles 并新增 NFS 导出(async,改前已备份 /etc/exports)
.16 ~ .20(全部 5 台) 装 epel-release + Lmod / lua / lua-posix;挂载 /opt/modulefiles ← NFS;写 /etc/lmod/lmod_config.lua 与 /etc/profile.d/zzz_site_lmod.sh;分发 /etc/slurm/task_prolog_lmod.sh
.16(主控,写共享内容) 生成 10 个 modulefile:Core/ 下 4 个家族入口 + site.lua + slurm/<版本>,以及 4 个家族下各 1 个示例工具模块
.16 / .17(控制节点) slurm.conf 的 TaskProlog 由 task_prolog_eda.sh 接管为 task_prolog_lmod.sh(新脚本内部透传原脚本)
  • 是否停服:不停 slurmctld / slurmdbd;但为让 TaskProlog 生效会 scontrol reconfigure 并重启各计算节点的 slurmd —— 该节点在跑的作业会被中断,故执行前须确认 squeue 为空。
  • 窗口期:5 台串行约 7 分钟(主要是 EPEL 首次拉元数据与装包)。
  • 被改写的系统配置:各节点 /etc/fstab(新增 /opt/modulefiles 行)、/etc/slurm/task_prolog_lmod.sh(新增)、/etc/profile.d/zzz_site_lmod.sh(新增)、/etc/lmod/lmod_config.lua(新增)、控制节点 slurm.conf(仅 TaskProlog 一行)。变更前均已备份。
  • 不可逆项:无数据删除动作;/opt/modulefiles 原为空目录,无内容被遮蔽。

五、分步操作

5.1 准备运行环境

cd /home/rain/Documents/slurm_ins

python3 -m venv .venv                                    # 建工程 venv(仅首次)
.venv/bin/python -c "import sysconfig, pathlib; \
  p = pathlib.Path(sysconfig.get_paths()['purelib']) / 'py_public_modules.pth'; \
  p.write_text('/home/rain/py_public_modules', encoding='utf-8')"     # 挂载共享库(仅首次)
.venv/bin/python -m pip install -r requirements.txt

5.2 填写配置

vi config/slurm_deploy.yaml       # 含凭据,权限保持 600
# 必填:database.password(同时作为 slurmdbd.conf 的 StoragePass)
# 开关:slurm.ha.enabled / slurm.custom_config.enabled / slurm.health_ensure
# 节点:nodes.control[].primary 标记主控;nodes.compute[] 的 cpus/memory_mb 可留空(自动探测)
# 环境:os_check 校验目标发行版/主版本/架构;auto_detect 控制规格探测与内存比例
# 换用其它集群导出的模板时:custom_config.legacy 全部留空即可(从模板自动提取旧主机名/
# IP/安装路径);分区 Nodes= 会按 yaml 的节点与 partitions 定义重算,不依赖模板里的旧节点

5.3 预演(不改动任何系统状态)

.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --dry-run

预演会真实读取各节点状态,因此变更清单是实际待执行动作;输出末尾给出 「变更记录总数 / 待执行变更 / 幂等跳过」三项计数。

5.4 正式部署

.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --log-level INFO

实现体按 BaseDeployer 固定生命周期执行:pre_check → sync_status → backup → deploy → health_check。 sync_status 判定零变更时直接退出,不进入 deploy。

deploy 十三阶段(实现文件:slurm_deployer.py):

阶段 动作 实现方法
1 系统准备:dnf 装包(按需启用 CRB)、建 slurm 用户(固定 uid 64030)、写 /etc/hosts 段、放通 firewalld 端口、统一 munge key _stage_system_prepare
2 节点基础环境:命令 PATH / LDAP 认证(sssd + authselect)/ 共享家目录(含 SELinux 放通) _stage_node_env
3 环境模块:Lmod 软件栈 / 工具家族 modulefiles / 站点配置 / TaskProlog 脚本 _stage_module_env
4 记账库初始化:建库 / 建号 / 授权 _stage_provision_database
5 源码编译(自动探测 mariadb_config) _stage_build
6 打包编译产物并取回本地 _stage_pack
7 分发 + 解压,并按 ldd 结果补齐运行库 _stage_deploy_host / _install_runtime_libs
8 HA:挂载 NFS 共享目录、写 fstab、建 StateSaveLocation 子目录并 chown _stage_mount_shared_state
9 渲染 slurm.conf / slurmdbd.conf / cgroup.conf / topology.conf _stage_render_configs
10 分发 slurm_conf_old/pro_epilog/*.sh 作业钩子 _stage_distribute_prolog_scripts
11 安装机制A 健康检查程序 _stage_install_healthcheck
12 生成 systemd 单元 → 停旧 → 切 symlink → 按依赖顺序启动 _stage_create_systemd / _stage_start_new
13 确认记账链路(QoS 与账户已在阶段 12 内建好) _stage_init_dbd

阶段 2 与阶段 3 的判定与执行分别由 NodeEnvInstaller、ModuleEnvInstaller 提供, 与独立模式 --node-env / --module-env 共用同一份实现,两条入口不会行为分叉。

阶段 3 只落 modulefiles 与脚本,不碰 slurm.conf:TaskProlog 由阶段 9 的配置渲染统一写入 (_stage_module_env 传入 manage_slurm_conf=False),避免重复编辑与多余的服务重启。

关键顺序与高危动作(节选自实现体):

# —— 阶段 10 内部:启动顺序不可调换 ——
systemctl start slurmdbd                                   # 1) 先起记账后端
sacctmgr -i add cluster cluster                            # 2) 预创建 cluster 记录
sacctmgr -i add qos interactive                            # 3) 建 QoS:PartitionName 引用它,
                                                           #    晚于 slurmctld 创建会让后者直接 fatal
mkdir -p /etc/systemd/system/slurmctld.service.d           # 4) 首次启动:一次性 override
printf '[Service]\nExecStart=\nExecStart=<install>/sbin/slurmctld -D -s -i\n' \
  > /etc/systemd/system/slurmctld.service.d/init-state.conf #    state 目录为空时需 -i 重建
systemctl daemon-reload && systemctl start slurmctld && sleep 8 && systemctl stop slurmctld
rm -rf /etc/systemd/system/slurmctld.service.d             #    用完立即移除,避免每次重启覆盖 state
systemctl daemon-reload && systemctl start slurmctld       # 5) 正常启动主控,再启动备控
systemctl start slurmd                                     # 6) 最后起计算节点

# —— 阶段 9 停旧(高危):顺序 slurmd → slurmctld → slurmdbd ——
systemctl stop slurmd slurmctld slurmdbd

# —— 阶段 4 分发(高危:覆盖安装目录)——
rm -rf /usr/local/slurm-26.05 && tar xzf /tmp/slurm-bin-<date>.tar.gz -C /usr/local
ln -sfn /usr/local/slurm-26.05 /usr/local/slurm            # 切 symlink,旧目录保留以支持回滚

5.5 安装集群健康保障(可独立执行)

# 仅安装健康保障,不重新部署 Slurm;适用于任意已部署好的集群
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --health-ensure
# 机制A:内置 HealthCheck(slurmd 周期执行,失败自动 DRAIN 节点)
install -m 0755 slurm_health_check.sh /usr/local/bin/slurm_health_check.sh
# 在 slurm.conf 写入三个参数后重载:
#   HealthCheckInterval=300 / HealthCheckProgram=... / HealthCheckNodeState=ANY
systemctl restart slurmctld && systemctl restart slurmd

# 机制B:控制节点巡检守护
install -m 0644 cluster.json            /etc/slurm-health/cluster.json    # 拓扑由部署器生成,不硬编码
install -m 0755 cluster_health_check.py /usr/local/bin/cluster_health_check.py
install -m 0755 wait-for-slurmctld.sh   /usr/local/bin/wait-for-slurmctld.sh
systemctl daemon-reload
systemctl enable --now slurm-cluster-health.timer               # 每 15 分钟巡检一次
systemctl enable slurm-cluster-health-bootstrap.service         # 开机引导一次

timer 单元使用 Wants=slurmctld.service 而非 Requires=。后者会在 slurmctld 重启(停+起) 时把 timer 一并停止,导致巡检静默失效。

5.6 共享存储:迁移到专用 async 导出

首次部署时 StateSaveLocation 落在 /srv/nfs/k8s-hdd,该导出为 sync 且与 k8s 的 4 个 PVC 共用,实测单次 fsync 500–800ms,slurmctld 持续告警。不能直接改该导出为 async(会影响 k8s 持久化数据),因此新建专用导出。

宿主机 192.168.31.249(dell-R620)上执行:

sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d_%H%M%S)   # 高危:改 /etc/exports 前必备份
sudo mkdir -p /data/nfs/slurm-state
# 新增一行(async + 直接落盘,非 loop 镜像)
sudo sh -c 'printf "/data/nfs/slurm-state 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)\n" >> /etc/exports'
sudo exportfs -ra
sudo exportfs -v | grep slurm-state      # 预期:/data/nfs/slurm-state ... (async,...)

随后把 config/slurm_deploy.yaml 的 slurm.ha.shared_state.nfs_export 改为 /data/nfs/slurm-state, 再执行一次第 5.4 节。实现体会:停 slurmctld → umount 旧挂载 → 挂载新导出 → 更新 fstab → 重新启动。

5.7 安装用户管理能力(可独立执行,基本集群就绪后)

# 安装:落地同步脚本 + 运行配置 + systemd timer,并立即同步一次
.venv/bin/python main.py --config config/slurm_deploy.yaml --user-mgmt

# 卸载:停掉 timer、删除脚本与配置(已创建的账户与关联不回滚)
.venv/bin/python main.py --config config/slurm_deploy.yaml --user-mgmt --rollback

安装前会做环境检查,任一项不符合即汇总列出全部不符合项并退出,不做任何变更。检查项:

检查项 不符合时的提示
配置完整性 LDAP uri / base_dn / bind_dn / 凭据是否齐备
Slurm 是否已部署 {prefix}/etc/slurm.conf 是否存在(提示"请先完成 Slurm 集群部署")
守护进程 各控制节点 slurmctld 是否为 active
sacctmgr 可用 {prefix}/bin/sacctmgr 是否存在且可执行
集群名一致 yaml 的 ClusterName 与节点上 slurm.conf 是否一致
LDAP 客户端 控制节点是否装了 ldapsearch(附安装命令)
LDAP 连通性 各控制节点到 LDAP 端口的 TCP 可达性
LDAP 凭据与结构 用配置凭据 bind 并读取基准 DN,确认有对象

落地内容(实现文件 user_mgmt_assets.py):

# 同步脚本(Python,调用 ldapsearch + sacctmgr)
install -m 0755 slurm_user_sync.py /usr/local/bin/slurm_user_sync.py
# 运行配置与凭据
install -m 0644 config.json  /etc/slurm-usermgmt/config.json
install -m 0600 ldap.pass    /etc/slurm-usermgmt/ldap.pass     # 凭据不进命令行与日志
# systemd 定时任务
install -m 0644 slurm-user-sync.service /etc/systemd/system/
install -m 0644 slurm-user-sync.timer   /etc/systemd/system/
systemctl daemon-reload && systemctl enable --now slurm-user-sync.timer   # 默认每日 03:30(带随机延迟)
/usr/bin/python3 /usr/local/bin/slurm_user_sync.py                        # 安装后立即同步一次

同步规则(按当前 LDAP 的真实结构,见附录 B 第 7 条):

账户层级 来源 parent
frontend / analog / it / backend ou=* 无(基准 DN ou=eda 仅作容器不建账户)
design / dv cn=* frontend
custom / layout cn=* analog
cad / sre cn=* it
pnr / signoff cn=* backend

用户关联按 uid → gidNumber → 小组账户;ldapadmin 在排除列表内。清理(LDAP 中已消失的用户)默认启用,带三重保护:受保护列表、运行中作业跳过、LDAP 返回用户数低于阈值时整体跳过。

5.8 安装节点基础环境(可独立执行)

前置:宿主机已导出 /data/nfs/home(见 5.9)。 本模式不重启任何 Slurm 服务,可在有业务的集群上安全执行(执行前确认 squeue 为空更稳妥)。

# 安装:命令 PATH + LDAP 认证 + 共享家目录,覆盖全部节点
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --node-env

# 卸载:停 sssd/oddjobd、撤 authselect、卸 /home 挂载、删 profile.d 片段
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --node-env --rollback

安装前检查(任一项不符合即汇总列出全部不符合项并退出,不做任何变更):配置完整性、 5 台 SSH 连通、dnf / authselect / mount.nfs 可用、SELinux 为 Enforcing 时 setsebool 存在、 各节点到 LDAP 389 与 NFS 2049 的 TCP 可达。

落地内容(实现文件 node_env_assets.py / node_env_deployer.py):

# 1) 命令 PATH —— 登录用户(含 root)直接用 sinfo/squeue/sbatch
cat > /etc/profile.d/slurm-path.sh <<'EOF'
# Slurm 命令路径(由 slurm 部署器写入,请勿手工修改)
SLURM_PREFIX="/usr/local/slurm"
export PATH="$SLURM_PREFIX/bin:$SLURM_PREFIX/sbin:$PATH"
EOF
chmod 0644 /etc/profile.d/slurm-path.sh

# 2) LDAP 认证 —— 装包 → 写 sssd.conf → authselect → 起服务
dnf install -y sssd sssd-ldap oddjob oddjob-mkhomedir tcsh
install -m 0600 /tmp/sssd.conf /etc/sssd/sssd.conf   # 权限必须 600,否则 sssd 拒绝启动
systemctl enable sssd && systemctl restart sssd      # 此时 nsswitch 已含 sss
# 3. 确认能解析出用户后,才动 PAM(顺序不可颠倒)
for i in $(seq 1 30); do getent passwd cy01 >/dev/null 2>&1 && break; sleep 1; done
authselect select sssd with-mkhomedir --force        # ⚠️ 高危:接管 PAM 与 NSS
systemctl enable --now oddjobd                       # with-mkhomedir 依赖它建家目录

# 3) 共享家目录 —— 挂载 + 持久化 + SELinux 放通
mount -t nfs -o rw,soft,_netdev 192.168.31.249:/data/nfs/home /home   # ⚠️ 高危:覆盖挂载 /home
#   fstab 用字段精确匹配替换(awk '$2 != "/home"'),避免误删其它含 /home 字样的行
setsebool -P use_nfs_home_dirs on                    # SELinux Enforcing 下必须放通

/etc/sssd/sssd.conf 内容(bind 密码取自 yaml,不落日志):

[sssd]
config_file_version = 2
services = nss, pam
domains = rainchen

[domain/rainchen]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://192.168.31.10:389
ldap_search_base = dc=rainchen,dc=com
ldap_schema = rfc2307
ldap_default_bind_dn = cn=admin,dc=rainchen,dc=com
ldap_default_authtok = <LDAP bind 密码>
ldap_default_authtok_type = password
ldap_id_mapping = false
ldap_tls_reqcert = never
cache_credentials = true
enumerate = false

三个关键约束(踩过才知道):

  1. ldap_id_mapping = false:LDAP 里已带真实 uidNumber/gidNumber,必须用真值。 若用 sssd 默认的映射算法,同一用户在节点上的 uid 会是随机值,与 Slurm 记账对不上。
  2. 先 NSS 后 PAM:authselect 会接管 /etc/nsswitch.conf 与 /etc/pam.d/*。必须先起 sssd 并确认 getent passwd <user> 正常,再执行 authselect,避免"PAM 已改但解析不通"的中间态。 authselect 执行后立即校验 SSH 连通与 getent passwd root,任一异常就地回退为 authselect select minimal --force 并报错退出。
  3. loginShell 与家目录:LDAP 中用户 loginShell=/bin/csh,而 Rocky 9 默认不含 csh, 需 system_auth.extra_packages: ["tcsh"];家目录由 with-mkhomedir 首次登录时在 NFS 上自动创建。

关于家目录挂载选项(rw,soft,_netdev):

  • 用 soft:宿主机不可达时向应用返回 I/O 错误,而不是让访问家目录的进程进入不可 kill 的 D 状态(hard 下 su - <user> 与 SSH 登录会直接卡死)。代价是写操作超时可能出现 "应用报错、服务端已写入"的不一致;若更看重数据一致性,把 home_share.mount_options 改回 hard 即可,重跑会自动卸载重挂。
  • 不用 intr:内核 2.6.25 之后该选项已废弃(hard 挂载的操作本身可被 SIGKILL 中断),写上无效果。
  • 判定同时比对挂载源与挂载选项:只改 yaml 中的选项重跑也会正确触发重挂, 否则会出现"配置改了但实际挂载没变"的长期不一致。

5.9 共享家目录:宿主机新增 /home 导出

与 5.6 同属宿主机侧配套操作。宿主机 192.168.31.249 原本只导出了 k8s 与 slurm-state 相关目录,没有 /home 导出,需先新增。

宿主机 192.168.31.249(dell-R620)上执行:

sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d-%H%M%S)   # 高危:改 /etc/exports 前必备份
sudo mkdir -p /data/nfs/home
sudo sh -c 'printf "/data/nfs/home 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)\n" >> /etc/exports'
sudo exportfs -ra
sudo exportfs -v | grep '/data/nfs/home'      # 预期含 (async,...)

随后把 config/slurm_deploy.yaml 的 home_share.nfs_server / nfs_export 指向该导出,再执行 5.8。

家目录由各节点首次登录时经 with-mkhomedir 在共享目录上自动创建(实测 drwx------ cy01:custom /home/cy01)。 若希望预先批量创建,可在任一台已挂载 /home 的节点上按 LDAP 的 uid/gid 手工 mkdir + chown, 部署器不会覆盖已存在的家目录。

5.10 安装环境模块(Lmod,可独立执行)

前置:宿主机已导出 /data/nfs/modulefiles(见 5.11)。

# 安装:Lmod 软件栈 + 工具家族 modulefiles + 站点配置 + Slurm 集成,覆盖全部节点
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --module-env

# 卸载:撤 TaskProlog、删模块树与站点配置、卸共享挂载
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --module-env --rollback

⚠️ 安装与卸载都会 scontrol reconfigure 并重启各计算节点的 slurmd(TaskProlog 由 slurmd 执行), 该节点在跑的作业会被中断 —— 执行前须确认 squeue 为空。

安装前检查(任一项不符即汇总列出并退出):配置完整性、5 台 SSH 连通、dnf 可用、各节点到 NFS 2049 可达。

落地内容(实现文件 module_env_assets.py / module_env_deployer.py):

# 1) 软件栈 —— Lmod 不在 Rocky 官方仓库内,需先启用 EPEL
dnf install -y epel-release && dnf install -y Lmod lua lua-posix

# 2) 共享 modulefiles 挂载(与 /home 同规格;fstab 用 awk 字段精确匹配替换)
mount -t nfs -o rw,soft,_netdev 192.168.31.249:/data/nfs/modulefiles /opt/modulefiles

# 3) modulefiles 树 —— 内容只生成一次,落在共享存储上(NFS 模式下由主控写入)
#    /opt/modulefiles/
#    ├── Core/{site.lua, slurm/<版本>.lua, cadence.lua, synopsys.lua, siemens.lua, pdk.lua}
#    ├── cadence/IC/20.1.lua        # 示例:目录守卫 + PATH + 许可证变量
#    ├── synopsys/vcs/2023.12.lua
#    ├── siemens/calibre/2026.1.lua
#    └── pdk/sky130/1.0.lua         # 纯数据类,不注入 PATH

# 4) 站点级配置 —— 刻意不覆盖 Lmod 包自带的 modules.sh(那个归 rpm 管理)
#    /etc/lmod/lmod_config.lua        systemName / allow_prepend_abs_path
#    /etc/profile.d/zzz_site_lmod.sh  自包含:非 login shell 先初始化 Lmod,再前置 Core 路径

# 5) Slurm 集成 —— TaskProlog
install -m 0755 task_prolog_lmod.sh /etc/slurm/task_prolog_lmod.sh
#    slurm.conf 的 TaskProlog 由 config_renderer 渲染(独立模式做最小编辑)
scontrol reconfigure && systemctl restart slurmd     # 各计算节点

Core/synopsys.lua(家族入口)与 synopsys/vcs/2023.12.lua(工具模块)的形态:

-- 家族入口:把该家族的工具树挂进 MODULEPATH,并声明同族互斥
local tree = "/opt/modulefiles/synopsys"
if not isDir(tree) then LmodError("模块树不存在:" .. tree .. "(共享目录未挂载?)") end
prepend_path("MODULEPATH", tree)
family("eda_suite")        -- 同族互斥:加载 cadence 会自动替换 synopsys

-- 工具模块:安装目录守卫 + PATH + 工具专属环境变量
local dir = "/opt/eda/synopsys/vcs/2023.12"        -- TODO: 待工具就位后填真实路径
if not isDir(dir) then LmodError("工具尚未安装或路径待配置:" .. dir) end
prepend_path("PATH", pathJoin(dir, "bin"))
setenv("VCS_HOME", dir)
setenv("SNPSLMD_LICENSE_FILE", "27020@lic-server")

四个关键约束(实测踩过):

  1. Lmod 需 EPEL:Rocky 9 的 baseos/appstream 里没有 Lmod(只有 Tcl 版 environment-modules, 无 hierarchy / spider,不符合 EDA 标准)。epel-release 由 Rocky 的 extras 仓库提供。
  2. TaskProlog 是单值参数:模板原本指向旧集群的 task_prolog_eda.sh(注入 EDA_JOB_ID / EDA_JOB_NODELIST / EDA_WORKDIR)。接管后我们的脚本成为唯一入口,内部透传原脚本, 既有注入不失效;--module-env --rollback 会自动把该行恢复回原脚本。关闭 module_env.enabled 时渲染器也会保留模板原值,不会把这一行删掉。
  3. TaskProlog 只注入 LMOD 位置变量,绝不注入 MODULEPATH:TaskProlog 运行在 slurmd 侧, 看不到用户提交时加载了哪些模块;一旦 export MODULEPATH 就会覆盖用户 --export=ALL 的结果, 导致"提交前加载的模块在作业里全部丢失"。
  4. module 是 shell function,跨不过进程边界:Slurm 作业脚本是非 login shell。 作业脚本首行须 source /etc/profile.d/zzz_site_lmod.sh(该文件自包含:先初始化 Lmod, 再前置 Core 路径,一行即可)。

作业脚本的标准写法:

#!/bin/bash
#SBATCH -J sim -N 1
source /etc/profile.d/zzz_site_lmod.sh     # 必须:作业脚本是非 login shell
module load synopsys/vcs/2023.12           # 生产脚本建议写全版本号,保证可复现
vcs -full64 ...

两个使用注意: ① 不要写 module load xxx | head 这类管道 —— 管道会让 module 在子 shell 中执行,加载不生效; ② 若 module avail 看不到刚部署的模块,是 Lmod 的目录扫描缓存所致,执行 module --ignore_cache avail 或清理 ~/.cache/lmod 即可(部署器的健康检查已带 LMOD_IGNORE_CACHE)。

5.11 共享 modulefiles:宿主机新增导出

与 5.6 / 5.9 同属宿主机侧配套操作。

宿主机 192.168.31.249(dell-R620)上执行:

sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d-%H%M%S)   # 高危:改 /etc/exports 前必备份
sudo mkdir -p /data/nfs/modulefiles
sudo sh -c 'printf "/data/nfs/modulefiles 192.168.31.0/24(rw,async,no_subtree_check,no_root_squash)\n" >> /etc/exports'
sudo exportfs -ra
sudo exportfs -v | grep '/data/nfs/modulefiles'

随后把 config/slurm_deploy.yaml 的 module_env.nfs.nfs_server / nfs_export 指向该导出,再执行 5.10。

modulefiles 本身只是 10 个文本文件,放共享存储的意义是一处维护、全集群一致, 同时为后续 EDA 工具本体走 NFS 预留位置(工具体积大,绝不建议放各节点本地)。

5.12 modulefile 真实化:virtuoso IC25.10.000(2026-09-26,跨工程变更)

触发:Cadence IC25.10(ic25_install 工程)安装验收完成,5.10 的 cadence 占位 IC/20.1(/opt/eda/... 假路径)替换为真实工具;由 ic25 HANDOFF 待办 #0 发起, 经用户批准后实施。本节不手改 /opt/modulefiles(红线),一切经 --module-env。

变更内容(改前均备份于 docs/backup/,.bak.20260926-000421):

  1. config/slurm_deploy.yaml(样例同步):module_env.families.cadence 的 tools 换为 virtuoso / IC25.10.000,install_dir=/tools/cadence/virtuoso/IC25.10.000(节点客户端视图), bin_subdir=tools.lnx86/dfII/bin/64bit;license 变量不写(待 license 就绪)。
  2. 渲染器新增 prepend 字段(module_env_assets.py):变量 → 相对 install_dir 子目录列表, 渲染为 prepend_path——LD_LIBRARY_PATH 必须 prepend 保留既有值,原 env(setenv)会覆盖。 实测 virtuoso 的 210 个树内 not found 库分布于 26 个目录,全部列入 (module_env_deployer.validate_config 同步加 prepend 校验:变量名/相对路径/禁 ..)。
  3. verify_modules 尝试加 cadence 验收钩子后回退:首次执行 --module-env RC=1 (health 6 项失败)——① 单步 module load cadence/virtuoso/... 在家族分层下 unknown (须先 load 家族入口激活 MODULEPATH,正确用法为加载链 module load cadence virtuoso/IC25.10.000); ② master .16/.17 未挂 /tools,工具目录守卫必拦。回退后 health 归位本工程职责 (Lmod/Core/slurm 模块),cadence 验收移交 ic25 侧;链条写法已留档 example 注释。

执行与实测:

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env --dry-run   # 预期:待变更 3(新 lua),26 项幂等跳过
# 真实执行(squeue 实测空;本变更 TaskProlog 未变 → 部署器判定 conf_changed=False,实测【未】重启 slurmd)
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env             # 首轮 RC=1(health 见上,变更本体已成功)
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env             # 回退钩子后 RC=0,零变更全跳过(幂等)
# 终验(有 /tools 的节点):module load 后 ldd not found=0
for h in 18 19 20 28; do ssh root@192.168.31.$h 'bash -lc "module load cadence virtuoso/IC25.10.000 && command -v virtuoso && ldd \$(command -v virtuoso) | grep -c \"not found\""'; done
# 预期:每台输出 /tools/.../dfII/bin/virtuoso 与 0(.16/.17 无 /tools 不适用;lsf .25-.27 未装 Lmod 不适用)
  • 实测结论【已确认】:.18/.19/.20/.28 加载链后 NOTFOUND=0(217 树内库闭合); sinfo 正常、slurmd active;复跑幂等零变更。已知缺口登记:master .16/.17 无 /tools、 lsf 三台(.25–.27)未装 Lmod——均不影响作业面,处置见 ic25 HANDOFF 待办。

5.13 cadence license 变量接入(2026-09-26,跨工程变更)

触发:license 服务在 192.168.31.29 就绪(ic25 侧 license_srv_20260926),客户端需拿到 CDS_LIC_FILE;由 ic25 HANDOFF 待办 #1 发起,用户确认后实施。完整八章档案在 ic25 工程的 ic25_modulefile_lic_20260926/docs/SOP.md,本节只记本工程侧改动。

变更内容(改前备份:ic25_modulefile_lic_20260926/backup/slurm_ins/config/,.bak.20260926-145859):

  1. config/slurm_deploy.yaml(.example 同步):module_env.families.cadence.tools[virtuoso].env 由 flow 写法改为 block,新增 CDS_LIC_FILE: "5280@192.168.31.29" 与 LM_LICENSE_FILE: "5280@192.168.31.29"(渲染为 setenv,工作方式见 5.12 的 env/prepend 分工)。
  2. 未动渲染器、未动 TaskProlog、未动 verify_modules(cadence 验收仍归 ic25 侧)。

执行与实测(squeue 实测为空):

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env --dry-run   # 预期 待变更 1、幂等跳过 26
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env             # RC=0
.venv/bin/python main.py --config config/slurm_deploy.yaml --module-env             # 复跑:待变更 0、幂等 27(幂等成立)

实测结论【已确认】:/opt/modulefiles/cadence/virtuoso/IC25.10.000.lua 出现两行 setenv(...LIC...); 客户端 .18 加载链后 echo $CDS_LIC_FILE = 5280@192.168.31.29。 遗留【已确认】:该 license 为 node-locked(FEATURE 行带 HOSTID=),客户端 checkout 被拒 (-9,57 Invalid host),需版权方换发 floating 版本;详见上述 ic25 档案第 6、8 章。

六、验证

SLURM=/usr/local/slurm/bin

# 1. 集群与节点
$SLURM/sinfo -N -o '%N %T %C' | sort -u          # 预期 3 行,STATE=idle
$SLURM/scontrol ping                              # 预期 primary/backup 均 is UP

# 2. 主备形态与共享存储
$SLURM/scontrol show config | grep -E 'SlurmctldHost|StateSaveLocation'
# 预期两条 SlurmctldHost,StateSaveLocation=/mnt/slurm-shared/slurmctld-state
findmnt -o TARGET,SOURCE,FSTYPE /mnt/slurm-shared
# 预期:/mnt/slurm-shared  192.168.31.249:/data/nfs/slurm-state  nfs4

# 3. 服务
systemctl is-active munge slurmdbd slurmctld       # .16 预期均 active
systemctl is-active munge slurmctld                # .17
systemctl is-active munge slurmd                   # .18~.20

# 4. 记账与 QoS
$SLURM/sacctmgr -n -P show qos format=Name,Priority
# 预期 4 行:interactive / regression / normal / long
$SLURM/sacctmgr -n -P show assoc format=Cluster,Account,User | head
$SLURM/sacct -n -P -o JobID,State,ExitCode --starttime today | tail   # 历史作业可查

# 5. 健康保障
bash /usr/local/bin/slurm_health_check.sh; echo "exit=$?"   # 预期 0(在计算节点执行)
systemctl is-active slurm-cluster-health.timer             # 预期 active
systemctl restart slurmctld && sleep 8 && systemctl is-active slurm-cluster-health.timer
# 预期仍为 active(验证 timer 不再被连带停止)

# 6. 日志告警(在 .16 执行)
journalctl -u slurmctld --since '10 min ago' --no-pager | grep -c 'Saving to StateSaveLocation'   # 预期 0
journalctl -u slurmctld -n 50 --no-pager | grep -cE 'http_parser|plugin_load'                     # 预期 0
journalctl -u slurmctld -n 50 --no-pager | grep -c 'MailProg is invalid'                          # 预期 0

# 7. 共享存储写入延迟
cd /mnt/slurm-shared && for i in 1 2 3; do
  s=$(date +%s%N); dd if=/dev/zero of=./iotest bs=4k count=20 conv=fsync status=none
  e=$(date +%s%N); echo "第$i 次: $(( (e-s)/1000000 )) ms"; done; rm -f ./iotest
# 预期:9–12 ms

# 8. 端到端作业
cd /tmp && printf '#!/bin/bash\nhostname\necho JOB_OK\n' > t.sh
$SLURM/sbatch -N1 -o /tmp/t_%j.out t.sh
sleep 15 && cat /tmp/t_*.out                       # 预期输出计算节点主机名与 JOB_OK
$SLURM/sacct -n -P -o JobID,State,ExitCode --starttime today | tail -1   # 预期 COMPLETED 0:0

# 9. 幂等(关键验收)
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml 2>&1 | tail -5
# 预期:零变更,无需部署(全跳过)

# 10. 命令 PATH(登录 shell 生效)
ssh root@192.168.31.16 "bash -lc 'command -v sinfo; sinfo -V'"
# 预期:/usr/local/slurm/bin/sinfo 与 slurm 26.05.2

# 11. LDAP 用户全节点可用
for ip in 16 17 18 19 20; do echo -n "192.168.31.$ip: "; \
  ssh root@192.168.31.$ip "getent passwd cy01 | cut -d: -f1,3,4,6,7"; done
# 预期:cy01:4001:4000:/home/cy01:/bin/csh(5 行)
for ip in 16 17 18 19 20; do echo -n "192.168.31.$ip su: "; \
  ssh root@192.168.31.$ip "su - cy01 -c 'id -u'"; done
# 预期:全部输出 4001(含 csh 与家目录均已就位)

# 12. 共享家目录
for ip in 16 17 18 19 20; do echo -n "192.168.31.$ip: "; \
  ssh root@192.168.31.$ip "findmnt -n -o SOURCE,OPTIONS /home | tr ',' ' ' | grep -E '/data/nfs/home|soft' | tr '\n' ' '"; echo; done
# 预期:每行含 192.168.31.249:/data/nfs/home 与 soft

# 13. LDAP 认证相关组件
ssh root@192.168.31.16 "systemctl is-active sssd oddjobd; authselect current | head -4; getsebool use_nfs_home_dirs; stat -c '%a %n' /etc/sssd/sssd.conf"
# 预期:active active;Profile ID: sssd + with-mkhomedir;use_nfs_home_dirs --> on;600 /etc/sssd/sssd.conf

# 14. 节点基础环境幂等(关键验收)
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --node-env 2>&1 | tail -5
# 预期:零变更,无需部署(全跳过)

# 15. 环境模块基本可用(5 台)
for ip in 16 17 18 19 20; do echo -n "192.168.31.$ip: "; \
  ssh root@192.168.31.$ip "bash -lc 'module --version' 2>&1 | grep -i 'version [0-9]'"; done
# 预期:5 行 Modules based on Lua: Version 8.7.65 ...

# 16. 模块层次、家族互斥与跨族共存
ssh root@192.168.31.16 "bash -lc 'module purge; module load synopsys; \
  module avail 2>&1 | grep -A2 modulefiles/synopsys; echo ---; module load cadence; module -t list'"
# 预期:modulefiles/synopsys 下出现 vcs/2023.12;
#       加载 cadence 时提示 Lmod is automatically replacing \"synopsys\" with \"cadence\";
#       module -t list 只剩 cadence

# 17. 工具守卫(工具未安装时明确报错,而非静默成功)
ssh root@192.168.31.16 "bash -lc 'module load synopsys; module load vcs/2023.12' 2>&1 | head -3"
# 预期:Lmod has detected the following error: 工具尚未安装或路径待配置:/opt/eda/synopsys/vcs/2023.12

# 18. 端到端:作业内 module 可用 + 既有 EDA TaskProlog 未失效
ssh root@192.168.31.16 "printf '#!/bin/bash\n#SBATCH -J lmodverify -N1 -o /tmp/lmodverify_%%j.out -t 00:03:00\nsource /etc/profile.d/zzz_site_lmod.sh\nmodule load slurm && command -v sinfo\necho \"EDA_JOB_ID=\$EDA_JOB_ID EDA_WORKDIR=\$EDA_WORKDIR LMOD_CMD=\$LMOD_CMD\"\n' > /tmp/lmod_verify.sh && /usr/local/slurm/bin/sbatch /tmp/lmod_verify.sh"
# 作业完成后,到运行节点查看 /tmp/lmodverify_<jobid>.out,预期:
#   /usr/local/slurm/bin/sinfo
#   EDA_JOB_ID=<jobid> EDA_WORKDIR=/root LMOD_CMD=/usr/share/lmod/lmod/libexec/lmod

# 19. 环境模块幂等(关键验收)
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --module-env 2>&1 | tail -5
# 预期:零变更,无需部署(全跳过)

七、回滚

7.1 备份位置

备份内容 位置 说明
宿主机 /etc/exports docs/backup/etc/exports.bak.* 本次 NFS 变更前备份,已移入交付物目录
集群侧配置/目录/数据库 各控制节点 /var/backups/slurm/ 实现体自带,随 --rollback 使用,保持原位

逐条登记与"何时可删"见 backup/README.md。

7.2 一键回滚(集群)

.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --rollback

动作:停新服务 → 找旧版本目录 → symlink 切回 → 起旧服务。

7.3 卸载健康保障

.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --health-ensure --rollback

动作:禁用并删除 timer/service → 删巡检脚本与拓扑配置 → 删 HealthCheck 程序 → 用 slurm.conf.healthbak.* 恢复 slurm.conf。

7.4 手工回滚(脚本失败时)

# a) 服务与 symlink
systemctl stop slurmd slurmctld slurmdbd 2>/dev/null     # 高危:停服
ln -sfn /usr/local/slurm-<旧版本> /usr/local/slurm
systemctl start slurmdbd slurmctld slurmd

# b) 共享存储:切回原导出(先确认 slurmctld 已停)
systemctl stop slurmctld
umount /mnt/slurm-shared
mount -t nfs -o rw,hard,intr,_netdev 192.168.31.249:/srv/nfs/k8s-hdd /mnt/slurm-shared
# 同步修改 /etc/fstab 中该行的导出路径

# c) 宿主机 /etc/exports:移除新增行(高危:改系统配置前先备份)
sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d_%H%M%S)
sudo sed -i '\#/data/nfs/slurm-state#d' /etc/exports
sudo exportfs -ra

# d) 数据库回滚(如需)
gunzip < /var/backups/slurm/db_slurm_acct_db_<timestamp>.sql.gz | \
  mysql -h 192.168.31.10 -u slurm -p slurm_acct_db

回滚后验证:scontrol ping 正常、sinfo 能列出节点、sacct 可查历史、三个守护进程 active。

7.5 卸载节点基础环境(第二轮改造)

# 一键卸载:命令 PATH + LDAP 认证 + 共享家目录(覆盖全部 5 台)
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --node-env --rollback

动作顺序:删 /etc/profile.d/slurm-path.sh → umount /home 并从 /etc/fstab 删该行 → setsebool -P use_nfs_home_dirs off → 停并禁用 sssd / oddjobd → authselect select minimal --force → 删 /etc/sssd/sssd.conf。

不会删除:已安装的软件包、NFS 上已创建的家目录及其内容。删除家目录需人工确认后手工执行。

PAM/NSS 若需还原到变更前状态(authselect 接管前的版本),各节点备份位于 /var/backups/slurm/node_env_<时间戳>/,内含 _etc_nsswitch.conf、_etc_fstab、 _etc_pam.d、_etc_sssd_sssd.conf:

ssh root@192.168.31.16 "ls -1 /var/backups/slurm/node_env_*/"          # 确认备份点
ssh root@192.168.31.16 "cp -a /var/backups/slurm/node_env_<ts>/_etc_nsswitch.conf /etc/nsswitch.conf && \
  cp -a /var/backups/slurm/node_env_<ts>/_etc_pam.d/. /etc/pam.d/ && \
  systemctl restart sshd"                                              # 高危:改 PAM 后必须确认 SSH 仍可登录

宿主机侧若也要撤销 /home 导出(先确认各节点已 umount /home):

sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d-%H%M%S)         # 高危:改前必备份
sudo sed -i '\#^/data/nfs/home #d' /etc/exports
sudo exportfs -ra

回滚后验证:getent passwd cy01 无输出;findmnt /home 无输出(回到本地目录); bash -lc 'command -v sinfo' 无输出(命令文件本身仍保留在 /usr/local/slurm/bin)。

7.6 卸载环境模块(第三轮改造)

# 一键卸载:撤 TaskProlog → 删模块树与站点配置 → 卸共享挂载
.venv/bin/python main.py \
  --config config/slurm_deploy.yaml --module-env --rollback

动作顺序:把 slurm.conf 的 TaskProlog 恢复为原脚本 /usr/local/slurm/task_prolog_eda.sh → 删 /etc/slurm/task_prolog_lmod.sh → 删 /etc/profile.d/zzz_site_lmod.sh 与 /etc/lmod/lmod_config.lua → umount /opt/modulefiles 并从 /etc/fstab 删该行 → scontrol reconfigure + 重启各计算节点 slurmd。

不会删除:已安装的 Lmod 软件包、共享存储上的 modulefiles 内容(避免误删共享内容)。 如需彻底清理,先确认无其它使用者,再人工删除 /data/nfs/modulefiles 下的内容与 Lmod 包。

宿主机侧若也要撤销 modulefiles 导出(先确认各节点已 umount /opt/modulefiles):

sudo cp -a /etc/exports /etc/exports.bak.$(date +%Y%m%d-%H%M%S)         # 高危:改前必备份
sudo sed -i '\#^/data/nfs/modulefiles #d' /etc/exports
sudo exportfs -ra

回滚后验证:bash -lc 'module --version' 报 command not found(或回落到 Tcl 版); findmnt /opt/modulefiles 无输出;grep '^TaskProlog=' /usr/local/slurm/etc/slurm.conf 预期回到 /usr/local/slurm/task_prolog_eda.sh。

八、风险说明

风险 影响 缓解措施
database.password 未填 部署在 pre_check 直接失败,不产生任何变更 配置检查前置,--dry-run 也会告警
编译依赖缺失或 mariadb_config 未找到 编译中断,旧版本未切换,集群不受影响 编译失败即中止;build_deps 已含 Rocky 9 全部所需包名
-devel 包不在默认仓库 装包失败,部署中断 部署器检测到 -devel 包时自动启用 CRB 仓库
firewalld 只放通 ssh 6817/6818 被拦,slurmd 无法拉取 configless 配置 部署器自动 firewall-cmd --permanent --add-port 后 reload
munge key 权限设为 group 可读 munged 拒绝启动:Keyfile is insecure,全线起不来 部署器固定 chown munge:munge + chmod 400(不能用 640)
首次启动 state 目录为空 slurmctld fatal: No Assoc usage file ... to recover 一次性 systemd override 注入 -i 初始化 state,用完即移除
QOS 晚于 slurmctld 创建 fatal: Partition xxx has an invalid qos 启动顺序固定为 slurmdbd → 记账初始化 → slurmctld → slurmd
编译节点的 -devel 包不分发 计算节点缺运行库,slurmd 或插件 dlopen 失败 运行库检查覆盖 sbin/* 与 lib/slurm/*.so,用 ldd 探测 + dnf repoquery 反查包名自动补齐
cgroup v2 下 slurmd 未按 systemd 模式启动 cannot create cgroup context for cgroup/v2,作业无法运行 单元用 Type=notify + ExecStart=... --systemd -s(--systemd 与 -D 互斥)
编译时缺 dbus-devel 不生成 cgroup_v2.so,配置再对也跑不了作业 build_deps 已含;分发判据额外校验该插件存在
编译依赖变化但版本号不变 产物实际不同却被判为"已安装",出现假一致 引入编译输入指纹(版本+依赖+configure 参数),变化即自动重编译与重分发
共享存储后端被更换 只看 mountpoint 会漏判为零变更,切换不执行 sync_status 比对 findmnt 的挂载源
渲染结果含生成时间戳 配置哈希每次都变,漂移检测永久判为有变更,幂等失效 渲染器禁止写入时间戳(config_renderer.py 有注释约束)
timer 用 Requires=slurmctld.service slurmctld 重启会连带停掉巡检 timer,健康守护静默失效 改用 Wants=,巡检同时校验 enabled 与 active
数据库操作凭据落到命令行/日志 密码出现在进程列表与日志文件 全部改用 0600 临时 cnf 文件传参,写入走 SFTP;日志已确认不含凭据
备控仅在 slurm-master-02,slurmdbd 单点 主控全挂时记账不可用(调度仍可继续) 本次未做 DBD 双活;如需可后续扩展
模板 MailProg 指向不存在的 sendmail slurmctld 每次启动记一条告警 部署器探测 /usr/sbin/sendmail,不存在时自动注释该行
模板里的分区与主机名指向旧集群 换用其它集群导出的模板时,替换映射对不上;且模板只定义 3 个计算节点时,新集群多出的节点不属于任何分区、无法接作业 旧环境标识改为从模板自动提取(ControlMachine/SlurmctldHost、NodeName、PluginDir、Prolog 路径、全文 IP),并用节点名前缀从注释补齐;PartitionName 的 Nodes= 按 yaml 重算(覆盖全部旧节点→视为全量,替换为当前全部计算节点);必要时用 custom_config.legacy 手工覆盖
模板注释里残留旧集群主机名 排障时误以为配置未生效 注释行同样参与旧标识替换
手工填写节点 CPU/内存与真机不符 RealMemory 偏大会导致内存超分、偏小则浪费资源 yaml 留空即自动探测(nproc + /proc/meminfo,内存取物理 95%);填写了则以 yaml 为准
目标机非 RHEL 系或版本不符 直接进入装包阶段,以 command not found / Unable to find a match 之类的晦涩报错失败,难定位 pre_check 先校验 /etc/os-release 的 ID/ID_LIKE、VERSION_ID 与 uname -m,不符即报错并说明期望值;可用 os_check.enabled: false 关闭
目标机与部署机之间有跳板/容器环境 /etc/os-release 缺失导致识别失败 该校验会明确报错而不是静默通过
sacctmgr -i 模式下对象不存在也输出表头 用它做存在性判断会恒为真,同步全部走"已存在,跳过",实际什么都没建 查询一律用 -n -P(不带 -i),-i 只用于变更操作
LDAP 基准 DN 本身被当成账户 ou=eda 会被建成一个无意义的顶级账户 基准 DN 只作容器,其直接子级视为顶级账户
用户清理误删 误删关联不可回滚(记账历史无法复原) 三重保护:受保护列表、有运行作业跳过、LDAP 返回用户数低于 min_ldap_users 时整体跳过清理
LDAP 凭据泄露 密码进入命令行、进程列表或日志 凭据写 0600 文件并用 ldapsearch -y 传入;巡检会校验该文件权限为 600
账户体系重复建设 部署时按"工种"建的账户与 LDAP 按"部门"建的账户并存,两套维度无法自动合并 已统一为 LDAP 单一来源;sacctmgr.accounts 中只保留 root,避免重建
authselect 接管 PAM/NSS 理论上可能影响 SSH 登录(PAM 栈被重写) 变更前备份 nsswitch.conf 与 /etc/pam.d/;顺序为先起 sssd 确认 getent 正常、再动 authselect;执行后立即校验 SSH 与 getent passwd root,异常就地回退 minimal 并报错退出
sssd.conf 含 LDAP bind 密码 凭据泄露 权限强制 600 并在校验阶段复查;内容经 SFTP 写入(不进命令行);日志不打印内容
ldap_id_mapping 取默认值 节点上 uid 由算法随机生成,与 LDAP/Slurm 记账对不上,作业归属错乱 明确设 false,使用 LDAP 真实 uidNumber/gidNumber;库中若无 uidNumber 才改 true
LDAP 用户 loginShell=/bin/csh 而节点无 csh su - <user> 无法进入预期 shell(或落回 /bin/sh) system_auth.extra_packages: ["tcsh"],由部署器一并安装
/home 被 NFS 覆盖挂载 本地 /home 原有内容被遮蔽 变更前确认为空目录;fstab 带 _netdev 避免开机阻塞;回滚 umount /home 即恢复本地目录
家目录用 soft 挂载 宿主机故障时写操作可能返回 I/O 错误,而服务端实际已写入,存在数据不一致风险 按"家目录优先保登录可用性"取舍(hard 会让 su - <user> 与 SSH 登录卡死);若更看重数据一致性,把 home_share.mount_options 改回 hard,重跑会自动卸载重挂
挂载判定只比对挂载源 漏判"仅挂载选项变更",改了 yaml 却不重挂,配置与实际长期不一致 两处挂载(/home 与 HA 的 /mnt/slurm-shared)的判定都已同时比对挂载源与挂载选项,共用 utils/idempotent_utils.mount_options_matched;该函数跳过 _netdev / intr 等不出现在内核挂载表的选项,避免把它们误判为漂移而反复卸载重挂
SELinux enforcing 未放通 NFS 家目录 用户能 su 但读不了自己家目录 部署器自动 setsebool -P use_nfs_home_dirs on,并在 pre_check 校验 setsebool 存在
LDAP 用户与本地用户名冲突 同名账号解析优先级混乱,登录身份与预期不符 authselect 生成的 nsswitch.conf 为 files sss systemd(本地优先);新增 LDAP 用户时应避开节点上的本地同名账号
明文 ldap:// 传输 bind 凭据与用户认证报文在网内明文 当前内网且 LDAP 未启用 TLS;如需加固,改 ldaps:// 并按需调整 tls_reqcert
NFS 不可达导致挂载失败 部署中断(不产生半成品,本地 /home 保持可用) pre_check 先探测各节点到 2049 的连通性,任一不通即汇总退出、不做变更
Lmod 需 EPEL 内网隔离环境下装不上 Lmod epel-release 由 Rocky 的 extras 仓库提供(无需外部网络即可安装),但 EPEL 元数据需可从网络拉取;若目标环境确实隔离,改用 Rocky 自带的 environment-modules(Tcl 版,无 hierarchy / spider,不符合 EDA 标准)或提前备离线 rpm
TaskProlog 被接管导致既有 EDA 注入失效 作业里 EDA_JOB_ID / EDA_JOB_NODELIST / EDA_WORKDIR 丢失 Lmod 的 TaskProlog 内部透传原脚本;--module-env --rollback 自动恢复原值;关闭 module_env.enabled 时渲染器保留模板原值(不删行)
TaskProlog 误注入 MODULEPATH 覆盖用户 --export=ALL 的结果,提交前加载的模块在作业里全部丢失 只注入不变的 LMOD_* 位置变量,明确不注入 MODULEPATH(TaskProlog 在 slurmd 侧,看不到用户提交环境)
module 是 shell function,跨不过作业边界 作业脚本里 module load 报 command not found 作业脚本首行 source /etc/profile.d/zzz_site_lmod.sh(该文件自包含:先初始化 Lmod 再前置路径);SOP/RUNBOOK 均给出标准写法
作业脚本写成 module load x \| head 管道让 module 在子 shell 中执行,加载静默失效,排查困难 文档明确禁止该写法(加载与查看分开写)
Lmod 目录扫描缓存 刚部署的模块在 module avail 里看不到,误判为部署失败 部署器健康检查带 LMOD_IGNORE_CACHE=yes;用户可执行 module --ignore_cache avail 或清 ~/.cache/lmod
示例工具模块指向未安装的路径 "加载成功但命令找不到",用户误以为环境正常 modulefile 内置 isDir 守卫,工具未就位时 module load 明确报错并给出路径
重启 slurmd 中断作业 计算节点上正在跑的作业被中断 安装与卸载都会重启 slurmd(TaskProlog 由 slurmd 执行),执行前须确认 squeue 为空;已在 5.10 醒目标注
EDA 工具本体放各节点本地 磁盘放不下且 5 台易不一致 设计为 modulefiles 与工具本体都走 NFS 共享(主控 /opt 仅余 15G,本地放不下工具)

附录

A. 交付物清单

文件 用途
docs/SOP.md 本文档:本次变更的实施、验证与回滚
docs/RUNBOOK.md Slurm 集群日常运行与故障处置手册
docs/INCIDENT-20260918-slurm-26.05部署排障.md 本次部署排障台账与复盘
docs/backup/README.md 备份台账(位置、用途、可删时机)
{main,deployer,slurm_deployer,config_renderer,health_assets,health_ensure_deployer}.py 实现体
{user_mgmt_assets,user_mgmt_deployer}.py 用户管理能力实现体(LDAP 同步资产与部署器)
{node_env_assets,node_env_deployer}.py 节点基础环境实现体(命令 PATH / LDAP 认证 / 共享家目录;含可复用的 NodeEnvInstaller)
{module_env_assets,module_env_deployer}.py 环境模块实现体(Lmod 软件栈 / 工具家族 modulefiles / 站点配置 / Slurm 集成;含可复用的 ModuleEnvInstaller)
config/slurm_deploy.yaml(.example) 部署配置(实例含凭据,权限 600)

B. 关键决策经过

  1. 共享存储为何新建导出而非改现有导出:/srv/nfs/k8s-hdd 同时承载 k8s 的 4 个 PVC, 改 async 会削弱 k8s 持久化数据的落盘保证。故新建 /data/nfs/slurm-state, 顺带摆脱 loop 镜像的嵌套文件系统开销。实测 fsync 由 500–800ms 降至 9–12ms。
  2. cgroup 版本:目标系统为 Rocky 9 + cgroup v2,模板原为 cgroup v1 语法; 保留语义但剔除 v2 不支持的 ConstrainSwapSpace / AllowedSwapSpace。
  3. License TRES:按选择剔除 AccountingStorageTRES 中的 license/* 项(本环境无 FlexLM)。
  4. QoS 强制关联:保留 AccountingStorageEnforce=associations,qos,limits,并配套自动创建 QoS 与账户层级,否则集群无法提交作业。
  5. 通用化改造(第二轮):原实现把旧环境标识写死为 k8s-master-01/k8s-node-01~03, 只能替换那一份特定模板;分区节点也直接沿用模板,导致"新集群 5 台、分区只覆盖 3 台"。 改为「从模板自动提取标识 + 分区按 yaml 重算」,并保留 custom_config.legacy 手工覆盖口子。 验证方式:构造 4 个场景(现网 3 节点、扩到 5 节点、换用 prod-ctl-01/prod-cmp-01~02 的另一份模板、 yaml 显式指定分区节点),13 项断言全通过。
  6. 用户管理能力与 LDAP 对齐:原 slurm_user_management/ 下两个脚本(本地组映射的 slurm_user_onboarding.py、LDAP 同步的 slurm_ldap_sync.sh)都是旧集群(teama/teamb/teamc 账户、EDA 分区、192.168.0.2 LDAP)的资产,直接迁移会失败。改造为按实际读取的 LDAP 结构 生成账户:dc=rainchen,dc=com 下只有 ou=eda,其下 4 个部门 ou 与 8 个小组 cn。 账户层级按 DN 递归建立,用户按 gidNumber 关联;两套旧方案的数据源与账户模型互斥,故只保留 LDAP 一套。验证结果:新建 12 个账户(层级正确)、8 个用户关联,ldapadmin 按排除列表跳过。
  7. 账户体系统一到 LDAP 单一来源:同步能力落地后,集群里同时存在两套维度不同的账户—— 部署时按"工种"建的 engineering/verification/synthesis/physical,与 LDAP 按"部门"建的 frontend/analog/it/backend 及其小组。二者名字不重叠、无对应关系,无法自动合并。经确认采用 "只留 LDAP"方案:移除 4 个 EDA 业务账户(移除前确认无任何用户关联、无在跑作业,并已备份 账户配置),并同步从 sacctmgr.accounts 中删去其定义,避免下次部署时被重建;仅保留管理账户 root。移除后作业提交验证通过(COMPLETED 0:0)。

  8. 免手填机器信息:节点 cpus/memory_mb 改为可留空自动探测(nproc + /proc/meminfo, 内存取物理 95%),并在 pre_check 增加发行版/主版本/架构校验。实测探测值 8 核 / 14944MB (物理 15730MB),与手工填写的 15000MB 基本吻合;OS 校验用「改期望值」的方式构造了 主版本不符、发行版不符、架构不符三个反例,均被正确拦截并给出可读原因。

  9. 节点基础环境为何做成两条入口:全新部署的用户诉求是"装完就能用",而对已就绪的集群 则需要补装且不能触碰 Slurm 服务。于是把判定与执行下沉到 NodeEnvInstaller (node_env_deployer.py),由主部署器阶段 2 与独立模式 --node-env 共用: 前者保证新集群一次到位,后者给存量集群补装且不重启任何 Slurm 服务。 这与既有"--health-ensure 独立 + 主流程内置阶段"的模式保持一致。
  10. 为何用 sssd 而非 nss-pam-ldapd:RHEL 系 9 的默认认证栈就是 sssd,authselect 的 sssd profile 能一并把 NSS 与 PAM 配好;sssd 自带凭据缓存,LDAP 短暂不可达时已登录会话不受影响。 id_mapping 必须显式设 false——LDAP 中已有真实 uidNumber/gidNumber,否则节点上的 uid 会由算法随机生成,与 Slurm 记账中的用户对不上。
  11. 家目录为何共享而非本地创建:最初方案是在各节点用 with-mkhomedir 本地建家目录,改动更小; 经确认改为 NFS 共享 /home——EDA 场景下用户在不同节点登录需要看到同一份工程数据。 代价是宿主机需新增导出(/data/nfs/home),并在各节点挂载后额外放通 SELinux 的 use_nfs_home_dirs(Rocky 9 默认 Enforcing 且该布尔为 off,不放通则用户读不了自己家目录)。
  12. 为何用"工具家族分层"而非编译器/MPI 分层:EDA 工具(cadence / synopsys / siemens)依赖 glibc 而非自编译工具链,不构成"编译器 → MPI → 应用"的依赖链。行业主流做法是把 Core 层作为家族入口, 加载后才暴露该家族的工具树;PDK 单独一族。好处是 module avail 默认只显示与当前环境相关的模块 (否则一次列出上百个),并可用 family() 实现同族互斥,防止两家 EDA 工具的 PATH 与许可证变量 互相覆盖。实测:加载 synopsys 后再加载 cadence,Lmod 自动替换并提示 Lmod is automatically replacing "synopsys" with "cadence";而 pdk 与工具不同族,可共存。
  13. TaskProlog 为何只注入 LMOD_* 位置变量:曾考虑顺带注入 MODULEPATH 以便作业内直接可用, 但 TaskProlog 运行在 slurmd 侧、看不到用户提交环境,一旦 export MODULEPATH 就会覆盖用户 --export=ALL 传进来的模块列表 —— 属于"看似增强、实则破坏"。故明确只注入固定不变的工具位置变量。
  14. 为何 modulefiles 放 NFS 而非各节点本地:内容虽小(10 个文本文件),但本地存放意味着 5 份副本、 改一处要同步五处;且后续 EDA 工具本体(数十至数百 GB)只能走共享存储,两者放一起最自然。
  15. --module-env 与主部署器阶段的职责划分:阶段 3 只落 modulefiles 与脚本,TaskProlog 交给 阶段 9 的渲染器统一写。原因是主部署器的漂移检测基于"渲染结果哈希":若由阶段 3 另行编辑 slurm.conf, 下次渲染会判为漂移并覆盖,还会多触发一次服务重启。独立模式(无渲染器参与)则采用最小编辑。 两者写入的行内容完全相同(manage_slurm_conf 开关控制),因此互不冲突。

C. 修订记录

日期 修改人 变更内容
2026-09-18 部署执行人 首版:三项能力改造 + 新环境部署 + 共享存储与启动告警修复
2026-09-18 部署执行人 依新版文档规范重写为三份文档,补 backup 台账、幂等与告警修复的风险项
2026-09-18 部署执行人 通用化改造:旧环境标识自动提取、分区节点按 yaml 重算、注释行纳入替换;实跑验证幂等不变
2026-09-20 部署执行人 补齐免手填能力:CPU/内存自动探测、目标系统发行版与架构前置校验
2026-09-20 部署执行人 新增 --user-mgmt:LDAP → Slurm 账户与用户定时同步,含安装前环境检查与卸载
2026-09-20 部署执行人 账户体系统一到 LDAP 单一来源:移除 EDA 业务账户及其配置定义,仅保留 root
2026-09-21 部署执行人 第二轮:补齐用户可用性——命令 PATH(slurm.path_env)、LDAP 认证(user_mgmt.system_auth,--node-env)、共享家目录(home_share);主部署器新增阶段 2;宿主机新增 /data/nfs/home 导出
2026-09-21 部署执行人 家目录挂载选项由 rw,hard,intr,_netdev 调整为 rw,soft,_netdev(避免宿主机故障时登录卡死);挂载选项判据下沉为共享函数 utils/idempotent_utils.mount_options_matched,/home 与 HA 共享存储两处统一使用;校正 slurm_deployer.py 中过时的阶段编号注释;修正文档中 /etc/exports 与账户层级两处不一致
2026-09-22 部署执行人 第三轮:新增环境模块能力(Lmod 软件栈、工具家族分层 modulefiles、站点配置、Slurm TaskProlog 集成),支持 --module-env 独立安装/卸载;主部署器新增阶段 3(共 13 阶段);宿主机新增 /data/nfs/modulefiles 导出;config_renderer 支持渲染 TaskProlog 并与既有 task_prolog_eda.sh 透传共存

来源:/home/rain/Documents/slurm/slurm_ins/docs/SOP.md | 最后更新:2026-09-26 15:06(由 build/sync.py 同步,请勿在站点侧修改)