跳到主要内容

基础镜像

sshjupyter 启动模式需要一个自带 SSH 服务、JupyterLab 和启动契约的镜像。像 pytorch/pytorch 这样的普通上游镜像一样都没有,这就是这两种模式被限制为使用派生自 Superheat 基础镜像的镜像的原因。

superheat/base

这是规范名称,托管在 Docker Hub 上。该镜像同时镜像到 ghcr.io/superheat-software/base,标签集完全一致,由同一次构建推送 —— 所以两个引用都可用,且当 Docker Hub 不可达时主机会回退到镜像源。建议使用短名称。

两者都是公开的,所以主机无需凭据即可拉取,使用基础镜像的模板也不需要配置镜像仓库凭据。

已发布的标签

标签是什么
cuda-12.1CUDA 12.1
cuda-12.4CUDA 12.4
cuda-12.6CUDA 12.6
cuda-12.8CUDA 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、带 pipjupyterlabpython3tinigosuopensslca-certificatescurlgitrsync。工作目录是 /workspace,入口点是 tini -- /opt/superheat/entrypoint.sh

镜像带有标签 com.superheat.base="true"。Superheat 就是靠这个标签识别一个镜像可用于 sshjupyter 模式,而任何 FROM 基础镜像构建的镜像都会继承它。

入口点做了什么

每一步都是幂等的,所以停止再启动实例时,在同一个容器上重新运行是安全的。

步骤详情
1. 导出环境容器环境会被写入 /etc/environment/etc/profile.d/superheat-env.sh,因为 sshd 和 Jupyter 启动的登录 shell 不会继承它。预置阶段的机密被排除在外。
2. 安装密钥PUBLIC_KEYSUPERHEAT_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_PORTJupyter 绑定端口,默认 8080
JUPYTER_DIRJupyter 根目录,默认 /workspace
JUPYTER_LABtrue 表示 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

有三条规则能让结果在 sshjupyter 模式下依然可启动:

  1. 继承,不要重建。nvidia/cuda 构建并塞进你自己的 sshd,会丢掉 com.superheat.base 标签,连带丢掉入口点契约。
  2. 不要覆盖 ENTRYPOINT 正是基础镜像的入口点在启动 sshd、Jupyter 和你的启动脚本。覆盖它会把一个 ssh 模板变成一个进不去的容器。
  3. 把你的进程放进 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.logJupyter 输出
/etc/profile.d/superheat-env.sh登录 shell 看到的环境

sshd 和 Jupyter 也会输出到容器日志,也就是控制台显示的内容。参见日志