尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JupyterHub Service 类 API 详解:托管服务、外部服务与代理路由的完整指南

JupyterHub Service 类 API 详解:托管服务、外部服务与代理路由的完整指南 后端微服务【免费下载链接】jupyterhubMulti-user server for Jupyter notebooks项目地址https://gitcode.com/gh_mirrors/ju/jupyterhub点击查看免费下载导读本文以 JupyterHub 官方 API 参考文档docs/source/reference/api/service.md为骨架深入解析jupyterhub.services.service.Service类的全部核心成员name、admin、url、api_token、managed、kind、command、cwd、environment、user、oauth_client_id、server、prefix、proxy_spec等并对照源码jupyterhub/services/service.py说明其实现原理。读完本文你将能独立配置一个 Hub-Managed Service由 Hub 托管的子进程或 Externally-Managed Service由 Docker/systemd 等外部工具管理的进程理解服务如何被注册进代理、如何拿到 Hub 颁发的凭据与路由信息以及如何通过 REST API 在运行时动态增删服务。Service 是什么两类服务形态在 JupyterHub 中一个Service是能够与 Hub 的 REST API 交互的独立进程或外部系统。典型用途包括定时清理空闲的单用户 notebook 服务器cull-idle一个以 Hub 为 OAuth 提供方的 Web 应用用于登录与授权定期执行某类 API 动作的脚本收集用户服务器活动数据的自动化任务。区分服务的两个关键特征是是否由 JupyterHub 托管managed是否运行 Web 服务器并需要加入代理路由表。由此划分出两种形态参考 docs/source/reference/services.mdHub-Managed Service由 Hub 以本地子进程方式启动Hub 负责进程生命周期进程意外退出时自动重启只能运行在 Hub 所在主机上。需要跑在 Docker 等容器环境中的服务应注册为外部服务。Externally-Managed Service由 Docker、systemd 等外部工具管理不在 Hub 的进程树内Hub 通过api_token识别其身份。Service类源码 docstring 中给出了两种形态的典型配置字典jupyterhub/services/service.py#L23-L38# 外部管理的、运行在固定 URL 上的服务 { name: my-service, url: https://host:8888, admin: True, api_token: super-secret, } # Hub 管理的、无 URL 的服务如 cull-idle { name: cull-idle, command: [python, /path/to/cull-idle], admin: True, }Service 类的核心 trait配置项Service继承自LoggingConfigurablejupyterhub/services/service.py#L160其输入型 traitinputTrue既可通过c.JupyterHub.services配置也可通过 REST API 提交。所有inputTrue的 trait 默认会持久化到数据库除非同时标记了in_dbFalse。下面逐一说明各成员的含义、默认值与实现细节。name服务名称服务的唯一标识也是代理路由与 OAuth client id 的命名基础。若服务有 HTTP 端点默认路径前缀为/services/name/。admin是否拥有 Hub API 的管理权限Bool类型默认False。源码注释指出admin 服务的 token 拥有对 Hub API 的管理访问权非 admin 服务的 token 仅有非管理访问权除了将认证委托给 Hub 外能做的事不多。从 jupyterhub/apihandlers/services.py#L39-L56 可以看到运行时创建服务时若admin: TrueHub 会校验请求者是否拥有默认 admin 角色的全部 scope。url服务所在 URLUnicode默认None。仅当服务确实运行 HTTP(s) 端点时填写。指定url后服务会被注册到代理的/services/:name路径下对托管服务而言该值还会以JUPYTERHUB_SERVICE_URL环境变量传给子进程。若端口填 0则由 Hub 自动选择端口见模块 docstringjupyterhub/services/service.py#L13-L16。api_tokenAPI 令牌Unicode默认None标记为in_dbFalse。对托管服务若未指定Hub 会在启动时自动生成并通过$JUPYTERHUB_API_TOKEN注入环境对 OAuth 服务此 token 同时充当 OAuth client secret。外部服务必须自行指定 token且每个服务需要唯一 token因为 Hub 靠 token 识别请求发起方。oauth_client_idOAuth 客户端 ID默认值为service-namejupyterhub/services/service.py#L325-L327通常无需修改。校验器强制要求其必须以service-开头否则抛ValueErrorjupyterhub/services/service.py#L329-L336。该字段标记in_dbFalse不入库。oauth_redirect_uriOAuth 重定向 URI默认值为prefix/oauth_callback即/services/name/oauth_callback。仅当重定向地址不同于默认值、或服务不通过代理暴露未设置url时才需要显式设置。例如外部 OAuth 示例examples/external-oauth/jupyterhub_config.py将重定向 URI 显式指到外部端口c.JupyterHub.services [ { name: external-oauth, oauth_client_id: service-oauth-client-test, api_token: api_token, oauth_redirect_uri: http://127.0.0.1:5555/oauth_callback, } ]oauth_client_allowed_scopesOAuth 客户端允许的 scope从 3.0 起取代已废弃的oauth_roles。它定义该服务签发的 OAuth token即用户通过浏览器完成认证后保存在 cookie 中的 token的最大与默认 scope也就是服务能在用户授权下代表用户执行哪些操作。默认空列表意味着仅能识别用户身份不能代用户执行任何动作。这在用户委托模式下至关重要服务自身可以不拥有任何 scope仅凭用户 OAuth 授权代为执行操作。oauth_no_confirm跳过 OAuth 确认页Bool默认False1.1 版本加入。设为True后用户访问该服务时不再看到授权确认页面。适用于被视为 Hub 一部分、无需额外提示的管理员服务。运行时通过 API 创建服务时若设置该项Hub 会把该服务的oauth_client_id加入oauth_no_confirm_listjupyterhub/apihandlers/services.py#L106-L110。display是否在 Hub 首页展示链接Bool默认True。置为False可让服务不出现在用户首页Services下拉菜单中仅在同时指定url时生效。参考示例 examples/service-whoami/jupyterhub_config.py 中whoami-api服务即设置了display: False。timeout服务就绪等待超时Integer默认 30秒6.0 版本加入。用于等待服务变为可用。info附加信息字典Dict可通过配置提供关于服务的杂项信息。managed 与 kind托管状态从何而来managed是一个只读属性property其实现异常简洁——只要配置了command服务即为托管服务jupyterhub/services/service.py#L271-L274property def managed(self): Am I managed by the Hub? return bool(self.command)kind属性据此返回字符串managed或externaljupyterhub/services/service.py#L276-L283。因此是否托管完全由command字段决定指定command→ Hub 以子进程方式托管不指定 → 假定由外部进程管理。托管服务的启动参数command、cwd、environment、user托管服务Hub-Managed比外部服务多了四个启动相关的配置commandCommand类型trait 校验后的命令列表minlen0JupyterHub 用它来 spawn 服务进程。只有希望服务作为子进程运行时才使用。例如 cull-idle 的经典配置docs/source/reference/services.md#L96-L114import sys c.JupyterHub.load_roles [ { name: idle-culler, scopes: [ read:users:activity, # 读取用户 last_activity servers, # 启动和停止服务器 # admin:users # 若还要清理空闲用户本身则取消注释 ] } ] c.JupyterHub.services [ { name: idle-culler, command: [sys.executable, -m, jupyterhub_idle_culler, --timeout3600] } ]cwd服务进程的工作目录缺省为 Hub 目录。environment额外传给服务进程的环境变量字典仅托管时生效。user要切换到的系统用户名缺省与 Hub 同用户运行若指定则需要 Hub 以 root 身份运行。托管的底层实现_ServiceSpawnerHub 并不直接Popen命令而是复用了 Spawner 的概念——源码注释坦言这里本不该用 Spawner但可复用的概念太多jupyterhub/services/service.py#L96-L97。Service.start()会构造一个_ServiceSpawnerLocalProcessSpawner的子类去除了 notebook 特定逻辑关键点包括start()中通过Popen(self.cmd, envenv, preexec_fn..., start_new_sessionTrue, cwdself.cwd or None)拉起子进程并在 PermissionError 时给出更友好的错误提示jupyterhub/services/service.py#L129-L157进程启动后add_poll_callback(self._proc_stopped)注册退出回调进程意外退出时_proc_stopped会记录错误并通过asyncio.ensure_future(self.start())自动重启jupyterhub/services/service.py#L484-L490若配置了user系统用户名make_preexec_fn会在 exec 前调用set_user_setuid(name, chdirFalse)切换用户jupyterhub/services/service.py#L117-L121托管服务默认的 OAuth 访问 scope 为access:services与access:services!servicenamejupyterhub/services/service.py#L110-L115。Service.stop()则停止轮询、删除 ORM 中的 server 记录并调用spawner.stop()jupyterhub/services/service.py#L492-L502。server、prefix、proxy_specURL 与代理路由这三个只读属性决定服务在代理中的位置server返回Server.from_orm(self.orm.server)若存在即服务对应的服务器记录。prefix服务的路径前缀实现为url_path_join(self.base_url, services, self.name /)即/services/name/jupyterhub/services/service.py#L381-L383。proxy_spec代理路由规则返回domain server.base_url配置了子域时否则返回server.base_urljupyterhub/services/service.py#L393-L400。模块 docstring 明确公开路由始终是/services/service-nameurl在配置中指定如果端口为 0则由 Hub 选择端口。因此一个带 URL 的服务最终可通过https://myhub.horse/services/my-service/访问。你的服务代码在路由时必须考虑JUPYTERHUB_SERVICE_PREFIX注意它自带结尾斜杠否则会出现 404 或路由错乱——例如/foo端点应挂载为JUPYTERHUB_SERVICE_PREFIX foo。href属性则为 UI 链接提供了便捷拼接//domain prefix或prefix。服务启动时注入的环境变量Hub 启动托管服务时会注入一整套JUPYTERHUB_*环境变量源码见 jupyterhub/services/service.py#L437-L440完整清单见 docs/source/reference/services.md#L125-L149JUPYTERHUB_SERVICE_NAME 服务名称 JUPYTERHUB_API_TOKEN API 令牌托管服务自动生成 JUPYTERHUB_API_URL Hub API 地址默认 http://127.0.0.1:8080/hub/api JUPYTERHUB_BASE_URL Hub 的 Base URL JUPYTERHUB_SERVICE_PREFIX 服务路径前缀/services/name/ JUPYTERHUB_SERVICE_URL 服务监听的本机 URL仅 proxied web 服务 JUPYTERHUB_OAUTH_ACCESS_SCOPES 3.0 起访问该服务所需的 JSON scope 列表 JUPYTERHUB_OAUTH_CLIENT_ALLOWED_SCOPES 3.0 起OAuth client 可代表用户请求的 scope JUPYTERHUB_PUBLIC_URL 服务的公网 URL配置了子域时可用 JUPYTERHUB_PUBLIC_HUB_URL JupyterHub 整体的公网 URL配置了子域时可用值得注意的实现细节若 Hub 监听在所有网卡ip为、0.0.0.0或::Service.start()会深拷贝 Hub 配置并改用127.0.0.1IPv6 为::1作为connect_ip因为托管服务永远是本地子进程jupyterhub/services/service.py#L449-L458。另外_ServiceSpawner.start()会从环境中移除JUPYTERHUB_ACTIVITY_URL因为服务没有活动上报地址jupyterhub/services/service.py#L129-L133。服务凭据与权限模型服务通过api_token直接访问 Hub API具体能做什么由该服务的角色分配role assignments决定。例如给一个服务分配列出用户的角色docs/source/reference/services.md#L199-L214c.JupyterHub.services [ { name: user-lister, command: [python3, /path/to/user-lister], } ] c.JupyterHub.load_roles [ { name: list-users, scopes: [list:users, read:users], services: [user-lister] } ]当服务配置了url或显式的oauth_client_id/oauth_redirect_uri时它还能作为 OAuth 客户端运行。用户访问这类服务并完成认证后会得到一枚 OAuth token该 token归认证用户所有与该服务的 OAuth client 绑定受该服务oauth_client_allowed_scopes配置约束使服务能代表用户行事。服务发起请求时有两套凭据可选自己的api_token以服务身份行事受自身角色约束或用户 OAuth token以用户身份行事。一个典型的grader-dashboard示例展示了如何让服务自身零权限、仅靠用户授权代执行操作docs/source/reference/services.md#L251-L277c.JupyterHub.services [ { name: grader-dashboard, command: [python3, /path/to/grader-dashboard], url: http://127.0.0.1:12345, oauth_client_allowed_scopes: [ list:users, read:users, ] } ] c.JupyterHub.load_roles [ { name: grader, scopes: [ list:users!groupclass-a, read:users!groupclass-a, servers!groupclass-a, access:servers!groupclass-a, access:services, ], groups: [graders] } ]该服务自身无任何角色、无权限但 grader 登录后dashboard 获得一枚可列出、读取 A 班用户信息的 token却不能启动/停止/访问用户服务器这些不在oauth_client_allowed_scopes中也无法在用户未授权时执行任何操作。运行时动态增删服务REST API只有外部托管服务无command可以在运行时通过 REST API 增删docs/source/reference/services.md#L294-L333对应实现见 jupyterhub/apihandlers/services.py。新增服务POST /hub/api/services/:servicename需要admin:servicesscopepayload 与配置文件支持的外部服务属性一致。add_service内部会先校验模型与请求的 scope_check_service_scopes并拒绝在运行时创建托管服务返回 400jupyterhub/apihandlers/services.py#L76-L80。响应201 Created服务及相关对象创建成功Hub 托管服务会同时启动但运行时不支持故实际仅外部服务400 Bad Requestpayload 无效或无法创建409 Conflict同名服务已存在。删除服务DELETE /hub/api/services/:servicename需要admin:servicesscope无 payload。响应200 OK删除成功Hub 托管服务会先停止400 Bad Request无法删除404 Not Found服务不存在405 Not Allowed服务由配置文件创建运行时不可删除。实战一个完整的 Hub-Managed Web 服务参考仓库中的 whoami 示例examples/service-whoami/jupyterhub_config.py同时配置一个纯 API 服务和一个 OAuth Web 服务import sys c get_config() c.JupyterHub.services [ { name: whoami-api, url: http://127.0.0.1:10101, command: [sys.executable, ./whoami.py], display: False, }, { name: whoami-oauth, url: http://127.0.0.1:10102, command: [sys.executable, ./whoami-oauth.py], # 默认 OAuth scope 最小仅请求访问服务与按名识别用户 # 通过 oauth_client_allowed_scopes 可请求更多用户信息 # 或代用户执行操作例如 inherit 表示继承用户全部权限 # oauth_client_allowed_scopes: [inherit], }, ] c.JupyterHub.load_roles [ { name: user, # 赋予所有用户访问所有服务的权限 scopes: [access:services, self], } ]对应服务端代码examples/service-whoami/README.md演示了两种认证路径whoami-oauth基于HubOAuthenticated的浏览器 OAuth 流程。启动jupyterhub后访问http://127.0.0.1:8000/services/whoami-oauth登录后返回 JSON 用户模型name、scopes等。返回内容取决于oauth_client_allowed_scopes配置默认只包含识别身份所需的最小信息。whoami-api基于基础HubAuthenticated只支持 token 认证的 API 请求不支持浏览器访问。从/hub/token页面申请 token 后直接请求tokend584cbc5bba2430fb153aadb305029b4 curl -H Authorization: token $token http://127.0.0.1:8000/services/whoami-api/ | jq .公告栏服务示例examples/service-announcement/jupyterhub_config.py则演示了command带参数、以及用角色精确控制谁能访问服务c.JupyterHub.services [ { name: announcement, url: http://127.0.0.1:9999, command: [sys.executable, -m, announcement, --port, 9999], } ] c.JupyterHub.load_roles [ { name: announcers, users: [announcer], scopes: [access:services!serviceannouncement], } ]使用 Hub 认证基础设施Service类负责服务注册与进程管理服务自身的请求认证则由 jupyterhub/services/auth.py 提供分为两级HubAuth最基础的认证适合只接受 token 授权 API 请求的服务。通过JUPYTERHUB_API_TOKEN环境变量或构造参数设置api_token调用user_for_token(token)向 Hub 的/hub/api/user发起请求换取用户模型Hub 响应会被缓存默认cache_max_age 300秒5 分钟可通过cache_max_age调节。HubOAuth支持浏览器 OAuth 认证适用于需要被浏览器直接访问的服务负责登录跳转、PKCE 校验、OAuth 状态与结果 cookie 管理。对于 tornado 服务可直接混入HubAuthenticated/HubOAuthenticatedmixin 并定义initialize注入hub_authclass MyHandler(HubOAuthenticated, web.RequestHandler): def initialize(self, hub_auth): self.hub_auth hub_auth web.authenticated def get(self): ...HubAuth 会自动从JUPYTERHUB_*环境变量加载配置。如果不希望使用参考实现也可以把 JupyterHub 当作标准 OAuth2 提供方用任意 OAuth 2 客户端如 requests Flask自行完成授权拿到 token 后调用GET /hub/api/userAuthorization: token token即可获取用户模型含name、groups、scopes字段仓库中的 FastAPI 示例examples/service-fastapi/jupyterhub_config.py展示了完全不依赖 JupyterHub 代码的第三方接入方式。小结Service类是 JupyterHub 服务体系的配置模型与运行时句柄command字段决定托管与否managed/kindurl与prefix/proxy_spec决定代理路由api_token与角色分配决定 API 权限oauth_client_id/oauth_redirect_uri/oauth_client_allowed_scopes决定 OAuth 交互边界。理解这些成员的默认值与联动关系是写出可靠、可维护的 JupyterHub 服务的第一步——无论是定时任务类的托管服务还是容器化部署的外部服务。赞分享后端微服务【免费下载链接】jupyterhubMulti-user server for Jupyter notebooks项目地址https://gitcode.com/gh_mirrors/ju/jupyterhub点击查看免费下载相关推荐如何免费安装Loop并掌握Mac窗口管理的6个技巧如何免费安装Loop并掌握Mac窗口管理的6个技巧 Loop 是一款免费开源的 macOS 窗口管理工具它把繁琐的拖拽窗口变成一次按键按下触发键、光标指向哪后端微服务JupyterHub 外部服务Service实战用 API Token 与 idle-culler 构建 Hub 自动化任务JupyterHub 外部服务Service实战用 API Token 与 idle culler 构建 Hub 自动化任务 JupyterHub 的 S后端微服务JupyterHub外部服务管理实战以闲置服务器清理为例JupyterHub外部服务管理实战以闲置服务器清理为例 前言 在JupyterHub的实际运维中外部服务 External Services 扮演着重要角后端微服务上一篇UltimateRecyclerView开源治理项目管理下一篇Moonraker核心功能解析为什么它是Klipper必备的Web接口工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表