整理一下 jupyter notebook 的各种使用方式。

本地部署

直接在本地主机上启动 jupyter notebook 服务是最简单的用法

1
jupyter notebook

需要说明的是:

  • jupyter服务默认监听本机的 8888 端口,如果本机的 8888 端口已经被占用,在启动时会自动递增,例如改为 8889 端口等,也可以使用--port选项指定;
  • jupyter 在启动时会尝试打开本地浏览器,或者手动通过http://localhost:8888/访问即可,可以加上选项阻止自动开启浏览器 --no-browser
  • 默认只允许接收来自localhost的请求,可以使用 --ip 选项指定,例如--ip=0.0.0.0代表允许所有的ip访问;
  • 默认的工作目录是执行命令时所处的目录,可以在后面加上工作目录作为位置参数,例如jupyter notebook /path/to/notebooks,还可以使用 --notebook-dir 选项指定,例如--notebook-dir=/path/to/notebooks

除了直接在前台运行,在Linux中还可以使用下面的操作启动 jupyter 服务并在后台运行,将输出定向到指定的日志中(需要完整路径)

1
nohup jupyter notebook > /path/to/jupyter.log 2>&1 &

jupyter服务如果在前台运行,直接 ctrl-C 两次即可关闭。

在Linux中,如果 jupyter 服务在后台运行,可以使用下面的命令查询相关的进程

1
2
3
4
5
# 查询所有 jupyter 相关的进程
ps -aux | grep jupyter

# 查询当前用户的所有进程
ps ux

然后获取 PID,直接 kill 即可,注意不要与查询命令本身的进程混淆

1
kill <PID>

远程服务器部署

现在我们考虑在服务器上运行的 jupyter notebook,如何部署使得本地可以访问?

  • 直接部署
  • 间接部署
    • 在服务器上使用 nginx 反向代理;
    • 在本机上使用 ssh 本地转发。

直接部署

直接部署 Jupyter Notebook 意味着在服务器上启动 Jupyter Notebook 服务并监听端口,默认启动方式如下

1
jupyter notebook

但是这种做法只允许来自localhost的访问,外部是无法访问的。

如果在启动时加上选项--ip=0.0.0.0,这代表允许所有IP地址访问。

1
jupyter notebook --ip=0.0.0.0

此时在本地通过ip和端口访问 http://<host-ip>:8888/ 即可。 这种方式简单,但可能会带来安全风险,需要注意配置防火墙并使用安全的认证方式。

nginx 反向代理

可以考虑在服务器上使用 Nginx 作为反向代理,转发请求到 Jupyter 服务,这可以为 Jupyter 提供更好的安全性和性能(例如可以支持 SSL 加密)。

下面是nginx的配置文件参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
server {
# listen 80;
listen 12139 ssl;
server_name jupyter.fenglielie.top;

ssl_certificate /home/fenglielie/.config/ssl/jupyter.fenglielie.top_nginx/jupyter.fenglielie.top.crt;
ssl_certificate_key /home/fenglielie/.config/ssl/jupyter.fenglielie.top_nginx/jupyter.fenglielie.top.key;
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

location / {
proxy_pass http://localhost:8888; # Jupyter Notebook 的地址和端口

proxy_set_header Host $host;
proxy_set_header X-Real-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

proxy_read_timeout 120s;
proxy_next_upstream error;
proxy_redirect off;
proxy_buffering off;

# 设置跨域头部
add_header 'Access-Control-Allow-Origin' '*'; # 将域名替换为您的域名
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Allow-Credentials' 'true';

# 额外的跨域配置,根据需要添加
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
}
}

解释:

  • 域名 jupyter.fenglielie.top,启用 ssl
  • 监听本机的 12139 端口(不是默认的443端口,因此不可省略),转发到本地的 8888 端口的 jupyter notebook/jupyter lab 服务
  • 如果提供自签名的 ssl 证书,浏览器可能会警告,这只是针对证书来源的可靠性警告,只要没有泄露,都不影响加密效果
  • 这里还涉及到跨域问题,因此进行了相应设置

在服务器上正常启动

1
jupyter notebook

在本地访问 https://jupyter.fenglielie.top:12139 即可。

这里需要修改jupyter服务的如下配置项,配置文件的生成见下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# jupyter_notebook_config.py

c.NotebookApp.allow_origin = '*'
c.NotebookApp.allow_remote_access = True
c.NotebookApp.notebook_dir = '/home/fenglielie/notebooks/'
c.NotebookApp.open_browser = False
c.NotebookApp.password = ****************************
c.NotebookApp.token = ''

# jupyter_lab_config.py

c.ServerApp.allow_origin = '*'
c.ServerApp.allow_remote_access = True
c.ServerApp.notebook_dir = '/home/fenglielie/notebooks/'
c.ServerApp.open_browser = False
c.ServerApp.password = ****************************
c.ServerApp.token = ''

解释一下这里的内容:

  • allow_origin 允许所有来源(处理跨域请求问题)
  • allow_remote_access 允许远程访问,默认为false,这代表 jupyter 会检查 HTTP 请求的 Host 头部,如果它指向一个非本地的地址就会返回 403,这里我们需要支持通过服务器域名和ip的访问,必须设置为 true。
  • notebook_dir 默认工作目录
  • open_browser 是否尝试开启浏览器
  • password token 这两个与访问控制有关,这里的含义是提供固定的密码,禁用token,具体细节见下文

注意:

  • 这两组配置是相似的却不完全一样的,在版本更新(包括从notebook到lab的大升级),有的选项可能被舍弃,进而名称发生变化。
  • allow_originallow_remote_access 是接收外部访问的相关网络配置,具体我也不太理解,这里不需要设置监听ip是因为通过nginx进行了反向代理,下文中的ssh本地转发不需要设置这两项,但是需要设置监听的ip。

ssh 本地转发

相比前几种做法,在本机上使用 SSH 本地转发的做法可能最为安全,因为它只能通过 SSH 隧道访问。

假设 jupyter notebook 在服务器 C 启动,监听 8888 端口

1
jupyter notebook

本地主机 A 可以 ssh 正常访问到服务器 C,那么在本地主机 A 执行如下命令(不要关闭这个会话,或者加上特定选项,使隧道即使在会话结束后仍然保持连接)

1
ssh -N -L 8889:localhost:8888 user@hostname

此时本地主机 A 就可以通过浏览器访问 http://localhost:8888/,这实际上会访问到服务器 C 的8888端口,可以使用服务器上的 jupyter notebook 服务。

值得注意的是,我们没有修改ip选项,此时 jupyter 服务仍然只接收来自localhost的请求,为什么上述做法可行? 因为 ssh 本地转发大致可以理解为:

  • 本地ssh服务监听本地的8889端口
  • 本地ssh将数据发送到服务器的22号端口(或者其它端口,取决于ssh的配置)
  • 服务器的sshd服务将收到的数据转发到本地的8888端口

因此jupyter服务接收的仍然是来自localhost的请求。

在本地的vscode中使用jupyter插件,使用http://localhost:8888/加上token或密码也可以访问到对应的jupyter内核。 使用jupyter内核与直接访问jupyter notebook的做法略有不同:

  • 使用jupyter notebook时,ipynb文件等存储在服务器 C 上;
  • 使用jupyter内核时,ipynb文件等仍然存储在本地主机 A,但是运行时会通过服务器 C 的 jupyter 服务进行计算。

补充

容器中启动jupyter服务

如果jupyter notebook服务是在docker容器中启动的,那么我们还需要额外考虑两个因素:

  • 权限问题:处于安全原因默认禁止root用户启用jupyter服务,但是在容器中通常直接使用root用户,因此需要在启动时加上--allow-root选项;
  • 网络问题:需要考虑容器和宿主服务器之间的网络关系,即使我们使用-p 8888:8888让宿主机的8888端口映射到容器的8888端口,但是对于容器来说这仍然不是来自localhost的请求,解决办法包括:
    • 可以在jupyter服务启动时加上选项--ip=0.0.0.0,允许所有IP地址访问,但是这就和直接部署的效果一样了,不够安全;
    • 可以在容器启动时使用--net=host选项,直接使用宿主机的网络,此时端口映射也没有意义了,-p选项会被忽略;(docker处理网络时的默认做法是桥接模式)

还需要特别注意的是,默认情况下的 UFW 防火墙对 docker 容器无效,因为 UFW 只是对 iptables 操作的简化封装,而 docker 实际上会绕过了 UFW 直接修改 iptables,如果有特殊的网络配置需求,可能需要进行一定的处理,阻止其修改 iptables。

配置文件

jupyter notebook 服务并不会自动生成配置文件,可以用如下命令显示生成配置文件

1
jupyter notebook --generate-config

生成的配置文件为 ~/.jupyter/jupyter_notebook_config.py,其中包含了所有的默认选项,可以根据需要进行修改。 前面提到的很多命令行选项在配置文件中都有对应的项,并且显然,命令行参数的优先级比配置文件更高。

除此之外,jupyter 还提供了 ~/.jupyter/jupyter_notebook_config.json 这个配置文件,json版本的配置文件相比于py版本的有如下区别:

  • json版本的配置文件只是静态配置,而py版本的配置文件本质上就是个py脚本,可以有更丰富的行为;
  • 两者同时存在时,json版本的配置优先级更高。

token和密码

Jupyter 支持使用 token 或密码进行访问控制,可以单独或同时启用,甚至关闭这两者(不推荐)。

在启动 Jupyter Notebook 时:

  • 如果同时设置了 token 和密码:优先要求 token,登录后可以设置密码。
  • 如果只有 token:启动时会生成一个随机 token,必须带着 token 访问。
  • 如果只有密码:直接通过密码登录,无需 token。
  • 如果两者都禁用:不需要任何验证,但完全暴露在网络上,极不安全。

Jupyter 默认开启 token 认证,在启动信息中会提示如下片段

1
2
3
4
5
To access the server, open this file in a browser:
file:///C:/Users/xxxx/AppData/Roaming/jupyter/runtime/jpserver-30804-open.html
Or copy and paste one of these URLs:
http://localhost:8888/tree?token=51206758218ed512e4c943e4f64618875b3f06b82440810b
http://127.0.0.1:8888/tree?token=51206758218ed512e4c943e4f64618875b3f06b82440810b

在浏览器中访问时,可能需要手动输入token,但是大部分情况下都不需要。

在配置文件中修改如下项可以手动禁用 token

1
c.ServerApp.token = ''

关于密码的生成有很多种方式,一种最常见的方式是在浏览器中远程访问时,第一次需要提供token,此时可以根据页面提示设置密码。

除此之外,还可以直接将密码(的哈希值)写入到配置文件中,例如在 Python 中执行如下命令

1
2
from notebook.auth import passwd
passwd()

输入密码,它会返回密码的 hash 值,把它填写到配置文件的 password 项,这可以避免在配置文件中明文存储密码。

1
c.ServerApp.password = xxxxx

在较新版本的 jupyter 中,可以有更简单的做法

1
jupyter notebook password

根据提示输入密码即可,jupyter 会自动将密码的 hash 值保存到配置文件。

到这里我们也注意到了,jupyter notebook 和 jupyter lab 只是面向单用户的服务,如果需要多用户服务,可以选择 Jupyterhub。