☰
JupyterHub 技术架构深度解析:Hub、Proxy 与单用户 Notebook 服务器的协作原理
2026/9/26 10:15:15 网站建设 项目流程
  • 后端
  • 微服务

【免费下载链接】jupyterhub

Multi-user server for Jupyter notebooks

项目地址:https://gitcode.com/gh_mirrors/ju/jupyterhub
点击查看免费下载

JupyterHub 是一个为多人团体提供单用户 Jupyter Notebook 服务器的多用户系统。本文以官方文档的 Technical Overview 为核心,结合仓库源码,系统讲解 JupyterHub 的三大核心子系统(Hub、Proxy、Single-User Notebook Server)、它们之间的请求路由机制、从用户访问到登录完成的完整时序,以及默认部署形态与两大扩展点(Authenticator、Spawner)。读完本文,你将理解 JupyterHub 的整体运行原理,掌握其默认端口、持久化文件、Cookie 加密与认证配置的底层细节,并知道如何通过自定义认证器与启动器把 JupyterHub 接入你的组织环境。

JupyterHub 的三大核心子系统

JupyterHub 本质上是一组进程的集合,这些进程协同工作,为群体中的每个人提供一个专属的单用户 Jupyter Notebook 服务器。由jupyterhub命令行程序启动的三个子系统分别是:

子系统技术栈职责
HubPython / Tornado管理用户账户与认证,并通过 Spawner 协调各个单用户 Notebook 服务器的启动与生命周期
Proxy动态代理(默认是 configurable-http-proxy,基于 node-http-proxy)JupyterHub 面向公众的部分,负责把 HTTP 请求路由到 Hub 与各单用户 Notebook 服务器
Single-User Notebook ServerPython / Tornado用户登录时为每个系统用户启动一个专属的 Jupyter Notebook 服务器,启动它的对象就是 Spawner

其中Hub是大脑:它负责账户数据库、认证流程、Spawner 编排与 API;Proxy是门面:它是唯一对外暴露的进程,所有外部流量都从这里进入;Single-User Notebook Server则是每个用户真正执行代码的工作环境。三者之间通过明确的 URL 前缀与内部 API 互相通信。

子系统如何交互:请求路由的幕后机制

用户通过浏览器访问 JupyterHub 所在服务器的 IP 或域名。其基本运作原则可以归纳为四点:

  1. Hub 启动 Proxy(在默认配置下,Proxy 是 Hub 的子进程);
  2. Proxy 默认把所有请求转发给 Hub;
  3. Hub 处理登录,并按需 spawn 单用户 Notebook 服务器;
  4. Hub 配置 Proxy,将 URL 前缀转发到对应的单用户服务器。

关键的网络拓扑是:Proxy 是唯一监听公共接口的进程。Hub 位于 Proxy 之后的/hub路径下;单用户服务器位于/user/[username]路径下。也就是说,外部流量永远先到达 Proxy,再由 Proxy 按前缀路由分发。

这一点在源码中有直接印证。jupyterhub/app.py 中hub_prefix默认值由base_url拼接得到:

hub_prefix = URLPrefix( '/hub/', help="The prefix for the hub server. Always /base_url/hub/" ) @default('hub_prefix') def _hub_prefix_default(self): return url_path_join(self.base_url, '/hub/')

而 Proxy 的路由注册逻辑集中在 jupyterhub/proxy.py 中,例如add_user(user, server_name)、add_route(routespec, target, data)等方法负责把/user/[username]/*这样的前缀动态绑定到实际运行的单用户服务器地址上。同时hub_routespec(默认是应用的base_url,而非/hub/)保证当用户的服务器未运行时,Hub 仍然能收到/user/:name的请求并做出响应(见 jupyterhub/app.py)。

从访问 JupyterHub 到用户登录的完整流程

当用户访问 JupyterHub 时,依次发生以下事件:

  1. 登录数据被交给Authenticator实例进行校验;
  2. 登录信息有效时,Authenticator 返回用户名;
  3. 系统为已登录用户spawn一个单用户 Notebook 服务器实例(由 Spawner 负责);
  4. 单用户服务器启动后,通知 Proxy 把对/user/[username]/*的请求转发到该服务器;
  5. 在/hub/路径下设置一个Cookie,内含加密令牌(在 0.8 版本之前,还会额外设置/user/[username]路径的 Cookie);
  6. 浏览器被重定向到/user/[username],随后的请求由单用户 Notebook 服务器处理。

单用户服务器如何通过 OAuth 与 Hub 确认身份

那么单用户服务器是如何识别请求者的身份、并与 Hub 完成认证的呢?标准流程如下:

  • 单用户服务器在收到请求时先检查 Cookie;
  • 若没有 Cookie,则重定向到 Hub,通过OAuth进行身份验证;
  • 在 Hub 完成验证后,浏览器被重定向回单用户服务器;
  • 令牌被验证并存入 Cookie;
  • 若始终无法识别用户身份,浏览器最终被重定向回/hub/login。

这段 OAuth 交互在 jupyterhub/singleuser/extension.py 中有完整实现:单用户服务器通过hub_auth(HubAuth,见 jupyterhub/services/auth.py)与 Hub 通信,注册/oauth_callback回调处理器,并重写登录重定向逻辑以避免 403 或重定向死循环。相关测试见 jupyterhub/tests/test_singleuser.py(如test_singleuser_auth、test_token_url_cookie)。

登录认证本身则由 Authenticator 控制访问权限。默认的 PAM Authenticator 使用 JupyterHub 运行所在服务器上的系统用户账户,这意味着你需要为团队中的每个用户创建系统账户。而更换为其他 Authenticator 后,用户就可以用 GitHub 账户、或组织已有的任意单点登录(SSO)系统登录。

默认行为:开箱即用的部署形态

监听地址与端口

默认情况下,Proxy监听所有公共接口的8000 端口,因此你可以通过以下任一方式访问 JupyterHub:

  • http://localhost:8000
  • 或任何指向该系统的公网 IP / 域名

这一默认值定义在 jupyterhub/app.py 的port配置项(默认8000)与bind_url(默认"http://:8000",见 jupyterhub/app.py)。而 Hub 与各单用户服务器在默认配置下只在 localhost 上互相通信,不直接暴露到公网。

启动时写入磁盘的两个文件

默认启动 JupyterHub 时,会在当前工作目录写入两个文件:

  • jupyterhub.sqlite:保存 Hub 全部状态的 SQLite 数据库。该文件让 Hub 能够记住哪些用户正在运行、运行在哪里,以及其他信息,从而支持单独重启 JupyterHub 的各个部分。需要特别注意的是,除 Hub 用户名外,该数据库不包含任何敏感信息(Hub 在存储前会对各类令牌做哈希处理)。
  • jupyterhub_cookie_secret:用于加密 Cookie 的密钥文件。该文件必须持久存在,否则 Hub 重启会使得所有 Cookie 失效;反过来,删除此文件并重启服务器即可使全部登录 Cookie 作废。

这两个文件的路径都可以通过配置项修改。在 jupyterhub/app.py 中,db_url默认值为sqlite:///jupyterhub.sqlite,并且如果直接给出纯文件名(不含://),会自动被补全为sqlite:///前缀的 SQLite URL。Cookie 密钥文件的默认名与路径则由cookie_secret_file定义:

cookie_secret_file = Unicode( 'jupyterhub_cookie_secret', help="""File in which to store the cookie secret.""" )

社区推荐的目录布局是:所有配置文件放在标准的 UNIX 系统目录/etc/jupyterhub,所有安全与运行时文件放在/srv/jupyterhub。相应的配置示例:

c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret' c.JupyterHub.db_url = 'sqlite:////srv/jupyterhub/jupyterhub.sqlite'

Cookie 密钥的三种配置方式

Cookie 密钥应为32 字节随机数(源码中COOKIE_SECRET_BYTES即 32,见 jupyterhub/app.py)。以下三种方式均可在 安全设置文档 中找到完整说明:

1. 使用密钥文件(推荐,需要持久化以维持登录状态):

openssl rand -hex 32 > /srv/jupyterhub/jupyterhub_cookie_secret
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/jupyterhub_cookie_secret'

若文件不存在,Hub 启动时会自动生成并写入新密钥(见 jupyterhub/app.py)。该文件不得被group或other读取,否则服务拒绝启动,推荐权限为600。

2. 使用环境变量(避免文件依赖):

export JPY_COOKIE_SECRET=$(openssl rand -hex 32)

该环境变量在 jupyterhub/app.py 中被声明为cookie_secret的默认来源(env='JPY_COOKIE_SECRET')。出于安全考虑,它只应让 Hub 进程可见;若每次启动都动态生成,所有用户每次 Hub 重启都会被登出。

3. 直接写在配置文件中(二进制字符串):

c.JupyterHub.cookie_secret = bytes.fromhex('64 CHAR HEX STRING')

此外,ConfigurableHTTPProxy.api_token用于 Hub 与 Proxy 之间的认证(CONFIGPROXY_AUTH_TOKEN环境变量亦可)。若未设置,Hub 会自行生成随机令牌,这意味着重启 Hub 时必须同时重启 Proxy——在默认配置下 Proxy 是 Hub 的子进程,这一过程会自动完成。详情见 安全设置文档的 Proxy 认证令牌章节。

JupyterHub 认证相关的 Cookie

Hub 与单用户服务器之间通过以下几类 Cookie 协同完成认证(详见 security-basics.md):

Cookie 名称作用路径限制
jupyterhub-hub-loginHub 受保护页面的登录令牌,设置后即视为已登录/hub/
jupyterhub-user-<username>单用户服务器与 Hub 完成 OAuth 后设置,内含 OAuth 访问令牌/user/<username>
jupyterhub-session-id随机字符串,Hub 与单用户服务器唯一共享的 Cookie,用于协调多个 OAuth Cookie 的登出/
jupyterhub-user-<username>-oauth-state短暂存在的 OAuth 状态校验 Cookie,仅存在于 OAuth 处理过程中/user/<username>

重置 Hub 的 Cookie 密钥会同时使jupyterhub-hub-login与jupyterhub-user-<username>失效。

定制 JupyterHub:Authenticator 与 Spawner 两大扩展点

JupyterHub 提供两个基础扩展点,分别对应"谁可以登录"与"如何为用户启动服务器"两个问题:

  • Authenticator(认证器):控制用户如何被认证,参考 Authenticators 文档;
  • Spawner(启动器):控制用户的单用户服务器进程如何被启动,参考 Spawners 文档。

两者都对应一个可定制类,JupyterHub 为每个都内置了基础默认实现。默认 Spawner 会在同一台机器上以用户的系统用户名启动 Notebook 服务器;另一种常见做法是用 Docker 等方案把每个服务器启动在独立容器中。

编写自定义认证器与启动器

启用自定义认证与启动的方式是:继承Authenticator或Spawner,并重写相关方法。

以认证器为例,核心可重写方法包括authenticate()(校验登录数据并返回用户名)与check_allowed()(判断用户是否被允许登录),完整的接口方法可以在 jupyterhub/auth.py 中看到,例如PAMAuthenticator的authenticate、system_user_exists、add_system_user等实现,以及与allowed_users、allow_all、check_allow_config相关的允许/阻止逻辑。

以启动器为例,需要重写的核心方法是start()(启动服务器并返回其实际地址)与poll()(检查服务器是否存活)、stop()(停止服务器),这些定义在 jupyterhub/spawner.py 中。仓库还提供了完整的测试参考,例如 jupyterhub/tests/test_spawner.py 中的test_spawner、test_spawner_poll、test_single_user_spawner等用例,以及 jupyterhub/tests/test_auth.py 中对 PAM 认证行为的验证。

配置方式

JupyterHub 使用 Python 配置文件jupyterhub_config.py(可通过jupyterhub --generate-config生成),所有定制都是对c.Authenticator.*与c.Spawner.*命名空间下的 traitlet 属性赋值。例如:

c.JupyterHub.authenticator_class = 'myauth.MyAuthenticator' # 或直接指定类对象 c.JupyterHub.spawner_class = 'mydocker.MyDockerSpawner' # Authenticator 示例配置 c.MyAuthenticator.allowed_users = {'alice', 'bob'} # Spawner 示例配置 c.MySpawner.default_url = '/lab'

关键配置与源码对照表

为了便于排查与二次开发,下表汇总了本文涉及的默认行为与对应的源码位置:

默认行为 / 配置项默认值源码位置
Proxy 监听端口port8000jupyterhub/app.py
对外绑定地址bind_urlhttp://:8000jupyterhub/app.py
Hub 前缀hub_prefixbase_url + /hub/jupyterhub/app.py
Hub 路由规格hub_routespec应用base_url(未设置 subdomain 时)jupyterhub/app.py
数据库db_urlsqlite:///jupyterhub.sqlitejupyterhub/app.py
Cookie 密钥文件cookie_secret_filejupyterhub_cookie_secretjupyterhub/app.py
Cookie 密钥(可从环境变量读取)32 字节随机数jupyterhub/app.py
动态路由注册add_user/add_route等jupyterhub/proxy.py

总结

JupyterHub 的架构核心在于"一个公共代理 + 一个中枢 Hub + 若干按需启动的单用户服务器"这一分工:Proxy 对外统一收口,Hub 负责认证与编排,单用户服务器为用户提供隔离的专属环境。理解这套子系统协作机制与默认行为,是正确部署、排障以及自定义扩展(认证器、启动器)的前提。若需进一步深入,建议继续阅读 Spawners 参考文档、Authenticators 参考文档、安全设置教程,以及 Proxy 配置指南 与 独立部署 Proxy 指南。

  • 后端
  • 微服务

【免费下载链接】jupyterhub

Multi-user server for Jupyter notebooks

项目地址:https://gitcode.com/gh_mirrors/ju/jupyterhub
点击查看免费下载
上一篇:ThingsBoard实战上手:Docker十分钟部署,从MQTT设备接入到实时告警仪表盘
下一篇:PSReadLine终极故障排除指南:10个常见问题快速解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询