跳转至

SOP — NFS 导出收紧:/tools、/pdk 改只读

主题:nfs_export_ro_20261007|类型:C 类变更(单机 3 步)|状态:已落地并验证(2026-10-07) 对象:宿主机 dell-R620(192.168.31.249)的 NFS server 导出策略 关联:巡检报告 eda_topo_audit_20261006/ §5 G4(本项即其结案)

1. 背景

/data/nfs/tools(EDA 工具安装根)与 /data/nfs/pdk(工艺库)按 EDA 存储标准应只读 + 版本化 (eda_storage_standardization/docs/SOP.md 附录 B.5 第 2 条)。实测(2026-10-07)两行导出与其它 导出同为 rw,async,no_subtree_check,no_root_squash —— 只读仅靠客户端 ro 挂载兜底, 任一客户端 root 可 mount -o remount,rw 后改写工具与 PDK,约束不成立。

2. 目标

  1. /etc/exports 中 /data/nfs/tools、/data/nfs/pdk 两行由 rw 改为 ro。
  2. exportfs -v 与内核导出表 /var/lib/nfs/etab 双证 ro。
  3. 客户端读不受影响;exportfs -ra 后其余 8 条导出保持原状。
  4. 幂等:同表达式再执行一次零变更。

3. 前置条件

  • 本机对 .249 root 免密 ssh(本机即 .249,亦可直接操作)。
  • 变更前确认无客户端以 rw 挂载这两条导出——否则收紧后会立即报写失败。 实测(2026-10-07):.18/.25 等全部客户端 /tools、/pdk 均为 ro 挂载,满足。
  • 备份:改前 /etc/exports 必须先备份(见第 7 章)。

4. 影响范围

项 内容
主机 dell-R620(192.168.31.249),单机
变更文件 /etc/exports 第 19、20 行(仅导出选项,路径与客户端网段不变)
受影响客户端 所有挂载 /tools、/pdk 的节点(.18–.20、.25–.28)—— 均本就 ro 挂载,行为不变
停服 不需要;exportfs -ra 重载期间客户端请求可能瞬时重试
窗口 任意;避开正在读工具的大量编译作业更稳(非必须)
在跑作业 不中断;仅写工具/PDK 的操作本就不可行

5. 分步操作

步骤 1:备份 /etc/exports

坑:/etc/exports 是全网导出唯一事实源,改错会让所有 NFS 客户端挂载失效—— 必须同时留「本地主题副本」与「宿主机同目录副本」两份。

TS=$(date +%Y%m%d-%H%M%S)
D=/home/rain/Documents/slurm/slurm_ins/nfs_export_ro_20261007/backup
mkdir -p "$D"
scp -q -p root@192.168.31.249:/etc/exports "$D/exports.bak.$TS"
chmod 600 "$D/exports.bak.$TS"
wc -c "$D/exports.bak.$TS"        # 预期 1140 B(改前)

预期:本地副本 1140 B。失败判断:scp 失败 → 先修连通性,不得继续。

步骤 2:把两行导出由 rw 改 ro

用 sed 原地替换,模式锚定行首路径,只会命中这两行;-i.bak.<TS> 同时留宿主机副本。

ssh root@192.168.31.249 \
  "sed -i.bak.$(date +%Y%m%d-%H%M%S) -E \
   's#^(/data/nfs/(tools|pdk)[[:space:]]+[0-9.]+/[0-9]+)\(rw,#\1(ro,#' /etc/exports"
ssh root@192.168.31.249 'grep -nE "/data/nfs/(tools|pdk)" /etc/exports'

预期:

19:/data/nfs/tools 192.168.31.0/24(ro,async,no_subtree_check,no_root_squash)
20:/data/nfs/pdk   192.168.31.0/24(ro,async,no_subtree_check,no_root_squash)

失败判断:行号/路径不对,或其它导出被改动 → 用步骤 7 的备份立即恢复。

步骤 3:重载导出并验证

ssh root@192.168.31.249 'exportfs -ra && echo reload_rc=0; exportfs -v'
ssh root@192.168.31.249 'grep -E "/data/nfs/(tools|pdk)" /var/lib/nfs/etab'
ssh root@192.168.31.18 'ls /tools | head -3; ls /pdk | head -3'

预期:exportfs -v 中两条为 ...,sec=sys,ro,...,其余仍 rw;etab 同样为 ro; 客户端仍能正常列出目录(读不受影响)。 失败判断:reload_rc 非 0 或客户端读失败 → 第 7 章回滚。

步骤 4:幂等复核

ssh root@192.168.31.249 \
 'cp -p /etc/exports /tmp/exports.idem; \
  sed -i -E "s#^(/data/nfs/(tools|pdk)[[:space:]]+[0-9.]+/[0-9]+)\(rw,#\1(ro,#" /etc/exports; \
  cmp -s /etc/exports /tmp/exports.idem && echo "IDEMPOTENT: 第二次零变更" || echo CHANGED; \
  rm -f /tmp/exports.idem'

预期:IDEMPOTENT: 第二次零变更(已无 (rw, 可匹配)。

6. 验证

项 命令 本次结果
配置文件 grep -nE '/data/nfs/(tools\|pdk)' /etc/exports 两行均为 (ro,...)
导出视图 exportfs -v 两条 ro,其余 8 条 rw 不变
内核侧 grep ... /var/lib/nfs/etab 两条 ro
客户端读 .18 上 ls /tools / ls /pdk 正常列出
幂等 步骤 4 零变更

7. 回滚

备份位置:主题 backup/exports.bak.<TS>(本地)+ 宿主机 /etc/exports.bak.<TS>(同目录)。 台账见 backup/README.md。

# 从宿主机副本恢复(推荐,路径与原文件同目录)
ssh root@192.168.31.249 \
  'cp -p /etc/exports.bak.<TS> /etc/exports && exportfs -ra && grep -nE "/data/nfs/(tools|pdk)" /etc/exports'
# 或从本地副本恢复
scp -p /home/rain/Documents/slurm/slurm_ins/nfs_export_ro_20261007/backup/exports.bak.<TS> \
  root@192.168.31.249:/etc/exports
ssh root@192.168.31.249 'exportfs -ra'

回滚后验证:grep 两行回到 (rw,;exportfs -v 同步;客户端读写恢复正常。 注意:回滚只影响导出策略,不改动 /tools、/pdk 内的任何数据。

8. 风险说明

风险 等级 说明与缓解
有客户端以 rw 挂载 → 收紧后写失败 中 变更前已实测全部客户端均为 ro 挂载;exportfs -ra 后如出现写失败,立即回滚
exportfs -ra 重载影响其它导出 中 重载是原子的重新导出;其余 8 条选项未改,重载后实测不变
sed 误伤其它行 低 模式锚定行首 /data/nfs/(tools\|pdk) + 客户端网段,实测仅命中 2 行;且留了宿主机 .bak
备份文件散落 /etc 低 宿主机 .bak.<TS> 为主题回滚专用,验证通过后由用户对照台账自删

附录 A:交付物清单

文件 说明
backup/exports.bak.<TS> 改前 /etc/exports(1140 B)
宿主机 /etc/exports.bak.<TS> 同上的镜像副本(便于单机就地恢复)
backup/README.md 备份台账

附录 B:同日并行定论(不在本次变更范围)

巡检报告 §5 G3(NFS 挂载参数 soft):经用户 2026-10-07 决策——有意保留 soft (实测 hard 延迟偏高;本环境为测试/学习用途,接受 soft 在服务端抖动时静默 EIO 的风险)。 该决策无系统变更,已记入 docs/BASELINE.md §2。

附录 C:补做——改由部署器维护(消除漂移,2026-10-07)

问题

上一轮改导出用的是手工 sed,但 /etc/exports 实际由 storage_layout_deployer.py 管理,暴露两层问题:

  1. storage_layout.export_options 是全局单一值(rw,async,no_subtree_check,no_root_squash), exports_line() 对所有 tier1 条目套同一套选项 → 配置源与现场(ro)不一致;
  2. 更隐蔽:ensure_export() 的幂等判据只看「路径是否在文件且在 kernel」, 不比对选项内容 → 选项漂移永远检测不出来,配置源与现场可以长期不一致而无人发现。

处置(均为实现体/配置,未改节点状态)

文件 改动
storage_layout_assets.py tier1_items 白名单补 export_options 字段(漏了会让条目级配置被静默丢弃——离线自测当场抓出);exports_line() 支持按条目覆盖;新增 item_export_options()
storage_layout_deployer.py ensure_export() 判据改为内容比对(export_line_of(raw, path) == want),不一致则改写文件行 + exportfs -o 单条重导;新增 export_line_of()
config/slurm_deploy.yaml tools/pdk 增 export_options: "ro,async,no_subtree_check,no_root_squash"

验证

  • 离线自测三条导出行:projects=rw,…、tools=ro,…、pdk=ro,… → PASS (首轮 FAIL 即因白名单漏字段,未上机)
  • --storage-layout --dry-run:三条导出全部 [SKIP] 已导出,导出维度零待变更 → 部署器已认可现场的 ro,漂移消除
  • 回滚:backup/slurm_ins/*.before.20261007-030645 覆盖三个源文件即可(本次只跑 dry-run,未改任何节点)

核实结论(2026-10-07,HANDOFF #19 收口)

对全量 --storage-layout --dry-run 暴露的 8 项既有待变更逐项核实(只读实采 + 读判定代码):

项 数量 判定 依据
.28 的 /mnt/lustre 参数 + fstab + remount 3(同一根因) 真实漂移 实采:.28 挂载为 rw,checksum,encrypt,…(无 noatime)、fstab 为 defaults,nofail;对照 .18/.19/.20 均为 …,noatime,… / defaults,noatime,nofail。判据走 judge_tier0() → mount_options_matched(),判定逻辑本身正确
.18/.19/.20/.28 的 /projects/* 组目录 2775 4 误报 实采 4 台 × 8 个组目录权限均为 drwxrwsr-x root <组>(=2775)、属组正确、无缺失 —— 真实状态已达期望。根因:_ensure_content_dirs() 无任何前置判定,无条件执行 mkdir -p && chown && chmod 且无条件 add_change;实跑时命令本身幂等(零实际变化),dry-run 却全数列出。这是本工程内唯一没做三态判定的动作
[BACKUP] 占位 1 非变更 dry-run 下的备份占位记录

两条衍生结论

  1. .28 的 Lustre fstab 属跨工程口径不一致:.28 的 Lustre 挂载由 lustre_ins 的 add-clients-25-28 写入(defaults,nofail),而 slurm_ins storage_layout 期望 defaults,noatime,nofail —— 两个工程对同一挂载点期望不同,谁后跑谁生效。宜在 lustre_ins 侧对齐,或明确「.28 的 Lustre 由 storage_layout 纳管」。
  2. _ensure_content_dirs() 缺幂等判据:与「幂等判定要看内容差异,不能只看对象是否存在」相悖(此处连存在性都不看)。修法:先 probe 各目录的 mode/owner/group,仅对不匹配者执行并记 change —— 否则每轮 dry-run 都报 4 项假差异,真差异会被淹没。

处置结果(2026-10-07 已执行)

步 动作 结果
① _ensure_content_dirs() 加内容比对判据:新增 _probe_group_dirs(),先 stat -c '%a\|%U:%G' 逐组探测,仅对缺失/不匹配者执行 mkdir/chown/chmod 并记 change py_compile 通过;dry-run 待变更 8 → 4,4 项假差异消失
② 全量 --storage-layout 执行(先验 .28 无在跑作业:squeue = 0) rc=0;幂等复跑「待执行 0 / 幂等跳过 19」;.28 实测挂载 rw,noatime,…、fstab defaults,noatime,nofail → 漂移消除
③ 跨工程对齐:lustre_ins/modules/client_mount.py::_fstab_entry() 的 opts 由 defaults,nofail 改为 defaults,noatime,nofail 源已对齐;未跑 lustre_ins 部署器——跑它会顺带触发 .25–.27 的挂载修复,属其 §3.1 #14 尚未走完的流程,故登记为 lustre_ins HANDOFF #16,与 #14 合并执行

备份:backup/slurm_ins/storage_layout_deployer.py.before.20261007-031659、 backup/lustre_ins/modules/client_mount.py.before.20261007-032024。


来源:/home/rain/Documents/slurm/slurm_ins/nfs_export_ro_20261007/SOP.md | 最后更新:2026-10-07 03:20(由 build/sync.py 同步,请勿在站点侧修改)