Jupyter notebook 本地和远程部署
整理一下 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
45server {
# 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_origin
和allow_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处理网络时的默认做法是桥接模式)
- 可以在jupyter服务启动时加上选项
还需要特别注意的是,默认情况下的 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
5To 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
2from notebook.auth import passwd
passwd()
输入密码,它会返回密码的 hash 值,把它填写到配置文件的 password
项,这可以避免在配置文件中明文存储密码。 1
c.ServerApp.password = xxxxx
在较新版本的 jupyter 中,可以有更简单的做法 1
jupyter notebook password
根据提示输入密码即可,jupyter 会自动将密码的 hash 值保存到配置文件。
到这里我们也注意到了,jupyter notebook 和 jupyter lab 只是面向单用户的服务,如果需要多用户服务,可以选择 Jupyterhub。