☰
服务器上运行Jupyter并进入虚拟环境:SSH隧道与内核注册实践
2026/10/24 9:14:23 网站建设 项目流程

实验室里的服务器平台,怎么把 Jupyter Notebook 真正用起来,并且让 Jupyter 跑进自己创建的虚拟环境?这问题看起来是两句命令的事,但实际操作时,我在不同机器上踩过的坑、帮人排查过的案例,少说也有一打。很多同学拿着本地电脑上“打开 Anaconda → 启动 Jupyter”的经验,到服务器上就懵了:机房里的机器通常没有显示器,Linux 命令行界面摆在眼前,浏览器却在你自己电脑上,Jupyter 默认监听的 localhost 又是它自己。这一堆概念叠在一起,核心问题其实就一个:怎样让远在机房的 Jupyter 服务,安全地接到你本地浏览器里,并且保证它用的解释器是你虚拟环境里那个 Python,而不是系统自带的那套。

这篇文章就围绕这个需求展开。内容分为几块:为什么很多人放着本地不用非要去服务器跑 Notebook、环境准备阶段装什么才不容易翻车、怎么把 Jupyter 服务启动起来并让浏览器连上、以及最关键的一步——如何让 Jupyter 进入虚拟环境。最后我会把实际使用中最常遇到的几个故障整理成速查表,每个问题都附上排查思路。适合刚接触实验室服务器的学生、经常在远程机器上做数据分析的工程师,以及需要多人共用一台 GPU 服务器的团队参考。文里所有操作都是我在类似场景下反复验证过的常规做法,照着抄基本能跑通。

1. 为什么要把 Jupyter Notebook 放到服务器上,而不是本地随便跑跑

1.1 服务器跑 Notebook 的场景价值

先说说需求来源。现在深度学习、数据处理这类活儿,数据量和模型规模越来越大,本地笔记本那点内存和显卡根本撑不住。我认识一位做图像检测的同学,本地机器 16G 内存,加载一个中等规模的数据集就开始疯狂换页,训练一个晚上它先把自己卡死了。后来把数据和代码都扔到实验室的服务器上,内存 128G 起步,GPU 显存也宽裕,同样的预处理脚本几分钟跑完,训练过程也不再受浏览器、办公软件抢占资源的干扰。

除了算力差距,还有数据落位的问题。很多实验室的采集数据直接存在服务器本地磁盘,动辄几百 GB 甚至上 TB。与其费劲往本地拷,不如直接在服务器上开 Notebook 做分析和建模。再一个就是多端访问的需求——在实验室用台式机连一次,回到宿舍用笔记本还能接着连同一个服务,浏览器一开就是之前的代码和输出状态,这对需要长时间跑实验、反复调参的场景非常实用。

1.2 服务器版 Jupyter 和本地版有什么本质区别

本地跑 Jupyter 的时候,服务启动在 127.0.0.1:8888,浏览器访问的就是本机程序,一切都在一台机器内部完成。服务器上跑 Jupyter 则完全不同:计算进程在服务器,交互界面在本地浏览器,中间隔了一整条网络链路。这带来两个直观问题:

第一,安全边界变了。本地服务默认只能自己访问,服务器上如果监听全网段地址,任何能连到这台机器的人都有可能访问你的 Notebook 页面。所以服务器上跑 Jupyter 不能头脑一热直接--ip=0.0.0.0,至少要有密码校验和 SSH 通道保护。

第二,环境归属变了。本地电脑上的 Jupyter 默认用的就是你自己装的 Python 环境,服务器上则可能同时存在系统 Python、Anaconda 基础环境、多个虚拟环境,你必须明确让 Jupyter 使用哪一个解释器。这也是“进入虚拟环境”这个诉求的本质——不是你在终端里conda activate一下就完了,而是要让 Jupyter 的 Python 内核指向那个环境里的具体解释器。

理解了这两点区别,后面所有操作就都有了判断标准:一切配置的目的,都是为了在“可访问”和“不裸奔”之间找到平衡,在“能用”和“用的是对的环境”之间不出岔子。

2. 环境准备:用一个干净的虚拟环境承载 Jupyter

2.1 推荐用 Miniforge 或 Anaconda,而不是直接碰系统 Python

实验室的服务器通常多人共用,系统级 Python 往往由管理员统一维护。直接在系统 Python 里pip install一堆包,很容易把依赖搞乱,轻则影响别人的项目,重则把系统环境弄到无法恢复。我的一贯做法是:每个项目建一个独立的虚拟环境,所有 Python 层面的东西都装在自己的环境里,绝不去动系统 Python。

工具选择上,我一般用 Miniforge。它比 Anaconda 轻量,安装快,而且默认走社区软件源,在学术网络环境下拉包通常更顺畅。安装方式很简单,下载对应架构的脚本后执行:

wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh bash Miniforge3-Linux-x86_64.sh -b -p ~/miniforge3

安装完成后再初始化 shell 配置,让conda命令直接可用:

~/miniforge3/bin/conda init bash source ~/.bashrc

如果机器上已经有 Anaconda,那也没必要再装 Miniforge,直接用现有的 conda 就行。关键是统一入口:先确认which conda指向哪里,别搞出两套 conda 互相打架的情况。

2.2 创建虚拟环境,并把 Jupyter 装进这个环境里

环境准备好了,接下来就是建一个专门用于 Jupyter 的虚拟环境。我习惯按项目建环境,比如py310_lab代表 Python 3.10 的实验室通用环境:

conda create -n py310_lab python=3.10 -y conda activate py310_lab

环境建好并激活后,在该环境内安装 Jupyter 组件。这一步的关键是:不要回到 base 环境里去装,否则会出现“Jupyter 在 base、解释器想要 py310_lab”这种张冠李戴的情况。安装命令:

conda install jupyter notebook ipykernel -y

这里我把ipykernel一起装了,后面注册内核会用到。如果你偏好 JupyterLab 的界面,也可以把jupyterlab一起装上,两者并不冲突。安装完以后可以顺手验证一下当前环境的 Python 路径:

which python which jupyter

正常情况下,两个路径应该都指向~/miniforge3/envs/py310_lab/bin/这个目录。如果jupyter指向了别处,说明 PATH 顺序有问题,这个问题我在第 5 节会专门讲。这一刻就把环境理顺,后面会省很多事。

3. 打通链路:启动 Jupyter 服务并让浏览器连上服务器

3.1 启动前的三个必要配置

第一次在服务器上跑 Jupyter,裸执行jupyter notebook基本一开一个坑。我习惯先做三个配置,顺序固定,每次新机器上都是这套流程。

第一步,生成配置文件:

jupyter notebook --generate-config

这个命令会在~/.jupyter/下生成jupyter_notebook_config.py,后续改动都基于它。

第二步,设置访问密码。用自带的 password 子命令,它会让你输入两遍密码,然后自动把哈希值写进配置文件:

jupyter notebook password

这一步比手动编辑配置文件里的NotebookApp.password字段靠谱得多,因为格式、转义这些细节工具都替你处理好了。设置完成后,浏览器访问时就需要输入这个密码。

第三步,明确监听地址和端口。默认配置下 Jupyter 只监听 127.0.0.1,这是安全的行为,配合下一节要讲的 SSH 端口转发,完全可以满足远程访问需求。千万别上来就改成 0.0.0.0 加公网可访问,除非你对网络安全有十足的把握,并且清楚自己在做什么。端口建议选 8888 或 8889 这类常见范围,如果被占用可以换。

3.2 一个安全且好用的访问方案:SSH 本地端口转发

既然 Jupyter 监听在服务器的 127.0.0.1,你的浏览器怎么访问它?答案是让 SSH 做一座桥。

在你自己电脑的终端里执行:

ssh -N -L 8888:localhost:8888 用户名@服务器地址

这条命令的含义是:把本地机器的 8888 端口和服务器上的 8888 端口连起来。本地浏览器访问http://localhost:8888时,流量会通过 SSH 加密通道直接送到服务器上的 Jupyter。这样做的好处非常明显:数据全程加密,别人截获不了你在 Notebook 里传的模型、代码和结果;服务器上不需要对公网开放任何额外端口,攻击面小很多;而且不需要记住服务器的 IP 和端口组合,本地地址始终是 localhost。

第一次用 SSH 端口转发时,记住保持这个终端窗口别关。如果关了,隧道就断了,本地浏览器自然访问不到。想在后台保持隧道不退出,可以用ssh -f -N -L ...让 SSH 在后台运行,但这样关闭时不太方便。我通常就是把窗口最小化,等不用了再按 Ctrl+C 关掉。

3.3 让 Jupyter 常驻后台,关掉终端也不会断

直接从终端启动的 Jupyter 进程,一旦 SSH 会话断开或者命令行窗口关闭,服务就会收到挂断信号然后终止。这对临时用一下不成问题,但如果是要在服务器上跑一个持续几天的训练任务,或者明天还要继续用同一个 Notebook,服务就最好常驻。

最简单的做法是用 nohup:

conda activate py310_lab nohup jupyter notebook --no-browser --port=8888 > ~/jupyter.log 2>&1 &

拆解一下这条命令:--no-browser告诉 Jupyter 不要在服务器上尝试打开浏览器,服务器也没浏览器可开;日志重定向到~/jupyter.log,方便之后排查问题;最后的&让命令立即返回,进程在后台运行。

如果条件允许,我更推荐用 systemd 管理 Jupyter 服务,这样进程生命周期更可控,崩溃了还能自动重启,开机也能自启。新建一个服务文件/etc/systemd/system/jupyter.service(需要 sudo 权限):

[Unit] Description=Jupyter Notebook Server After=network.target [Service] Type=simple User=你的用户名 WorkingDirectory=/home/你的用户名 ExecStart=/home/你的用户名/miniforge3/envs/py310_lab/bin/jupyter notebook --no-browser --port=8888 Restart=always RestartSec=5 Environment="PATH=/home/你的用户名/miniforge3/envs/py310_lab/bin:/usr/bin:/bin" [Install] WantedBy=multi-user.target

然后执行:

sudo systemctl daemon-reload sudo systemctl start jupyter sudo systemctl enable jupyter

注意 ExecStart 里我写的是绝对路径,而不是直接写jupyter,因为 systemd 启动时不会加载你的 shell 配置和 conda 环境,找不全 PATH。这个坑我当时踩过一次,服务看上去启动了但立即退出,日志里全是命令找不到。用绝对路径之后,一切恢复正常。

3.4 浏览器访问与基础验证

启动好服务、建立好 SSH 隧道之后,在本地浏览器打开:

http://localhost:8888

如果是首次启动并且没设置密码,Jupyter 会要求输入 token,这个 token 打印在启动日志或者终端输出里,格式类似?token=一串字符。设置了密码之后就简单了,直接输入密码即可。

进入主界面后先建一个空白 Notebook,执行下面两行代码验证基础链路:

import sys print(sys.executable)

如果输出路径包含py310_lab,恭喜你,Jupyter 已经跑在目标环境里了。如果路径显示的是其他环境,那就继续看第 4 节。

4. 核心操作:让 Jupyter 真正进入虚拟环境

4.1 方法一:启动前先激活环境,再在环境内启动 Jupyter

这是最直白的思路:既然要 Jupyter 用我这个环境,那我先进环境再启动它。操作步骤如下:

conda activate py310_lab which jupyter

第一反应是检查which jupyter,确认它指向~/miniforge3/envs/py310_lab/bin/jupyter。如果不是,说明你的 PATH 里 conda 的路径没排在最前,或者存在多个 Jupyter 安装。这时别直接跑jupyter notebook,而是用 Python 模块方式启动,这样一定用的是当前环境的解释器:

python -m jupyter notebook --no-browser --port=8888

python -m的语义是“用当前 Python 解释器去运行 jupyter 模块”,它天然和当前环境绑定,不会受 PATH 里其他 Jupyter 的影响。这是我在多环境服务器上最稳的启动方式。

这个方法的好处是直观、不需要额外配置。代价是服务一旦启动,它默认的 Python 内核就是启动它的那个环境,如果你还想让 Jupyter 里能选择其他环境,那就得用第二种方法。

4.2 方法二:把虚拟环境注册为 Jupyter 内核,随切随用(推荐)

第二种方法解决的是多环境并存的场景。你不必为了某个环境单独启动一整个 Jupyter 服务,而是在一个 Jupyter 里随时切换不同内核。

做法是,在要暴露给 Jupyter 的那个虚拟环境里,执行:

conda activate py310_lab python -m ipykernel install --user --name "py310-lab-kernel" --display-name "Python (py310_lab)"

这条命令做的事情,是在用户目录下创建一个内核描述文件,路径一般在:

~/.local/share/jupyter/kernels/py310-lab-kernel/

里面有一个kernel.json,内容大致是:

{ "argv": [ "/home/用户名/miniforge3/envs/py310_lab/bin/python", "-m", "ipykernel_launcher", "-f", "{connection_file}" ], "display_name": "Python (py310_lab)", "language": "python" }

这个 JSON 记录了三样关键信息:用哪个解释器启动内核、在界面上显示成什么名字、语言类型。只要这个文件存在,Jupyter 启动后就会在 Kernel 菜单里列出对应的内核选项。用户可以在新建 Notebook 时选择,也可以在已有 Notebook 里点击 Kernel → Change Kernel 切换。

这里有几个值得注意的细节:

--name是内核的内部标识,尽量用短横线风格的英文,不要带空格;--display-name是显示在界面上的名字,随便写中文都行。--user表示把内核注册到当前用户目录,这样只有自己能选择这个内核。如果希望机器上所有用户都能用,可以改成--sys-prefix,但一般实验室场景用--user就够了,还能避免互相干扰。

注册完内核后,刷新浏览器里的 Jupyter 页面,点新建 Notebook 的按钮,下拉列表里应该能看到“Python (py310_lab)”这个选项。选它,就是一个跑在py310_lab环境里的 Notebook 了。

4.3 环境是别人建的,我也想用,怎么办

实验室里经常会遇到一种情况:环境不是你创建的,但你需要用它跑代码。比如管理员装好了一个带 PyTorch 的环境,你只需要调用它。这时候你不需要新建环境,只需要查看那个环境的 Python 路径,然后注册内核。

conda activate 那个环境的名字 which python

拿到路径后,直接用那个环境的 Python 安装 ipykernel 并注册,方法同 4.2:

/opt/某个环境/bin/python -m ipykernel install --user --name "别人的环境" --display-name "Python (别人的环境)"

注意,如果那个环境是管理员所有,你对它没有写权限,就别尝试往里装东西了。这种情况下更稳妥的做法是:复制一份环境配置到自己名下,比如用conda env export > environment.yml导出依赖,再conda env create -f environment.yml重建一个属于你自己的环境。这样既能复现依赖,也不动别人的地盘。如果环境本身可以正常 import 所有包、只是缺少 ipykernel,而你又实在没法安装,也可以直接手工写一个 kernel.json 指向那个环境的 Python——但说实话,这个做法很脆,一旦依赖补丁稍有变化就容易失效,我一般不推荐,除非是临时用一下。

4.4 如何确认 Notebook 真的在使用目标环境

很多“我明明注册了内核,为什么还是找不到包”的问题,本质上就是解释器选错了。判断方法很简单:在 Notebook 里执行以下代码:

import sys print(sys.executable) import os print(os.environ.get("CONDA_PREFIX", "No conda env activated"))

如果输出的 Python 路径指向py310_lab且 CONDA_PREFIX 显示py310_lab,说明内核和环境完全对齐。如果sys.executable指向 base 或其他环境,说明当前 Notebook 用的内核不对,需要手动切换内核,然后Restart Kernel,再重新执行。

还有一个容易被忽略的坑:切换内核后,已经导入过的模块不会自动重新加载。比如你在 A 内核里import numpy了,然后切到 B 内核,变量可能还残留着旧的模块引用。这不是 Jupyter 的 bug,而是 Python 进程复用时导入表的既有行为。所以切换内核后的第一件事永远是 Kernel → Restart,而不是直接继续运行。

5. 常见问题与排查技巧实录

5.1 浏览器访问出现连接失败或 403

连接失败通常是三种原因:Jupyter 服务根本没起来、SSH 隧道没建立、端口被防火墙挡住。

排查顺序我建议是:先在服务器本地确认端口监听状态

ss -tlnp | grep 8888

如果看到 Jupyter 进程在监听,那问题就出在隧道或防火墙。SSH 隧道建立后,在你本地执行

curl -I http://localhost:8888

能返回 HTTP 响应头说明隧道通了。如果本地连接被拒,多半是 SSH 命令里的端口号、主机名写错,重新核对即可。

403 的情况就微妙一些。Jupyter 新版对跨域请求和 Host 头校验比较严格,如果你通过一个奇怪的域名或者代理方式访问,可能被拒。优先保证访问地址是 localhost,避免走中间跳转。另外 token 过期也会表现为访问被拒,重新登录一次即可。

5.2 明明在 conda 环境里,which jupyter却指向系统目录

这个问题几乎在每个新配置的多用户服务器上都会出现。原因很简单:shell 的 PATH 里,系统 Jupyter 所在的目录排在 conda 环境目录前面,操作系统在解析命令时先找到了它。

排查办法:

echo $PATH

如果发现 conda 环境目录不在最前,重新conda activate py310_lab,然后确认:

echo $CONDA_PREFIX

激活成功后,CONDA_PREFIX应该指向你的环境目录,同时which python应该指向环境内的解释器。如果这些都没问题但which jupyter还是不对,那就是环境里确实没装 jupyter,补装即可:

conda install jupyter ipykernel -y

如果不想依赖 PATH,直接用python -m jupyter notebook启动,可以彻底绕开这个问题。

5.3 Notebook 里 import 不到刚用 pip 安装的包

进程启动之后,Python 的模块搜索路径就基本固定了。你在终端里pip install了新包,但 Notebook 里 import 还是失败,常见原因有两个:

一是安装位置不对。终端里的 pip 和环境内的 pip 指向了两个不同的 site-packages。运行python -m pip install而不是裸pip install,可以保证它和当前解释器绑定。

二是内核没重启。Python 解释器不会动态刷新已加载的模块路径。安装完依赖后,在 Notebook 里 Kernel → Restart,再重新 import。这个动作虽然简单,但至少一半的“找不到包”问题靠它就能解决。

5.4 Jupyter 跑一段时间就断,或者无法访问

长时间运行后连接断开,先看服务器端进程还在不在:

ps aux | grep jupyter | grep -v grep

进程还在,那就考虑资源问题。训练任务占满内存导致系统 OOM,Jupyter 进程被内核杀掉,这种事我用 nohup 起服务时常遇到。排查方法是查看启动时重定向的日志文件,比如之前写的~/jupyter.log,末尾会记录服务被终止的原因。如果是 OOM,要么减少单次加载的数据量,要么换更大内存的机器跑。

另一种常见情况是使用了终端启动 Jupyter,然后 SSH 断开了,进程随之退出。如果不想用 systemd,也可以在启动命令前加tmux或screen,把 Jupyter 放在独立的会话里运行,这样 SSH 断开不会影响它。

5.5 显卡相关库识别不到

很多人反映进入 Notebook 后import torch不报错,但torch.cuda.is_available()返回 False。先别急着重装驱动,按顺序排查:先执行

nvidia-smi

确认驱动和 GPU 设备正常。然后在 Notebook 里检查内核解释器路径,确认它是不是你装 GPU 版依赖的那个环境。最常见的问题是内核选错,装 PyTorch 的环境是 A 环境,结果 Notebook 跑在 B 环境里,自然识别不到 CUDA。

还有一类问题是 conda 环境与 CUDA 版本不匹配。安装 GPU 相关依赖时,建议先确认当前环境的 CUDA 运行库版本,再选择匹配的预编译包。这个检查比反复重装驱动要省时得多,我见过太多人一开始就怀疑驱动,结果浪费时间。

5.6 配置了密码,但浏览器不让你登录

设置完jupyter notebook password后,如果浏览器端还是反复要求 token 而不接受密码,通常是因为同时有多个 Jupyter 服务在跑,你连接的是那个没配置密码的旧进程。比如之前用 nohup 启动了服务,密码是后来才设置的,不重启旧进程,它读的还是旧配置。

解决思路很简单:把所有 Jupyter 进程停掉再重新启动。停掉之前注意保存 Notebook,避免意外丢失未保存的内容。

6. 最后分享几点个人体会

整套流程跑通之后,真正影响使用体验的往往是一些小习惯。我在多台服务器上折腾过这套东西,有一点体会特别深:Notebook 适合做探索、调试和实验记录,不适合做长周期训练的执行载体。真到了要持续跑十几个小时甚至几天的训练任务,我还是会把代码整理成.py脚本,用后台方式运行,再配合日志记录。Notebook 太适合交互,但在断线重连、进程持久性上面天然弱于脚本。

另外一个经验是环境命名规范。实验室多用户共用的机器,内核名称如果都起成 “python3” 这种模棱两可的名字,用的时候根本分不清谁是谁。我习惯把内核名和项目、环境绑定起来,比如nlp_bert_env、cv_detection_py310。这样不仅自己看着清晰,其他同事用起来也少踩坑。

还有一点容易被忽视:个人目录配额。服务器上的 Notebook 会存储大量 checkpoint、中间结果和调试数据。如果都堆在用户目录下,磁盘配额很快耗尽。我一般建议在代码里,把较大的中间数据或者模型输出重定向到共享数据盘,或者在项目目录下建立清晰的 data、model、log 子目录,定期清理临时文件。

最后一个实用小技巧:把你常用的启动命令固化成脚本或别名,放在环境配置里。比如在~/.bashrc中写一个函数:

alias nb='ssh -N -L 8888:localhost:8888 用户名@服务器地址'

以后每次只需要敲nb,隧道就建好了。Jupyter 服务如果已经常驻,什么都不用再管,直接开浏览器访问 localhost 就行。

这套方案我在不同的实验室服务器上复现过很多次,从装机到稳定使用,整个过程不超过半小时。如果你正卡在某个环节,不妨对照这篇内容一步一步检查,尤其是第 4 节的内核注册和第 5 节的排查思路,大部分问题都能在那里找到答案。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询