把科学计算工作负载从 Docker 迁往 Apptainer,只有把“迁移”限定为复制应用的用户空间时,这件事才称得上容易。Apptainer 可以拉取 OCI 镜像、解包各层,再生成单一的 SIF 文件。难点从这份文件进入共享集群后开始显现:进程沿用提交作业者的身份,镜像保持只读,主机目录和环境变量可以进入容器,而作业能分到哪些 CPU、内存、GPU 和节点,仍由 Slurm 或其他调度器决定,容器运行时不参与分配。[1][2]

采用 Apptainer 的理由就在这些差异里;若将其当作需要遮掩的兼容性缺陷,就会错过设计重点。项目将 Apptainer 描述为一种以集成为先、面向共享系统的运行时:同一份不可变镜像可以送入集群,计算中心仍掌控身份、文件系统、加速器、高速网络和策略。当前仓库将 v1.5.1 列为最新版本,发布于 2026 年 6 月 4 日。这说明 Apptainer 仍在活跃维护,是面向当前的运维选择;把它视为旧版 Singularity 留下的权宜方案,已经偏离现状。[1]

因此,合理的迁移承诺应当收窄:保留软件制品,同时改写执行契约。Docker 镜像能在工作站上成功启动,只够把对应工作负载列入 Apptainer 迁移候选;集群就绪仍要另行验证。

转换一次,此后作业固定使用 SIF

最容易顺手输入的第一条命令,也不适合成为生产习惯:

apptainer run docker://ghcr.io/example/solver:latest

它适合做冒烟测试。但在底层,Apptainer 会抓取 OCI 数据块(blob)和配置,将各层转换为 SIF,并把结果保存在缓存中。项目明确建议把 Docker 镜像拉取为本地 SIF,后续直接运行该文件。在共享系统上,这一点尤其重要:大量作业集中运行时,镜像仓库 API 限额面临耗尽风险,转换工作也容易重复。[2]

更扎实的交接方式如下:

apptainer pull solver-2026-07.sif \
  docker://ghcr.io/example/solver@sha256:<pinned-oci-digest>
sha256sum solver-2026-07.sif > solver-2026-07.sif.sha256

锁定 OCI digest,也就锁定了输入;记录 SIF 校验和,则锁定了集群实际使用的制品。两者都很重要,因为同一 OCI 镜像经过两次转换,所得 SIF 文件的字节未必完全一致,转换日期等元数据有时会变化。[2] 拉取或构建应放在 CI、指定的构建主机,或其他经计算中心批准、适合大量拉取镜像的位置。经过批准的 SIF 应存入按版本管理的只读项目目录,避免作业数组中的数千项任务各自解析 latest

团队若需要集群专用软件包或可审计配方,Apptainer 定义文件(definition file)更适合作为这条构建边界。文件头选择 docker 等引导代理(bootstrap agent),%post%environment%files%runscript 等构建段(section)描述构建过程,定义文件还可以嵌入最终镜像,供后续检查。[3] 这份配方应与原 Dockerfile 并列保存,两套依赖说明保持同源。同一条构建流水线应同时产出供工作站和 CI 测试使用的 OCI 制品,以及集群真正运行的 SIF。

调用者身份会随进程进入容器

Docker 工作流常把镜像中的 USER 指令视为运行身份。Apptainer 会忽略这条指令。普通作业进入容器时,用户和组身份与主机上保持一致;--fakeroot 可以让该账户在映射后的用户命名空间内显示为 root,却不会让研究人员成为主机 root,主机上的身份和权限仍然不变。[1][2]

这是第一项决定性的兼容性测试。安装在 /root 下的软件、强制使用固定服务账号的启动脚本,以及启动时尝试对系统路径执行 chown 的入口程序(entrypoint),在 Docker 中可以成立,进入集群后则存在立即失败的风险。修正工作主要落在镜像构建阶段:把程序安装到 /usr/local/opt,明确运行数据的存放位置,并确保应用能以任意非 root UID 启动。--fakeroot 应限于受控构建或诊断,是否可用取决于站点配置;生产作业仍需遵循正常的权限模型。

这套身份模型也让数据归属更加清楚。写入已绑定项目目录或暂存(scratch)目录的输出,所有者就是实际提交作业的研究人员。共享 Unix 权限与配额仍按集群管理员设定的含义生效。Apptainer 因而很适合批处理科研任务;若软件的基本假设是“稍后由特权守护进程修复权限”,适配度就很低。

从设计之初按只读处理

SIF 通常以只读方式挂载。Docker 的容器可写层不会随迁移变成持久作业状态的存放位置。Apptainer 可以使用 --writable-tmpfs、沙盒(sandbox)或叠加层(overlay),但清晰的迁移方案会把不可变软件与明确声明的可写数据分开。[2]

每一条实际写入路径都应配置 bind:

apptainer exec \
  --bind "$SCRATCH/run-$SLURM_JOB_ID:/work" \
  solver-2026-07.sif \
  /opt/solver/bin/run --input /work/input --output /work/results

Apptainer 支持 --bind src:dest:opts--mount 语法,管理员还可以定义额外的系统 bind 路径。[4] 实际迁移时,可以先让应用在根文件系统严格只读的条件下运行一次,逐一记录所有写入尝试。缓存、模型下载、编译后的 kernel、锁文件、临时数据库和许可证状态,都需要归入有明确所有者的位置。若处理办法只是“让整个容器可写”,并行作业终会把这份便利变成数据损坏或不可复现的结果。

还有一处容易意外引入状态:Apptainer 默认安装会绑定用户主目录、/tmp 和当前工作目录;站点配置还可以加入项目路径。主机数据因此取用方便,也给 ~/.config、用户自行安装的 Python 包或旧凭据文件留下了干预固定镜像行为的入口。[2][4] 测试时应启用 --contain--containall,再只恢复工作流所需的存储。一次成功运行应能由镜像、已声明的 bind、输入数据和作业参数完整解释,研究人员主目录里碰巧存在的文件不应成为隐含条件。

清理环境,再排查镜像

Docker 默认不会注入主机环境,Apptainer 通常会将其传入。在大量使用环境模块(module)的集群上,PYTHONPATHLD_LIBRARY_PATH、代理设置、区域设置(locale)或工具专用配置都能悄然穿过这道分界。--cleanenv 会移除大部分环境状态;有意加上 APPTAINERENV_ 前缀的值,或经 --env--env-file 提供的值,仍可在容器内设置。[2][5]

因此,--cleanenv 适合作为验收测试的默认项。只传回作业自行管理的变量,例如:

export APPTAINERENV_OMP_NUM_THREADS="$SLURM_CPUS_PER_TASK"
apptainer exec --cleanenv solver-2026-07.sif /opt/solver/bin/run

在部分由 OCI 镜像转换而来的容器中,Apptainer 对环境变量和 runscript 参数的求值方式也有差异。--no-eval 会关闭额外的 shell 求值;--compat 则组合了多项接近 Docker 的行为,包括隔离(containment)、临时可写根文件系统和关闭求值。[2] 排查初期可以用 --compat 快速锁定某项假设,随后再明确生产作业究竟需要其中哪一种行为。长期使用整组兼容行为,容易遮住迁移本应显明的主机状态、写入路径或命名空间分界。

调度器分配资源,Apptainer 将其带入容器

Apptainer 命令仍是已调度作业里的一个进程。镜像中设有八个工作线程(worker thread),作业实际拿到的核心数仍以调度器分配为准;应用要求的 32 GiB 内存和命令行 --nv 所对应的 GPU,也同样由调度器分配。NIH 的 Biowulf 指南展示了正确顺序:先在批处理提交中申请资源,进入调度器划定的资源范围后再加载 Apptainer 并运行 SIF;其中的 GPU 示例先经调度器申请 CPU、内存和 GPU,随后才调用 apptainer exec --nv。[8]

--nv 会让 NVIDIA 设备节点和主机的基础 CUDA 库进入容器。镜像内的应用仍须面向主机显卡与驱动所支持的 CUDA 版本和设备计算能力(device capability)。[6] 这项分工经过明确设计:用户空间软件栈可以放在 SIF 中,内核驱动(kernel driver)则归计算中心管理。AMD GPU 也遵循同一原则,对应选项为 --rocm。[6]

MPI 把这条分界划得更清楚。在常见的混合模式(hybrid model)中,主机上的 srunmpirun 启动进入容器的进程,因此镜像内的 MPI 必须与主机 MPI 及互连网络栈(fabric stack)兼容。[7] 单节点测试无法验证这一点。批准镜像前,应执行小规模的多节点集合通信测试,确认 rank 布局,并与计算中心的原生基线比较吞吐量。把 MPI 库封装进镜像,高速互连仍由集群提供。

用迁移门槛检验执行契约

可信的试点需要一个有代表性的工作负载,以及五轮条件各异的运行。第一轮,在容器内检查 id$HOME、工作目录和文件所有权。第二轮,采用只读根文件系统与明确的 bind,并让两个并发作业分别写入独立输出目录树。第三轮,加入 --cleanenv,只恢复已声明的变量。第四轮,让调度器施加较小的内存或时间限制,确认退出状态、日志和部分输出都容易理解。第五轮,实际走通 GPU 或多节点 MPI 硬件链路,避免用纯 CPU 冒烟测试批准镜像。[4][5][6][7][8]

如果实验室、科研平台团队或软件团队已经发布 OCI 镜像,能够建立受控的 SIF 构建流程,并管理校验和、存放位置和调度器模板,Apptainer 会是很合适的选择。当许多用户需要在共享文件系统和计算中心管理的加速器上运行同一套复杂科学软件栈时,它尤其有用。若工作负载依赖特权守护进程、可变根文件系统状态、Docker Compose 风格的服务编排、私有容器网络或长期运行的控制平面,Apptainer 的适配度会降低。这些差异牵涉整套运行模式,范围远超几个选项的调整。

团队能够完整说出执行契约时,迁移才算完成:固定的 OCI 输入、有记录的 SIF 制品、任意调用者 UID、只读软件、明确的可写路径、干净的环境、由调度器管理的资源,以及经过测试的驱动或 MPI 兼容性。Apptainer 的价值正在于让这些集群事实始终可见。它保留镜像,也拒绝把镜像等同于完整系统。

来源

  1. Apptainer 项目仓库——当前版本、SIF 可移植性、以集成为先的设计、共享系统安全模型、治理与维护状况。
  2. Apptainer User Guide,“Support for Docker and OCI Containers”——OCI 到 SIF 的转换、registry 缓存、只读行为、USER、主机挂载、环境传播、--compat 与兼容性限制。
  3. Apptainer User Guide,“Definition Files”——bootstrap agent、构建段、环境、文件、runscript 与可检查的构建配方。
  4. Apptainer User Guide,“Bind Paths and Mounts”——管理员和用户绑定路径、--bind--mount、containment 与挂载控制。
  5. Apptainer User Guide,“Environment and Metadata”——主机变量优先级、--cleanenvAPPTAINERENV_、环境文件与镜像元数据。
  6. Apptainer User Guide,“GPU and Other Device Support”——--nv、主机驱动库、CUDA 兼容性、设备可见性与 --rocm
  7. Apptainer User Guide,“Apptainer and MPI applications”——混合与 bind MPI 模式、launcher 位置、ABI 兼容性与集群互连分界。
  8. NIH High-Performance Computing,“Apptainer”——一份独立的生产实践指南,涵盖 Biowulf 缓存、存储绑定、交互式资源分配、批处理作业,以及先向调度器申请 GPU 再运行容器。
  9. Wikimedia Commons,“Frontier Supercomputer (1).jpg”——OLCF 于 2022 年在橡树岭国家实验室拍摄的 HPE Cray EX Frontier 系统照片,本文配图来源。