端口与打开按钮
端口是如何发布的
容器内部的端口在主机把它发布出去之前,是无法从互联网访问的。启动时,每个已声明的容器端口都会被映射到主机上的一个空闲端口,取自这台主机可用的端口范围。你连接的是主机端口,它几乎从不和容器端口同号——原因与你的 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 界面准备的快捷方式——推理前端、仪表盘、查看器之类——这样你就不必去查已发布的端口号。
端口号也会以 OPEN_BUTTON_PORT 的形式传给容器,以备进程需要知道控制台对外公布的是它的哪个端口。
每种启动模式都可以有打开按钮,args 也不例外。args 模式的模板运行的是镜像自己的入口点,但它仍然会收到 OPEN_BUTTON_PORT,也仍然会有打开按钮 —— 否则提供 Web 界面的镜像就永远无法从控制台打开,而这正是 args 模式最主要的用途。
jupyter 模式的实例除了这个按钮之外,还会有自己的打开 JupyterLab 按钮。参见打开 JupyterLab。
实例访问令牌
模板需要令牌的实例,在你部署时都会得到一个自己的密钥:访问令牌。它和 JupyterLab 令牌是同一个值。实例的连接标签页会在访问令牌下以遮盖形式显示它,并提供显示和复制按钮;API 会把它作为实例的 access_token 返回给你的组织。实例被销毁后它就没了。
每个你能启动的、在已发布端口上提供 Web 界面或 API 的精选模板,都会在该端口上要求这个令牌:
| 模板 | 端口 | 如何出示令牌 |
|---|---|---|
| ComfyUI、Stable Diffusion WebUI (A1111)、Fooocus、Kohya_ss、Faster-Whisper(它的语音识别 API) | 应用的端口 | 应用前面有一个访问代理。打开按钮会为你登录;脚本发送 Authorization: Bearer <token> |
| Text Embeddings Inference | 80 | 服务器把它当作自己的 API 密钥来校验:每个请求都要带 Authorization: Bearer <token> |
| Triton Inference Server | 8000 (HTTP)、8001 (gRPC) | 除健康检查外的所有 API:HTTP 发送 Authorization: Bearer <token>,gRPC 发送元数据 triton-grpc-protocol-authorization: Bearer <token> |
| Open WebUI | 8080 | 以 admin@superheat.local 登录,密码就是这个令牌。注册已关闭,别人无法再创建账号 |
令牌只覆盖模板开始要求它之后部署的实例。更早部署的实例,端口保持原样——任何找到它们的人都能访问——直到你销毁它;重新部署才能拿到令牌。
Ollama、InvokeAI、LLaMA-Factory 和 vLLM 目前标记为即将推出。它们的服务器没法在每条路由上都要求令牌——vLLM 的 API 密钥会留下一条推理路由不设防——所以在能做到之前,它们先下架。已经在运行的这些实例不受影响。
# An API port behind the token — Text Embeddings Inference here
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"inputs": "hello"}' \
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