tmux 远程服务器实践:尽量降低 SSH 中断对命令的影响
问题背景#
在远程服务器上部署模型或运行长任务时,最让人焦虑的就是 SSH 断连导致命令被杀:
- 部署 vLLM 服务器,模型加载需要几分钟,突然网络抖动,进程直接中断
- 跑 benchmark 测试,笔记本合上盖子,测试直接失败
- 在咖啡厅或高铁上运维,网络不稳定,随时可能中断关键操作
这个问题的根源是:SSH 断连会发送 SIGHUP 信号,终端下的所有子进程都会被终止。
一个常见做法:用 tmux 在服务器上创建持久化会话。 即使你断开了 SSH,tmux 会话里的命令仍然在后台继续运行。
什么是 tmux?#
tmux(Terminal Multiplexer)是一个终端复用器,允许你在单个终端窗口中创建多个会话、窗口和窗格。它的核心价值在于:
- 会话持久化:会话运行在服务器端,不依赖你的 SSH 连接
- 网络韧性:WiFi 断线、VPN 断开、笔记本睡眠,都不会影响服务器上的进程
- 多窗口/多窗格:可以在一个终端里同时运行命令、查看日志、监控资源
这与 Screen 类似,但 tmux 在今天的资料和使用案例里更常见一些。
一些外部实践参考#
tmux 在 ML/DevOps 场景里已经很常见。下面是一些我阅读过、也觉得有参考价值的材料:
Weights & Biases(W&B)官方教程#
W&B 在其 ML 从业者 tmux 教程(2022)里提到了一些做法:
- 为每个实验创建命名会话(
tmux new -s <experiment_name>) - 使用
Ctrl+B, d安全分离,尽量避免直接关闭终端 - 配置扩展的回滚缓冲区(
history-limit 50000)保留训练日志 - 在状态栏显示 GPU/服务器指标
tmux-trainsh:GPU 工作流自动化工具#
tmux-trainsh(2026)是一个专门为 GPU 和远程服务器设计的工作流运行器。它的核心理念是 “terminal-first”:
- 用 Python 编排远程任务,在 tmux 会话中执行
- 支持在 Vast.ai、RunPod 等云 GPU 服务上远程部署
- 自动管理 tmux 会话生命周期
DataMade 团队约定#
DataMade 工程团队使用以下约定:
- 始终以特定用户运行 tmux(如
ubuntu) - 会话命名格式:
[用户名]-[进程描述] - 进程完成后立即清理会话,避免混乱
DevOps 侧的一些经验#
Mark Callen 在 Why Every DevOps Engineer Should Be Using tmux(2025)中总结:
- 会话持久化是 DevOps 的"安全网"
- 自动在 SSH 登录时启动/附加 tmux
- 使用窗格分割实现命令+监控并排运行
基础用法#
1. 创建命名会话#
ssh admin@100.97.87.120
tmux new -s vllm-deploy
2. 在会话中运行命令#
# 在 tmux 会话内执行
docker run -d --name vllm-server --gpus all nvcr.io/nvidia/vllm:26.01-py3
3. 安全分离(不杀进程)#
按 Ctrl+B,然后按 d
这会分离会话,但命令仍在服务器后台运行。
4. 重新附加#
ssh admin@100.97.87.120
tmux attach -t vllm-deploy
5. 列出所有会话#
tmux list-sessions
6. 清理完成的会话#
tmux kill-session -t vllm-deploy
进阶:窗格分割用于实时监控#
tmux 允许你在同一个窗口中分割出多个窗格,非常适合一边跑命令一边监控:
# 在 tmux 会话内
Ctrl+B, % # 垂直分割
Ctrl+B, " # 水平分割
# 在不同窗格中运行不同命令
# 窗格 1: docker logs -f vllm-server
# 窗格 2: nvidia-smi -l 2
# 窗格 3: free -h -s 5
在本项目中的集成#
在 nv-dgx-spark 项目中,我已经将 tmux 集成到 Makefile 中,让远程部署更加可靠。
可用的 tmux 命令#
# 在 tmux 会话中部署 vLLM(不怕 SSH 断连)
make tmux-vllm-deploy
# 在 tmux 中运行任意命令
make tmux-cmd COMMAND="docker pull nvcr.io/nvidia/vllm:26.01-py3" SESSION="docker-pull"
# 查看服务器上的 tmux 会话列表
make tmux-list HOST=100.97.87.120
# 重新附加到某个会话
make tmux-attach HOST=100.97.87.120 SESSION=vllm-deploy
# 在 tmux 中跑 benchmark(长时间运行也安全)
make tmux-benchmark
# 清理完成的会话
make tmux-kill HOST=100.97.87.120 SESSION=vllm-benchmark
tmux 辅助脚本#
项目还提供了一个 tmux 管理器脚本:
# 创建会话
./scripts/tmux-manager.sh 100.97.87.120 new my-task "docker logs -f vllm-server"
# 附加到会话
./scripts/tmux-manager.sh 100.97.87.120 attach my-task
# 列出会话
./scripts/tmux-manager.sh 100.97.87.120 list
# 杀死会话
./scripts/tmux-manager.sh 100.97.87.120 kill my-task
tmux vs nohup vs systemd#
| 方案 | 持久化 | 交互性 | 日志管理 | 适用场景 |
|---|---|---|---|---|
| tmux | ✓ | ✓(可重新附加) | 手动(可用 tee) |
交互式部署、调试、benchmark |
| nohup | ✓ | ✗ | 输出到文件 | 简单的一次性任务 |
| systemd | ✓ | ✗ | journalctl | 生产服务长期运行 |
一种常见的分工:
- 部署/调试阶段:用 tmux,可视化、可交互、随时查看进度
- 生产运行阶段:用 systemd service(项目已提供
config/vllm.service),自动重启、日志管理
实践小结#
结合上面的资料和我自己的使用场景,我现在更常用下面这套 tmux 工作流程:
1. 始终使用命名会话#
# ✓ 好:有意义的名称
tmux new -s vllm-deploy-qwen7b
# ✗ 避免:无意义的自动生成 ID
tmux new
2. 正确分离#
Ctrl+B, d # ✓ 正确:安全分离
Ctrl+D # ✗ 错误:关闭 shell,杀死会话
3. 及时清理完成的会话#
# 确认任务完成后
tmux kill-session -t vllm-deploy-qwen7b
4. SSH 登录时自动附加 tmux(可选)#
在远程服务器的 ~/.bashrc 中添加:
# Auto-attach to tmux on SSH login
if [[ -z "$TMUX" ]] && [[ -n "$SSH_CONNECTION" ]]; then
tmux attach -t main || tmux new -s main
fi
这样每次 SSH 登录都会自动进入或创建 tmux 会话。
5. 配置扩展回滚缓冲区#
在远程服务器的 ~/.tmux.conf 中添加:
set -g history-limit 50000 # 保留 50000 行回滚
set -g mouse on # 启用鼠标支持
真实场景示例#
场景 1:在高铁上部署 vLLM#
# 在 tmux 中启动部署
make tmux-vllm-deploy
# 安全分离
# (Ctrl+B, d 或在 Makefile 中已自动分离)
# 网络断了?没关系,部署仍在进行
# 网络恢复后,重新查看进度
make tmux-attach HOST=100.97.87.120 SESSION=vllm-deploy
场景 2:跑长时间 benchmark#
# 在 tmux 中启动(不会因为合上笔记本而中断)
make tmux-benchmark
# 查看结果
ssh admin@100.97.87.120 "tail -f /tmp/vllm-benchmark.log"
场景 3:多节点调试#
# 在节点 A 上监控日志
./scripts/tmux-manager.sh 100.97.87.120 new nodeA-watch "docker logs -f vllm-server"
# 在节点 B 上跑测试
./scripts/tmux-manager.sh 100.67.164.92 new nodeB-test "curl http://localhost:8000/health"
# 随时切换查看
./scripts/tmux-manager.sh 100.97.87.120 attach nodeA-watch
./scripts/tmux-manager.sh 100.67.164.92 attach nodeB-test