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. 目标¶
/etc/exports中/data/nfs/tools、/data/nfs/pdk两行由rw改为ro。exportfs -v与内核导出表/var/lib/nfs/etab双证ro。- 客户端读不受影响;
exportfs -ra后其余 8 条导出保持原状。 - 幂等:同表达式再执行一次零变更。
3. 前置条件¶
- 本机对
.249root 免密 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 管理,暴露两层问题:
storage_layout.export_options是全局单一值(rw,async,no_subtree_check,no_root_squash),exports_line()对所有 tier1 条目套同一套选项 → 配置源与现场(ro)不一致;- 更隐蔽:
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 下的备份占位记录 |
两条衍生结论
.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纳管」。_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同步,请勿在站点侧修改)