- 后端
- 微服务
【免费下载链接】jupyterhub
Multi-user server for Jupyter notebooks
JupyterHub 是一个为多人团体提供单用户 Jupyter Notebook 服务器的多用户系统。本文以官方文档的 Technical Overview 为核心,结合仓库源码,系统讲解 JupyterHub 的三大核心子系统(Hub、Proxy、Single-User Notebook Server)、它们之间的请求路由机制、从用户访问到登录完成的完整时序,以及默认部署形态与两大扩展点(Authenticator、Spawner)。读完本文,你将理解 JupyterHub 的整体运行原理,掌握其默认端口、持久化文件、Cookie 加密与认证配置的底层细节,并知道如何通过自定义认证器与启动器把 JupyterHub 接入你的组织环境。
JupyterHub 的三大核心子系统
JupyterHub 本质上是一组进程的集合,这些进程协同工作,为群体中的每个人提供一个专属的单用户 Jupyter Notebook 服务器。由jupyterhub命令行程序启动的三个子系统分别是:
| 子系统 | 技术栈 | 职责 |
|---|---|---|
| Hub | Python / Tornado | 管理用户账户与认证,并通过 Spawner 协调各个单用户 Notebook 服务器的启动与生命周期 |
| Proxy | 动态代理(默认是 configurable-http-proxy,基于 node-http-proxy) | JupyterHub 面向公众的部分,负责把 HTTP 请求路由到 Hub 与各单用户 Notebook 服务器 |
| Single-User Notebook Server | Python / Tornado | 用户登录时为每个系统用户启动一个专属的 Jupyter Notebook 服务器,启动它的对象就是 Spawner |
其中Hub是大脑:它负责账户数据库、认证流程、Spawner 编排与 API;Proxy是门面:它是唯一对外暴露的进程,所有外部流量都从这里进入;Single-User Notebook Server则是每个用户真正执行代码的工作环境。三者之间通过明确的 URL 前缀与内部 API 互相通信。
子系统如何交互:请求路由的幕后机制
用户通过浏览器访问 JupyterHub 所在服务器的 IP 或域名。其基本运作原则可以归纳为四点:
- Hub 启动 Proxy(在默认配置下,Proxy 是 Hub 的子进程);
- Proxy 默认把所有请求转发给 Hub;
- Hub 处理登录,并按需 spawn 单用户 Notebook 服务器;
- 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 时,依次发生以下事件:
- 登录数据被交给Authenticator实例进行校验;
- 登录信息有效时,Authenticator 返回用户名;
- 系统为已登录用户spawn一个单用户 Notebook 服务器实例(由 Spawner 负责);
- 单用户服务器启动后,通知 Proxy 把对
/user/[username]/*的请求转发到该服务器; - 在
/hub/路径下设置一个Cookie,内含加密令牌(在 0.8 版本之前,还会额外设置/user/[username]路径的 Cookie); - 浏览器被重定向到
/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_secretc.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-login | Hub 受保护页面的登录令牌,设置后即视为已登录 | /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 监听端口port | 8000 | jupyterhub/app.py |
对外绑定地址bind_url | http://:8000 | jupyterhub/app.py |
Hub 前缀hub_prefix | base_url + /hub/ | jupyterhub/app.py |
Hub 路由规格hub_routespec | 应用base_url(未设置 subdomain 时) | jupyterhub/app.py |
数据库db_url | sqlite:///jupyterhub.sqlite | jupyterhub/app.py |
Cookie 密钥文件cookie_secret_file | jupyterhub_cookie_secret | jupyterhub/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
相关推荐
TogetherJS架构深度剖析:客户端与Hub服务器的完美协作
TogetherJS是一个令人惊喜的实时协作服务,它通过客户端与Hub服务器的高效协作,让网站轻松实现多用户实时交互。TogetherJS的核心架构由两个主要组
即时通讯前端后端JupyterHub 架构概念全解:Authenticator、Spawner、Proxy、Services 与单用户服务器/内核的分层解析
JupyterHub 架构概念全解:Authenticator、Spawner、Proxy、Services 与单用户服务器/内核的分层解析 JupyterHu
后端微服务JupyterHub 单用户服务器:jupyterhub-singleuser 命令与 OAuth 认证实现原理
JupyterHub 单用户服务器:jupyterhub singleuser 命令与 OAuth 认证实现原理 本文基于 JupyterHub 仓库中的官方说
后端微服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考