端口与打开按钮
端口是如何发布的
容器内部的端口在主机把它发布出去之前,是无法从互联网访问的。启动时,每个已声明的容器端口都会被映射到主机上的一个空闲端口,取自这台主机可用的端口范围。你连接的是主机端口,它几乎从不和容器端口同号——原因与你的 SSH 端口不是 22 一样。
容器会被告知它的每个端口落在了哪个主机端口上,方式是为每个已发布的 TCP 端口设置一个环境变量:
# inside the instance
env | grep SUPERHEAT_TCP_PORT_
# SUPERHEAT_TCP_PORT_22=40123
# SUPERHEAT_TCP_PORT_8080=41007
SUPERHEAT_TCP_PORT_<n> 保存容器端口 <n> 对应的已发布主机端口。这只是信息性的:它存在是为了让实例上的进程能正确地对外公布自己的公网地址。UDP 端口没有这样的变量。
各启动模式会发布哪些端口
端口来自模板,再加上启动模式本身需要的那些:
| 启动模式 | 自动发布 | 此外 |
|---|---|---|
ssh | 容器端口 22 | 加上模板上声明的每一个端口 |
jupyter | 容器端口 22 和 8080 | 加上模板上声明的每一个端口 |
args | 不自动发布任何端口 | 只有模板上声明的端口 |
vm | 容器端口 22 | 加上模板上声明的每一个端口 |
声明的端口可以带一个标签。标签为 open、open_button 或 open-button 时,该端口会被标记为打开按钮的目标。其他任何标签都只是描述性的,端口按普通应用端口处理。
打开按钮
当模板声明了一个打开按钮端口时,实例的连接标签页会在 SSH 命令上方显示一个打开按钮,指向主机上的那个端口。它是为你的镜像所提供的 Web 界面准备的快捷方式——推理前端、仪表盘、查看器之类——这样你就不必去查已发布的端口号。
如果主机位于 NAT 之后、外部无法直接访问,打开按钮指向的会是 https://<工作负载 ID>-<容器端口>.relay.superheat.cloud/ 形式的中继地址,而不是主机上的端口;中继只在 Superheat CN 启用 superheat.cloud 域名之后开启(参见打开 JupyterLab)。
端口号也会以 OPEN_BUTTON_PORT 的形式传给容器,以备进程需要知道控制台对外公布的是它的哪个端口。
每种启动模式都可以有打开按钮,args 也不例外。args 模式的模板运行的是镜像自己的入口点,但它仍然会收到 OPEN_BUTTON_PORT,也仍然会有打开按钮 —— 否则提供 Web 界面的镜像就永远无法从控制台打开,而这正是 args 模式最主要的用途。
jupyter 模式的实例除了这个按钮之外,还会有自己的打开 JupyterLab 按钮。参见打开 JupyterLab。
实例访问令牌
如果模板需要,每个实例在部署时都会得到一个自己的密钥:访问令牌。它和 JupyterLab 的令牌是同一个值。实例的连接标签页在访问令牌下显示它,默认遮蔽,旁边有显示访问令牌和复制访问令牌按钮;API 也会把它作为实例的 access_token 返回给你的组织。实例销毁后它就作废了。
在 Superheat CN 上可以启动的精选模板里,凡是在已发布端口上提供网页界面或 API 的,都要求在这个端口上出示令牌:
| 模板 | 端口 | 怎样出示令牌 |
|---|---|---|
| ComfyUI、Stable Diffusion WebUI (A1111)、Fooocus、Kohya_ss、Faster-Whisper(它的语音识别 API) | 应用的端口 | 应用前面有一个访问代理。打开按钮会替你登录;脚本发送 Authorization: Bearer <令牌> |
| Text Embeddings Inference | 80 | 服务把它当作自己的 API 密钥检查:每个请求都带 Authorization: Bearer <令牌> |
| Open WebUI | 8080 | 用 admin@superheat.local 登录,密码就是令牌。注册已关闭,别人无法再创建账号 |
令牌只覆盖模板开始要求它之后部署的实例。更早部署的实例,端口保持原样——任何找到它们的人都能访问——直到你销毁它;重新部署才能拿到令牌。
Triton Inference Server 同样要求令牌,但它的镜像来自 NVIDIA NGC,在 Superheat CN 上显示为即将推出,参见Superheat CN 上的镜像。Ollama、InvokeAI、LLaMA-Factory 和 vLLM 目前被标记为即将推出:它们的服务无法对每个路由都要求令牌——vLLM 的 API 密钥会留下一个推理路由不设防——所以在能做到之前先下架。已经在运行的实例不受影响。
# 一个要求令牌的 API 端口——这里是 Text Embeddings Inference
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"inputs": "你好"}' \
http://203.0.113.14:41007/embed
打开按钮怎样替你登录
对于访问代理后面的模板,打开链接会以 ?token= 带上令牌。第一次打开时,代理把令牌换成这个浏览器里的一个 cookie,再把你转到不带令牌的同一个地址,所以令牌会立刻离开地址栏。之后在同一个浏览器里访问就不再需要令牌。既没带令牌也没带这个 cookie 的请求会得到 401。
这个 cookie 只对应用自己的页面、以及直接打开应用时有效。由其他来源的页面让你的浏览器发出的请求——包括同一台机器上的其他端口——会被拒绝并返回 403,所以主机上别的租用者的应用无法借你的 cookie 操作你的应用。
直连——机器的 IP 地址加上一个已发布的端口——走的是明文 HTTP。打开链接里的令牌、这个 cookie 以及这条连接上的其他一切都不加密传输,所以要像对待密码一样对待打开链接:不要把它贴到你不会贴令牌的地方。
浏览器会把 cookie 发给同一地址上的每一个端口,而不只是设置它的那一个。直连时,打开同一台机器上别的租用者运行的应用——也就是这台机器地址上其他端口的任何链接——就等于把你的 cookie 交给了对方,连同你的应用一起,只要实例还在就一直有效。在另一个浏览器或无痕窗口里打开这个地址上别人的链接,或者改用 SSH 转发访问你自己的应用。
通过 SSH 转发端口时不经过代理,因为 SSH 已经认证过你了。在代理后面,应用监听的是 127.0.0.1,所以要转发到这个地址:
ssh -N -L 8188:127.0.0.1:8188 -p 40123 root@203.0.113.14
你自己的模板
你自己的模板,只有在端口声明了要求方式时才会要求令牌——也就是模板编辑器里这个端口的访问控制设置(访问代理或应用校验令牌),或者通过 API 设置的 auth。没有这个字段的端口照旧发布:任何找到机器地址和端口的人都能访问。在它前面放一个会认证的东西,或者改用 SSH 转发访问。参见端口。
访问你自己启动的服务
端口是在实例启动时发布的。在运行中的实例里起一个服务器不会发布任何东西,所以一个刚跑起来的 python -m http.server 8000 从外部访问不到,无论你笔记本上的防火墙怎么设置都一样。
你有两个选择。
在模板上声明该端口,然后重新部署。 对于任何你会跑不止一次的东西,这是长期有效的做法。在模板的端口里加上这个端口,给它一个标签,然后重新部署。参见端口。
通过 SSH 转发。 这不需要重新部署,对于事先没有规划的端口可以立即生效:
ssh -N -L 8000:localhost:8000 -p 40123 root@203.0.113.14
让它一直运行,然后在浏览器里打开 http://localhost:8000。流量走的是你已经建立的 SSH 连接,因此不会有新的东西暴露到互联网上。
无论选哪条路,如果你希望已发布的端口可用,就把服务绑定到 0.0.0.0 而不是 127.0.0.1。只监听回环地址的进程可以通过 SSH 转发访问到,但通过已发布的主机端口访问不到。唯一的例外是访问代理后面的应用:那里应用监听回环地址,由代理占着已发布的端口。
协议
声明端口时要指定协议:tcp、udp、http 或 https。前两个才是主机上实际预留的类型。后两个只是关于该端口提供什么服务的提示,它们本身跑在 TCP 之上。
相关内容
- 端口 —— 在模板上声明端口
- 通过 SSH 连接
- 打开 JupyterLab