跳转至

SOP —— 本地 scratch(宿主 KVM 加虚盘 + 节点挂载 /local_scratch)

  • 主题:给 LSF 三台执行节点补齐本地临时层 /local_scratch,作为部署器独立阶段 15 (--stage local-scratch)受管,避免手工操作造成漂移。
  • 变更日期:2026-10-07
  • 触发来源:slurm_ins《EDA 集群存储与服务拓扑巡检报告》§二 G1 的第二部分(三层存储缺 /local_scratch), 经 lsf_ins HANDOFF §3 #5 落地。
  • 口径来源:与 Slurm 集群一致(slurm_ins --local-scratch,见其 local_scratch_deployer.py 与 config/slurm_deploy.yaml 的 local_scratch: 段)。
  • 交付物:本目录(local_scratch/),含本文档与 backup/README.md。

一、背景

巡检报告原判「.25–.27 缺 /local_scratch,交付物需在 .25–.27 各配一块本地盘(非纯软件动作)」。 本次取证推翻了「需要硬件」这一前提:

  • .25–.27 是本机 KVM 上的虚机(virsh list 可见 lsf-work-01/02/03 running), 磁盘只有系统盘 vda(100G,根分区用 4.1G);
  • 同宿主上的 Slurm 三台计算节点已用同款方式各挂了一块 50G 虚盘 (/kvm/img/slurm-work-0X-scratch.qcow2 → 节点内 /dev/vdb → /local_scratch xfs)。

因此这是纯软件动作:宿主创建 qcow2 并热插(不停虚机),节点内格式化后挂载。

另注:本环境 NFS 挂载与本地挂载均由各自部署器统一管理;手工 virsh attach-disk + 手工挂载 会让部署器 --drift 漏检、复跑不收敛,故本次实现为部署器阶段,不手工操作。

二、目标

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

  1. 宿主 /kvm/img 内每台 worker 各有一块 50G qcow2(lsf-work-0X-scratch.qcow2), 且已 --persistent 热插为该虚机的空闲 virtio 盘;
  2. 节点内 /local_scratch 已挂载(xfs,defaults,noatime),/etc/fstab 有 UUID 条目持久化;
  3. /local_scratch/users 存在且为 1777;/etc/profile.d/eda_scratch.sh 导出 LOCAL_SCRATCH;
  4. 该阶段纳入 --drift 检测,复跑幂等(第二次零变更);
  5. 端到端:从 .28 提交的作业落到执行节点后可读写 /local_scratch。

三、前置条件

  • 宿主(操作机自身)具备免密 sudo(virsh / qemu-img 需 root);
  • local_scratch.host_pool_path 指向的存储池存在且与实际路径一致;
  • 目标虚机在宿主上存在且运行中(热插不停机);节点 SSH 免密可达;
  • 目标虚机尚无该盘(首次插入);nodes.workers 的 name 即 libvirt 域名;
  • 变更前 bjobs -u all 无 RUN 作业(本次热插不停机、不重启 LSF,属额外保险)。

四、影响范围

项 内容
主机 宿主(本机,libvirt)+ .25、.26、.27 三台 worker
组件 宿主:存储池新增 qcow2、虚机 XML 增加一块 disk;节点:/etc/fstab、新增挂载点、profile.d 片段
停服 否。virsh attach-disk 为热插,虚机不重启;格式化对象是新建的空虚盘,不触碰系统盘
窗口 无窗口要求;实测单次执行 26.3 s
在跑作业 变更前确认无 RUN 作业;热插过程不影响已登录会话
磁盘占用 qcow2 稀疏:50G 标称、实测各约 3 MB

与 Slurm 侧同一宿主、同一存储池(/kvm/img);命名空间分离(lsf-work-0X-scratch.qcow2 vs slurm-work-0X-scratch.qcow2),互不影响。

五、分步操作

实现体:deploy/lsf/stages/local_scratch.py(LocalScratchModule);配置: deploy/lsf/config/lsf_deploy.yaml 的 local_scratch: 段。以下为等价操作序列(以实现体为准)。

步骤 1:宿主侧创建虚盘并热插

宿主命令一律经 sudo -n。幂等:盘已存在则跳过创建;已插则跳过热插。

# ① 创建稀疏虚盘(幂等:已存在则不重建)
sudo qemu-img create -f qcow2 /kvm/img/lsf-work-01-scratch.qcow2 50G

# ② 取该虚机已用设备名,推导空闲槽位(不硬编码 vdb)
sudo virsh dumpxml lsf-work-01 | grep -oP "target dev='vd\w'" | sort -u

# ③ 热插(不停虚机;--persistent 使其在重启后仍存在)
sudo virsh attach-disk lsf-work-01 /kvm/img/lsf-work-01-scratch.qcow2 vdb \
  --targetbus virtio --sourcetype file --driver qemu --subdriver qcow2 --persistent

# ④ 回读实机设备名(不依赖预设值)
sudo virsh dumpxml lsf-work-01 | grep -A2 'lsf-work-01-scratch'

坑:运行中的虚机持有镜像写锁,探测盘是否可用须 qemu-img info --force-share 只读打开; 若文件存在却读不出信息,判 unknown 并中止,绝不当作「需创建」而误覆盖。

步骤 2:节点侧确认设备与空盘判据

# 等内核识别(状态量轮询,非固定长等待)
ssh root@192.168.31.25 '[ -b /dev/vdb ] && echo yes || echo no'

# 空盘判据(三条全过才允许格式化)
ssh root@192.168.31.25 'findmnt -no SOURCE / ; lsblk -nro NAME /dev/vdb; blkid -s TYPE -o value /dev/vdb'

预期:设备出现;/ 不是 /dev/vdb;/dev/vdb 无子分区;blkid 无输出(空盘)。 失败判断:任一条不符 → 实现体判 unknown 并中止(不格式化)。

步骤 3:格式化 → fstab(UUID) → 挂载 → 环境

# ① 格式化(高危:不可逆,仅对确认过的空盘执行)
ssh root@192.168.31.25 'mkfs.xfs -f /dev/vdb'

# ② 取 UUID
ssh root@192.168.31.25 'blkid -s UUID -o value /dev/vdb'

# ③ fstab 写入(按挂载点字段精确替换同名旧行,再追加;不用通配)
ssh root@192.168.31.25 "tmp=\$(mktemp -p /tmp) && \
  awk -v mp=/local_scratch '\$2 != mp' /etc/fstab > \"\$tmp\" && \
  echo 'UUID=<上一步的UUID> /local_scratch xfs defaults,noatime 0 0' >> \"\$tmp\" && \
  cp \"\$tmp\" /etc/fstab && rm -f \"\$tmp\""

# ④ 挂载 + 建 users 目录
ssh root@192.168.31.25 'mkdir -p /local_scratch && mount /local_scratch'
ssh root@192.168.31.25 'mkdir -p /local_scratch/users && chmod 1777 /local_scratch/users'

# ⑤ profile.d 片段(导出 LOCAL_SCRATCH;刻意不设 TMPDIR,见第 8 章)
ssh root@192.168.31.25 'cat > /etc/profile.d/eda_scratch.sh <<"EOF"
# 本地 scratch(由 LSF 部署器写入,请勿手工修改)
LOCAL_SCRATCH_ROOT="/local_scratch/users"
export LOCAL_SCRATCH="$LOCAL_SCRATCH_ROOT/$USER"
EOF
chmod 644 /etc/profile.d/eda_scratch.sh'

步骤 4:按部署器执行(推荐入口)

cd deploy/lsf
.venv/bin/python main.py -c config/lsf_deploy.yaml --stage local-scratch --dry-run   # 预演
.venv/bin/python main.py -c config/lsf_deploy.yaml --stage local-scratch             # 执行

坑:该阶段不在主流程(宿主加盘 + 格式化属不可逆高危),必须显式 --stage local-scratch; enabled: false 时该阶段与 --drift 均自动跳过。

六、验证

cd deploy/lsf
# ① 幂等复跑:应「状态已一致,无漂移(全跳过)」
.venv/bin/python main.py -c config/lsf_deploy.yaml --stage local-scratch

# ② 独立漂移检测:全阶段无漂移,退出码 0(含 阶段15(local-scratch))
.venv/bin/python main.py -c config/lsf_deploy.yaml --drift; echo "rc=$?"

# ③ 实机核对(三台)
for h in 25 26 27; do ssh root@192.168.31.$h \
  'findmnt -n -o SOURCE,TARGET,FSTYPE,OPTIONS /local_scratch; grep local_scratch /etc/fstab; \
   ls -ld /local_scratch/users; df -hT /local_scratch | tail -1; cat /etc/profile.d/eda_scratch.sh'; done

# ④ 端到端:提交引用 /local_scratch 的作业(输出须写**共享路径**,见第 8 章坑 2)
ssh root@192.168.31.28 "source /data/nfs/lsf/lsf/conf/profile.lsf; \
  echo 'hostname; df -h /local_scratch | tail -1; \
        mkdir -p /local_scratch/users/\$USER && echo LOCAL_SCRATCH_WRITABLE; \
        source /etc/profile.d/eda_scratch.sh && echo LOCAL_SCRATCH=\$LOCAL_SCRATCH' \
  | bsub -o /data/nfs/lsf/.ls_probe.out -n 1"

实测结果(2026-10-07):

检查项 结果
执行 状态: success,耗时 26.31 s,三台全部达标
幂等复跑 阶段15「状态已一致,无漂移(全跳过)」
--drift 全阶段无漂移(含 阶段15(local-scratch): 无漂移),rc=0
三台挂载 /dev/vdb → /local_scratch xfs rw,noatime,50G(用 389M)
fstab 三台各 1 条 UUID=… /local_scratch xfs defaults,noatime 0 0
目录/环境 /local_scratch/users = drwxrwxrwt;profile.d 片段内容正确
端到端作业 作业 1049 落在 lsf-work-01:/dev/vdb 50G … /local_scratch、LOCAL_SCRATCH_WRITABLE、LOCAL_SCRATCH=/local_scratch/users/root
与 Slurm 口径一致 挂载源/文件系统/选项/fstab 形式/users 权限/profile 导出项逐项一致

七、回滚

备份位置:

备份 位置 说明
部署器文件(6 份) local_scratch/backup/deploy/lsf/** 各 <原名>.bak.20261007-090939
节点 /etc/fstab(3 份) .25–.27 的 /etc/fstab.bak.20261007-091231 保持原位(实现体回滚依赖)
宿主虚机 XML(3 份) deploy/lsf/backup/local_scratch/20261007-091231/lsf-work-0X.xml 热插前的定义快照

台账见 backup/README.md。回滚命令(实现体已内置):

cd deploy/lsf
.venv/bin/python main.py -c config/lsf_deploy.yaml --stage local-scratch --rollback

其动作:节点侧 umount /local_scratch → 移除 fstab 条目 → 删 profile 片段 → 删空目录; 宿主侧 virsh detach-disk <vm> <dev> --persistent。 qcow2 文件按设计保留在存储池内(删除属不可逆动作,工具不代劳);确认无用后由人工删除:

sudo rm -f /kvm/img/lsf-work-0X-scratch.qcow2     # 高危:确认已 detach 且不再需要

回滚后验证:三台 findmnt /local_scratch 无输出;--drift 对该阶段应报「漂移」(因盘已不在, 但有 enabled: true)——此时把 local_scratch.enabled 置 false 即恢复「不纳管」状态。

八、风险说明

风险 说明与缓解
格式化不可逆 目标设备由实机推导:① 盘路径须在配置的存储池内且命名合规;② 不得被其它虚机引用;③ 节点侧须满足「非根设备、无子分区、无既有文件系统」三条,任一不满足即判 unknown 并中止。不硬编码 /dev/vdb(由 virsh dumpxml 的空闲槽位推导)
误 attach 到他人的盘 _assert_safe_target 遍历所有虚机的 XML,若目标盘已被别的虚机引用则拒绝执行(与 Slurm 同款判据)
热插中断虚机 virsh attach-disk 为热插,不需重启;本次实测三台作业与服务均无中断
探测失败被当成「需变更」 三态化:unknown 一律中止(如盘文件存在但 qemu-img 读不出、设备已在别处挂载)
盘序漂移导致挂错盘 fstab 按 UUID 绑定,不用设备名
坑 1:作业里 $TMPDIR 未指向 scratch 与 Slurm 口径一致,profile.d 刻意不设 TMPDIR(profile 会被所有 shell 加载,指向尚未创建的作业目录会破坏 mktemp)。作业级 TMPDIR 应另由调度器注入——LSF 侧已于 2026-10-08 实现(lsf.conf 的 LSB_JOB_TMPDIR + LSB_SET_TMPDIR,见 ../tmpdir_inject_20261008/SOP.md)
坑 2:作业输出写 /tmp/... 读不到 /tmp 是执行节点本地盘,提交节点看不到;验收输出须写共享路径(本次用 /data/nfs/lsf/),且取回要等作业真正结束(实测调度延迟可达数十秒)
回滚后残留 qcow2 属预期(工具不删盘);已在 backup/README.md 登记位置与处置方式
与 Slurm 同宿主争用存储池 命名与盘文件完全分离;池容量需自行关注(本次新增 3×50G 标称、实测约 3 MB/块)

来源:/home/rain/Documents/LSF/lsf_ins/local_scratch/SOP.md | 最后更新:2026-10-08 03:49(由 build/sync.py 同步,请勿在站点侧修改)