基础镜像
ssh 和 jupyter 启动模式需要一个自带 SSH 服务、JupyterLab 和启动契约的镜像。像 pytorch/pytorch 这样的普通上游镜像一样都没有,这就是这两种模式被限制为使用派生自 Superheat 基础镜像的镜像的原因。
superheat/base
这是规范名称,托管在 Docker Hub 上。该镜像同时镜像到 ghcr.io/superheat-software/base,标签集完全一致,由同一次构建推送 —— 所以两个引用都可用,且当 Docker Hub 不可达时主机会回退到镜像源。建议使用短名称。
两者都是公开的,所以主机无需凭据即可拉取,使用基础镜像的模板也不需要配置镜像仓库凭据。
已发布的标签
| 标签 | 是什么 |
|---|---|
cuda-12.1 | CUDA 12.1 |
cuda-12.4 | CUDA 12.4 |
cuda-12.6 | CUDA 12.6 |
cuda-12.8 | CUDA 12.8 |
latest | 最新的 CUDA 次版本 |
sha-<12 chars>-<minor> | 每次构建、每个 CUDA 次版本对应一个不可变标签 |
每个 cuda-<minor> 标签都基于 Ubuntu 22.04 上对应的 NVIDIA CUDA cuDNN runtime 镜像构建——cuda-12.4 来自 nvidia/cuda:12.4.1-cudnn-runtime-ubuntu22.04。镜像只有 amd64 版本,这与机群一致:Superheat 主机都是 x86_64。
cuda-<minor> 这套命名也是 [Automatic] 模板标签解析时对照的对象。把模板的标签保留为 [Automatic],会选中所选机器的 CUDA 能运行的最新标签,于是一个模板可以适配驱动版本参差不齐的机群。当你需要一次构建可复现而不是保持最新时,请固定一个具体标签。
它增加了什么
在 CUDA runtime 之上:openssh-server、带 pip 和 jupyterlab 的 python3、tini、gosu、openssl、ca-certificates、curl、git 和 rsync。工作目录是 /workspace,入口点是 tini -- /opt/superheat/entrypoint.sh。
镜像带有标签 com.superheat.base="true"。Superheat 就是靠这个标签识别一个镜像可用于 ssh 和 jupyter 模式,而任何 FROM 基础镜像构建的镜像都会继承它。
入口点做了什么
每一步都是幂等的,所以停止再启动实例时,在同一个容器上重新运行是安全的。
| 步骤 | 详情 |
|---|---|
| 1. 导出环境 | 容器环境会被写入 /etc/environment 和 /etc/profile.d/superheat-env.sh,因为 sshd 和 Jupyter 启动的登录 shell 不会继承它。预置阶段的机密被排除在外。 |
| 2. 安装密钥 | PUBLIC_KEY 和 SUPERHEAT_SSH_PUBLIC_KEYS 会写入 /root/.ssh/authorized_keys。 |
| 3. 启动 sshd | 主机密钥缺失时会先生成,然后 sshd 监听容器 22 端口。仅支持密钥;不接受密码。 |
| 4. 启动 Jupyter | 仅当设置了 JUPYTER_TOKEN 时。自签名证书只生成一次,JupyterLab 通过 HTTPS 绑定到 0.0.0.0:${JUPYTER_PORT}。 |
| 5. 运行启动脚本 | SUPERHEAT_ONSTART 运行一次,由哨兵文件把关。失败会被记录,容器保持运行。 |
| 6. 移交 | 如果镜像有 CMD 就执行它;否则容器进入空闲状态,以便为端口映射保持运行。 |
环境契约
这些变量名就是 Superheat 与镜像之间的接口。基于基础镜像构建,入口点会替你处理它们;自己写入口点,这些就是你必须实现的东西。
| 变量 | 用途 |
|---|---|
PUBLIC_KEY | 单个 OpenSSH 公钥,追加到 authorized_keys |
SUPERHEAT_SSH_PUBLIC_KEYS | 以换行分隔的 OpenSSH 公钥,追加到 authorized_keys |
SUPERHEAT_ONSTART | 首次启动时运行一次的 Bash 脚本。属敏感内容;不会写入环境文件。裸别名 ONSTART 同样被接受 |
JUPYTER_TOKEN | 设置后,JupyterLab 会以它作为访问令牌启动 |
JUPYTER_PORT | Jupyter 绑定端口,默认 8080 |
JUPYTER_DIR | Jupyter 根目录,默认 /workspace |
JUPYTER_LAB | true 表示 JupyterLab(默认),false 表示经典 notebook |
OPEN_BUTTON_PORT | 仅供参考——控制台打开按钮的目标端口 |
CONTAINER_ID | 仅供参考——工作负载标识符 |
GPU_COUNT | 仅供参考——分配了多少块 GPU |
SUPERHEAT_TCP_PORT_<n> | 仅供参考——容器端口 <n> 对应发布的主机端口 |
Superheat 在你的模板环境变量和租用者的任何覆盖之后,最后才设置这些变量,因此它们都无法被压过。参见环境变量。
构建你自己的镜像
从 CUDA 版本符合你代码需要的那个标签继承,安装你的依赖,别动入口点:
FROM superheat/base:cuda-12.4
RUN pip install --no-cache-dir torch torchvision transformers
COPY train.py /workspace/train.py
WORKDIR /workspace
有三条规则能让结果在 ssh 和 jupyter 模式下依然可启动:
- 继承,不要重建。 从
nvidia/cuda构建并塞进你自己的 sshd,会丢掉com.superheat.base标签,连带丢掉入口点契约。 - 不要覆盖
ENTRYPOINT。 正是基础镜像的入口点在启动 sshd、Jupyter 和你的启动脚本。覆盖它会把一个ssh模板变成一个进不去的容器。 - 把你的进程放进
CMD。 入口点会在预置完成后执行CMD,所以这样启动的长期运行的服务会与 sshd 和 Jupyter 并存。
如果你的镜像做不到这几条——比如是你无法控制的上游镜像,或者它有自己的入口点——那就改用 args 启动模式。它会运行任何镜像原生的入口点,不需要基础镜像提供任何东西。参见启动模式。
该从哪个标签继承
| 场景 | 标签 |
|---|---|
| 你的框架锁定了某个 CUDA 次版本 | 对应的 cuda-<minor> |
| 你想要可用的最新 CUDA,并且可以重新构建 | latest |
| 你需要几个月后仍能解析出完全相同的构建 | sha-… 标签 |
固定一个 cuda-<minor> 标签通常是正确答案。latest 会在新的 CUDA 次版本发布时移动,这对开发镜像没问题,但作为可复现训练的基础就很差。
文件系统布局
| 路径 | 内容 |
|---|---|
/workspace | 工作目录,默认也是 Jupyter 的根目录 |
/opt/superheat/entrypoint.sh | 预置入口点 |
/var/lib/superheat/onstart.sh | 实际被执行的启动脚本内容 |
/var/lib/superheat/.onstart-done | 阻止启动脚本重复运行的哨兵文件 |
/var/log/superheat/onstart.log | 启动脚本输出 |
/var/log/superheat/jupyter.log | Jupyter 输出 |
/etc/profile.d/superheat-env.sh | 登录 shell 看到的环境 |
sshd 和 Jupyter 也会输出到容器日志,也就是控制台显示的内容。参见日志。