跳转至

SOP — SSH 连接复用在本工程落地(slurm_ins 副本与共享库对齐)

主题目录:conn_reuse_20261007/|执行日期:2026-10-07|类型:C 类(改实现体)

1. 背景

ops-execution-efficiency §2 的口径是「要降的是会话数(握手数),不是命令条数」。 measured-baselines.md §三 记着 2026-09-30 已完成「SSH 连接复用」改造,实测 deploy 幂等复跑握手 44 → 14、耗时 26.5 s → 17.1 s。

但本次核查发现:那次改造落在共享库 /home/rain/py_public_modules/paramiko_module.py(备份见 k8s_ins/conn-reuse-refactor-20260930/backup/home/rain/py_public_modules/), 而 slurm_ins 的 deployer.py 用相对导入 from .utils.paramiko_module import ...,取的是本工程内的副本 slurm_ins/utils/paramiko_module.py —— 该副本从未同步那次改造。

副本的缺陷(改前实测):

  • SSHConnectionPool.get_connection() 的 docstring 直书「每次创建新连接」,self._connections 字典从未被写入(只在 close_connection / shutdown 里被读)→ 池化形同虚设;
  • exec_linux / upload_file / download_file 都在 finally 里 conn.close() → 每次远端调用建一条新连接、用完即关。

因此本工程(Slurm 6 节点)的每次 --dry-run 都在重复握手。2026-10-07 基线:slurm 主部署器 dry-run 墙钟 221.0 s(已含同期 sync_status 批量化的收益)。

另发现:slurm_ins/utils/paramiko_module.py 存在两份模块内容拼接的历史痕迹(第 1–423 行是一份含 SSHConnection 的模块,第 424 行起是第二份含 SSHConnectionPool 等;Python 后定义覆盖前定义,恰好能运行)。本次不动该结构,仅在 SOP 留痕,供后续清理参考。

2. 目标

# 目标 判据
1 本工程副本与共享库行为对齐(_pooled 归还语义) 离线自测通过;close() 对池内连接为「归还」、对池外为「真关」
2 任务内每台主机只建连一次 shutdown() 日志「本任务共新建连接 N 次」,N = 主机数
3 判定结果不变 dry-run 变更记录逐条一致(225 条 / 待执行 25 / 幂等 200)

3. 前置条件

  • 项目根 /home/rain/Documents/slurm/slurm_ins,.venv 可用;
  • 到 6 台节点的 SSH 凭据沿用 config/slurm_deploy.yaml(600,凭据不入文档);
  • 变更前完成 5.1 备份。

4. 影响范围

项 内容
变更对象 utils/paramiko_module.py(单文件)
影响面 全工程所有远端操作(exec_linux / upload_file / download_file 是唯一入口)
是否停服 否;验证全程 --dry-run(只读探测)
窗口期 无要求;集群无在跑作业
并发影响 同主机远端操作经 per-host RLock 串行;不同主机仍并发

5. 分步操作

5.1 备份(高危前置)

cd /home/rain/Documents/slurm/slurm_ins
mkdir -p conn_reuse_20261007/backup/slurm_ins/utils
cp -a utils/paramiko_module.py \
      conn_reuse_20261007/backup/slurm_ins/utils/paramiko_module.py.bak.$(date +%Y%m%d-%H%M%S)
ls -l conn_reuse_20261007/backup/slurm_ins/utils/

预期:1 个 .bak.<时间戳>(本次 STAMP = 20261007-043236)。坑:必须在任何编辑前完成。

5.2 SSHConnection:close() 拆成「归还 / 真关」

在 SSHConnection.__init__ 末尾加标记位;把 close() 的语义按共享库对齐(实现体:utils/paramiko_module.py):

# 概念等价(Python 侧实现,非 shell)
# __init__:            self._pooled = False
# close():             if self._pooled and self._is_active:  释放 SFTP、保留 transport、return  ← 归还
#                      else: close_force()
# close_force():       self._cleanup_internal(); self._is_active = False                       ← 真关

坑:_pooled 默认为 False,故 with SSHConnection(...) as c: 等池外用法行为不变(仍真关);只有 get_connection() 返回的连接会被置 True。

5.3 SSHConnectionPool:池化命中 + 计数 + per-host 可重入锁

# 概念等价(Python 侧实现)
# get_connection(host):
#     host_lock = acquire_host_lock(host)          # RLock,可重入
#     with host_lock:
#         cached = self._connections.get(host)
#         if cached and cached.is_active(): return cached          ← 命中复用
#         if cached: cached.close_force(); pop               ← 失活则清掉重建
#         conn = SSHConnection(...); conn.connect()
#         conn._pooled = True; self._connections[host] = conn
#         self._active_connections += 1; self._connect_count += 1
#         return conn
# shutdown(): 打印「本任务共新建连接 N 次 | 结束时保有 M 条」,逐条 close_force()

坑(必须留痕):

  • _host_locks 由 threading.Lock() 改为 threading.RLock():exec_linux 持锁后再调 get_connection,后者内部还要拿同一把锁 —— 用普通 Lock 会自锁死。
  • close_connection() / shutdown() / 失活清理一律用 close_force():这些场景要的是真关并从池移除,用 close() 会变成「归还」,连接留在池里泄漏。
  • _connect_count 只是诊断计数,用于验证复用效果,不参与任何判定。

5.4 三处调用点恢复「归还」语义

exec_linux / upload_file / download_file 三个函数的 finally 保留 conn.close()(此时语义已是「归还」):

# 概念等价
# finally:
#     if conn:
#         conn.close()   # 池内连接 → 归还(保留 transport 供复用);池外 → 真关

坑:这一步不能省——它保证 SFTP 通道及时释放;连接由 run() 收尾的 connection_pool.shutdown() 统一真关。

5.5 离线自测(上机前)

cd /home/rain/Documents/slurm/slurm_ins
.venv/bin/python -m py_compile utils/paramiko_module.py && echo COMPILE_OK
.venv/bin/python conn_reuse_20261007/selftest.py

预期:COMPILE_OK + 4 项断言 + CONN_REUSE_SELFTEST_OK connect_calls=3。

5.6 上机复验(只读)

cd /home/rain/Documents/slurm/slurm_ins
timeout 400 .venv/bin/python main.py --config config/slurm_deploy.yaml --dry-run 2>&1 | tee /tmp/connreuse.txt | tail -20

6. 验证

验证一:语义等价 —— dry-run 变更记录与基线逐条一致:

变更记录总数: 225    待执行变更: 25    幂等跳过: 200      (三次运行均同)

验证二:连接复用生效(本次新增的实证出口):

SSH连接池已关闭 | 本任务共新建连接 6 次 | 结束时保有 6 条

6 台主机 → 每台恰好 1 次建连,全程复用(改前为「每次远端调用一条连接」)。

验证三:墙钟(slurm 主部署器 --dry-run,同一命令):

状态 墙钟 建连
原始(sync_status 未批量化、无复用) 247.7 s 每次调用建连
+ sync_status 批量化(另一主题) 221.0 s 同上
+ 连接复用(本变更) 134.6 / 135.8 s(两次) 6 次

相对原始累计 -113 s(-45.6%);其中连接复用贡献 -86 s,是比 sync_status 批量化(-27 s)更大的杠杆。

验证四:离线自测 4 项 + POOLED_SEMANTICS_OK(池内 close() 为归还、close_force() 为真关)。

失败判断:任一 dry-run 变更记录与 225/25/200 不符,或建连数明显大于主机数 → 按第 7 章回滚。

7. 回滚

  • 备份位置:conn_reuse_20261007/backup/slurm_ins/utils/,台账见 conn_reuse_20261007/backup/README.md。
  • 回滚步骤:
cd /home/rain/Documents/slurm/slurm_ins
cp -a conn_reuse_20261007/backup/slurm_ins/utils/paramiko_module.py.bak.* utils/paramiko_module.py
  • 回滚后验证:py_compile + 跑一次 --dry-run,确认变更清单仍为 225/25/200。
  • 回滚触发条件:变更记录不符;建连数 > 主机数;出现连接相关异常(Transport 失效、命令超时激增)。

8. 风险说明

风险 缓解
长驻连接被 sshd 侧超时断开 connect() 本身有「活跃则复用」判断;get_connection 命中前先验 is_active(),失活即清理重建
同主机并发使用同一连接 引入 per-host RLock 串行化;_remote_query_many 本就按主机分线程,不同主机仍并发
连接泄漏(任务结束不关) BaseDeployer.run() 收尾固定调用 connection_pool.shutdown(),逐条 close_force() 并打印保有数
池外用法行为被改坏 _pooled 默认 False,with 语法与直接构造仍走真关路径
与共享库再次分叉 本次按共享库同一语义实现(_pooled + close_force),行为已对齐;两处并存属历史结构问题,见附录 B

附录 A 交付物清单

文件 说明
SOP.md 本文件
selftest.py 离线自测(monkeypatch connect/is_active,不连主机)
backup/slurm_ins/utils/paramiko_module.py.bak.20261007-043236 改前快照
backup/README.md 备份台账

附录 B 遗留(不在本次范围)

  1. 本工程副本 vs 共享库双份并存:slurm_ins/utils/paramiko_module.py(本文件,相对导入在用)与 /home/rain/py_public_modules/paramiko_module.py(.pth 挂载,其它工程用)各存一份。本次已做行为对齐,但结构分叉仍在——后续是否统一到共享库,需单独决策(涉及 .venv/.pth 与导入路径)。【2026-10-11 结案】 已由 paramiko_unify_20261007 决策并落地:删本工程 3 份同名副本、统一走共享库 .pth。
  2. 文件内含两份模块内容拼接(1–423 行 / 424 行起),当前可运行但易误读,建议另起一轮清理。【2026-10-11 结案】 副本删除后该拼接结构已消失(共享库为拍平版),无需再清理。
  3. sync_status 批量化在 submit_node / storage_layout / scratch_gc / local_scratch / node_env+module_env 上的遗留已于 2026-10-07 全部完成(submit_node_deployer.py::_sync_host 的收尾见 ../submit_node_batch_20261007/SOP.md)。原「仍未做」表述已过期——2026-10-07 复核时曾据此误判,附录已一并更正。

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