1. 从HPC的“孤岛”到云原生的“桥梁”:为什么我们需要Sarus Suite?
在传统的高性能计算领域,我们常常面临一个尴尬的局面:一边是追求极致性能、稳定和可控的物理机或专用集群,另一边是灵活、敏捷、易于分发的容器化应用生态。长久以来,HPC环境就像一个坚固的“孤岛”,它依赖MPI、OpenMP、PGAS等并行编程模型,对网络、存储、调度器有着苛刻的要求。而Docker引领的容器化浪潮,虽然席卷了互联网和云计算,但在HPC的门口却屡屡碰壁。Docker daemon的守护进程模型、对root权限的依赖、与Slurm/PBS等调度器的集成复杂度,都让HPC系统管理员望而却步。这种割裂导致科学计算应用的开发、测试和部署流程异常繁琐,可重复性和可移植性大打折扣。
Sarus Suite的出现,正是为了在这道鸿沟上架起一座桥梁。它不是一个全新的容器运行时,而是一个基于Podman构建的、专门为HPC环境量身定制的容器系统架构。Podman作为Docker的替代品,其无守护进程、rootless运行、兼容Docker CLI的特性,为HPC集成提供了绝佳的基础。但仅有Podman还不够,HPC环境还需要容器能无缝地、高性能地使用InfiniBand/RoCE高速网络、GPU、并行文件系统,并且能被Slurm等作业调度器直接管理。Sarus Suite就是这套完整解决方案的集大成者。
简单来说,如果你在HPC集群上遇到过以下问题,那么Sarus Suite就是你该仔细研究的工具:
- 想在集群上运行一个打包了复杂依赖(特定版本的CUDA、科学库)的容器化应用,但发现Docker因为权限问题根本装不上或用不了。
- 容器内的进程无法直接使用集群的InfiniBand网络进行MPI通信,性能损失巨大。
- 不知道如何让Slurm作业脚本直接启动和管理容器任务。
- 需要一种安全、可审计、多租户的容器运行方式。
Sarus Suite通过一系列精巧的架构设计,让容器在HPC环境中变得“原生”。它让研究人员和工程师能够像在云平台上一样,使用容器来封装和分发他们的计算工作流,同时又能完全榨取底层HPC硬件的性能潜力。接下来,我们将深入拆解这套架构的核心设计思想与关键实践。
2. 核心架构解析:Sarus Suite如何重新定义HPC容器运行时
Sarus Suite的架构可以看作是对标准OCI容器运行时栈的“HPC化”增强。它并非推翻重来,而是在关键路径上插入了一系列钩子和封装器,以确保容器进程能够融入HPC环境。其核心架构主要包含以下几个层次:
2.1 基于Podman的无守护进程基础
Sarus选择Podman作为底层容器引擎,是其最根本也最明智的设计决策。与Docker的C/S架构不同,Podman采用fork-exec模型直接调用runc来创建容器。这意味着:
- 无需守护进程:没有常驻的
dockerd,消除了单点故障和安全风险,也简化了在共享HPC系统上的安装和权限管理。 - 完美的Rootless容器:用户可以直接以自己的非特权身份创建和运行容器,这对于多租户的HPC中心至关重要。系统管理员无需担心用户通过容器获取主机root权限。
- CLI兼容性:
sarus命令的设计很大程度上兼容docker命令,用户学习成本极低。例如,sarus pull,sarus run等命令对于熟悉Docker的用户来说非常直观。
然而,原生的Podman仍然只是一个通用的容器工具。Sarus在Podman之上构建了一个抽象层,这个层负责解释用户命令,并生成适合HPC环境的、最终的podman run命令。
2.2 OCI Hook机制:容器生命周期的精准干预
这是Sarus实现HPC集成的技术基石。OCI运行时规范定义了“hooks”,允许在容器生命周期的特定时刻(如prestart,poststart,poststop)执行自定义脚本。Sarus大量使用了prestarthook。
当用户执行sarus run时,大致流程如下:
- Sarus CLI解析命令和参数。
- 根据配置和参数,动态生成一个或多个OCI Hook的配置文件。
- 调用
podman run,并通过--annotation等方式将Hook配置信息传递给底层的runc。 runc在启动容器进程前,执行prestarthook。这些hook是Sarus魔法的核心:- SSH Hook:在容器内启动一个SSH守护进程,并配置免密登录。这是为了让宿主机上的MPI启动器(如
mpirun)能够通过SSH进入容器内部启动MPI进程。 - MPI Hook:挂载宿主机上的MPI库(如OpenMPI, Intel MPI)到容器内,并调整容器内的
LD_LIBRARY_PATH等环境变量。这确保了容器内应用使用的是与宿主机兼容的高性能MPI实现,从而支持高速网络。 - GPU Hook:将宿主机的GPU设备文件(如
/dev/nvidia*)和相关的驱动库挂载到容器中,使容器内应用可以直接使用GPU。 - Globally-accessible filesystems Hook:将集群的并行文件系统(如Lustre, GPFS)挂载点以相同路径绑定挂载到容器内,保证容器内外访问存储的路径一致性。
- SSH Hook:在容器内启动一个SSH守护进程,并配置免密登录。这是为了让宿主机上的MPI启动器(如
通过这套Hook机制,Sarus实现了“开箱即用”的HPC能力注入。用户无需手动在容器镜像中配置复杂的SSH、MPI或存储,Sarus在运行时自动完成。
2.3 与作业调度器的深度集成:以Slurm为例
在HPC中,一切计算资源都由调度器管理。Sarus必须能与Slurm、PBS等调度器协同工作。集成方式通常有两种:
在Slurm作业脚本中直接调用
sarus:这是最常见的方式。用户在一个Slurm提交脚本中,使用srun或mpirun来启动任务,而命令的核心是sarus run。#!/bin/bash #SBATCH --job-name=my-sarus-job #SBATCH --nodes=2 #SBATCH --ntasks-per-node=4 #SBATCH --gres=gpu:1 # 使用 srun 启动,srun 会在每个计算节点上执行后面的命令 srun sarus run \ --mpi \ myregistry.com/hpc-app:latest \ ./my_mpi_application当
srun在多个节点上执行该命令时,每个节点上的Sarus都会独立启动容器,并通过预先配置的SSH实现跨节点的MPI进程间通信。使用
spank_sarusSlurm插件:这是更优雅、更集成的方案。SPANK是Slurm的插件接口。spank_sarus插件会在Slurm任务启动的早期被调用,自动为任务注入必要的环境变量和挂载信息,甚至可以自动拉取镜像。对用户来说,他们几乎感觉不到容器的存在,就像在运行一个原生二进制文件一样。#SBATCH ... # 同上 # 无需在命令中显式写出 sarus,插件会处理 srun ./my_mpi_application这种方式对用户最友好,但需要系统管理员在集群层面进行安装和配置。
2.4 镜像仓库与安全考量
Sarus支持标准的OCI镜像仓库(如Docker Hub, Quay.io, 或私有的Harbor)。对于HPC中心,通常会搭建一个本地镜像仓库,一方面加速镜像拉取,另一方面也便于进行安全扫描和审计。
安全是HPC的重中之重。Sarus的rootless模式奠定了安全基础。此外,管理员可以通过Sarus的配置文件严格限制用户行为,例如:
- 限制可拉取的镜像仓库白名单。
- 禁用容器的
--privileged模式。 - 控制哪些宿主机目录可以绑定挂载到容器内。
- 配合cgroups限制容器对CPU、内存等资源的使用,确保符合作业调度器的分配。
3. 关键组件与工作流程深度拆解
理解了宏观架构,我们再深入到几个关键组件的工作细节,这能帮助我们在遇到问题时进行排查。
3.1sarusCLI:用户接口与命令转换器
sarus命令行工具是用户的主要交互界面。它做的事情远比看上去多。当你键入sarus run --mpi -v /data:/data nvidia/cuda:11.8-runtime ./app时:
- 参数解析与验证:CLI首先解析所有参数。它会检查
--mpi标志,准备触发MPI hook;检查-v绑定挂载,确保路径在允许列表内(根据/etc/sarus.json配置)。 - 镜像处理:如果镜像在本地不存在,它会调用
podman pull(或skopeo)从配置的仓库拉取。Sarus支持缓存镜像层以提升效率。 - 生成OCI Bundle:Sarus会在一个临时目录(如
/var/sarus/...)为这个容器运行实例准备一个OCI Bundle。这包括:config.json: 容器的运行时配置。Sarus会在这里插入它生成的hook配置。- 容器的rootfs:通常通过overlayfs联合挂载实现。
- 构造最终的
podman run命令:这是核心步骤。Sarus不会直接创建容器,而是生成一个长长的、包含所有必要参数的podman run命令。例如,它会添加:--annotation: 传递hook配置。--security-opt label=disable: 在SELinux环境下可能需要。--device: 用于GPU设备。--volume: 用于绑定挂载存储和MPI库。--env: 设置特定的环境变量,如OMPI_MCA_btl_openib_allow_ib=1以启用InfiniBand。
- 执行与代理:最后,Sarus fork/exec执行这个构造好的
podman run命令,并可能代理容器的标准输入/输出。
注意:一个常见的困惑点是,用户以为
sarus是一个独立的运行时。实际上,它是一个智能的“翻译官”和“组装工”,将用户友好的sarus命令翻译成复杂的、HPC友好的podman run命令。理解这一点对调试至关重要——当你遇到容器启动失败时,可以尝试让Sarus输出它生成的最终命令(有些版本支持--debug参数),然后手动用podman run执行该命令,往往能更快定位问题。
3.2 Hook的执行环境与依赖隔离
Hook脚本本身在宿主机环境下执行,但拥有容器的命名空间视图。这意味着:
- MPI Hook:它需要知道宿主机上MPI库的安装路径。这个路径通常在Sarus的全局配置文件
/etc/sarus.json中定义。Hook的工作就是把/usr/mpi(例如)挂载到容器内的相同路径,并确保容器内的LD_LIBRARY_PATH包含该路径。这样,容器内的MPI程序在运行时,动态链接到的是宿主机的高性能MPI库,而非容器内可能存在的(或不存在的)版本。 - 依赖冲突风险:这里存在一个潜在问题。如果容器镜像内已经自带了MPI库(比如一个基于Ubuntu并安装了
mpich的镜像),而宿主机使用的是OpenMPI。Hook挂载宿主机库后,可能会因为库版本或ABI不兼容导致程序崩溃。最佳实践是:构建HPC容器镜像时,通常不安装MPI库,或者仅安装最小化的运行时依赖,将MPI的实现完全交给宿主机环境。镜像只包含应用代码和其非MPI的第三方库依赖。
3.3 跨节点通信:SSH + MPI的协同
这是Sarus实现分布式内存并行计算的关键。假设你在2个节点上各运行4个MPI进程:
- 容器启动:在每个计算节点上,Slurm的
srun启动了一个sarus run命令,进而启动了一个容器。每个容器内部,SSH hook都启动了一个sshd。 - MPI启动:用户命令中可能直接是MPI程序,或者由
srun启动mpirun。以OpenMPI为例,mpirun会读取Slurm提供的机器文件(hostfile),知道任务分布在哪些节点上。 - 进程启动:
mpirun通过SSH连接到其他节点。由于容器内的sshd配置了与宿主机共享的密钥,mpirun可以无缝登录到其他节点上的容器内部。 - 启动远程进程:登录后,
mpirun在远程容器内执行命令,启动MPI进程。因为所有容器内通过Hook挂载的MPI库路径是一致的,所以远程进程能正确链接。 - 高速网络通信:启动后的MPI进程,通过MPI库调用底层通信API(如OpenFabrics Interfaces, OFI)。由于容器进程使用了宿主机的MPI库,而宿主机MPI库在编译时已经支持了InfiniBand驱动,因此进程间的通信可以直接绕过TCP/IP,通过InfiniBand Verbs API进行,实现接近裸机的网络性能。
实操心得:跨节点SSH配置是故障高发区。务必确保Sarus配置的SSH密钥对在集群计算节点之间是互通的,且
sshd在容器内的启动参数正确。一个调试技巧是,先不用MPI,尝试用sarus run在两个节点上分别启动容器,然后在一个容器内手动SSH到另一个容器的IP,测试连通性。排除了SSH问题,MPI的问题就解决了一大半。
4. 实战部署与配置指南
理论说得再多,不如动手配置一遍。下面以一个基于Slurm的集群为例,概述系统管理员部署Sarus Suite的关键步骤。
4.1 前提条件与安装
- 集群环境:一个正在运行的Slurm集群,计算节点间SSH免密互通。节点上已安装高性能MPI库(如OpenMPI)和驱动(如OFED for InfiniBand)。
- 安装Podman:在所有计算节点和登录节点上安装Podman。确保版本兼容性。需要配置rootless模式,通常需要调整
/etc/subuid和/etc/subgid文件,让普通HPC用户拥有映射到足够数量子UID/GID的能力。# 例如,在RHEL/CentOS系列上 sudo yum install -y podman # 配置用户命名空间映射 echo "$USER:100000:65536" | sudo tee -a /etc/subuid echo "$USER:100000:65536" | sudo tee -a /etc/subgid - 安装Sarus:从GitHub Release页面下载最新版本的Sarus二进制包或使用RPM/DEB包安装。需要安装在所有节点上。
# 示例:解压并安装到 /opt/sarus tar -xzf sarus-<version>.tar.gz -C /opt ln -s /opt/sarus-<version> /opt/sarus - 配置Sarus:核心配置文件是
/etc/sarus.json。需要配置的关键部分包括:OCIBundleDir: 容器Bundle的生成目录,需要一个大容量的临时文件系统(如/tmp或专用的/scratch)。rootfsDirectory: 镜像解压层的目录。mountedFiles: 定义要挂载到所有容器中的文件,如/etc/hosts,/etc/passwd(只读)以保持用户信息。securityChecks: 启用或禁用安全特性。ssh: SSH Hook的配置,指定密钥路径、容器内sshd的路径等。mpi: MPI Hook的配置,指定宿主机MPI库的安装路径。registries: 允许拉取镜像的仓库列表,可以配置认证信息和TLS验证。
4.2 集成Slurm(使用SPANK插件)
- 编译
spank_sarus插件:Sarus源码中提供了插件。进入sarus_source/spank目录,根据Slurm版本进行编译。
这会将./configure --prefix=/usr/local SLURM_VERSION=21.08.2 # 根据实际Slurm版本修改 make sudo make installspank_sarus.so安装到Slurm的插件目录。 - 配置Slurm:编辑Slurm的配置文件(通常是
/etc/slurm/slurm.conf或/etc/slurm/plugstack.conf),启用该插件。# 在 plugstack.conf 中添加 required /usr/local/lib/slurm/spank_sarus.so - 配置插件:创建
/etc/sarus/spank.json,定义插件行为,例如默认镜像、是否自动拉取等。
4.3 用户端体验与示例
配置完成后,用户的使用体验可以非常流畅。
示例1:运行一个简单的GPU加速容器
# 交互式作业 salloc -N 1 -G 1 --gres=gpu:1 sarus run --tty --gpu nvcr.io/nvidia/pytorch:23.10-py3 python -c "import torch; print(torch.cuda.is_available())" # 输出应为:True示例2:提交一个多节点MPI作业脚本
#!/bin/bash #SBATCH --job-name=sarus-mpi-test #SBATCH --nodes=2 #SBATCH --ntasks-per-node=4 #SBATCH --partition=compute # 使用 spank_sarus 插件时,甚至可以不显式调用 sarus # 但这里展示显式调用 srun sarus run \ --mpi \ myharbor.example.com/hpc/mpi-benchmark:latest \ /opt/mpi-benchmarks/src/IMB-MPI1 PingPong用户只需要关心自己的应用和镜像,无需处理复杂的MPI库安装、网络配置和跨节点启动问题。
5. 性能调优、故障排查与最佳实践
将容器用于生产级HPC,性能和稳定性是生命线。以下是一些关键的经验和技巧。
5.1 性能调优要点
镜像构建优化:
- 使用多阶段构建:最终镜像只包含运行时必要的文件,减少镜像层数和体积,加速拉取和启动。
- 选择合适的基础镜像:推荐使用
ubi-micro或debian:stable-slim等极小化镜像。避免使用ubuntu:latest这类包含大量非必要软件的大镜像。 - 层缓存策略:将不经常变化的依赖安装(如系统包更新、基础库)放在Dockerfile的前面,将经常变化的代码复制放在后面。
存储I/O优化:
- 绑定挂载并行文件系统:务必使用
-v将集群的并行文件系统(如/lustre)挂载到容器内相同路径。避免在容器内进行大量数据读写,应直接读写绑定挂载的路径。 - 注意OverlayFS开销:容器的可写层(upperdir)使用OverlayFS。如果应用会产生大量小文件或频繁写入,这可能会成为瓶颈。考虑将临时目录或输出目录挂载到内存文件系统(
tmpfs)或高性能本地SSD上。sarus run --mount=type=tmpfs,destination=/tmp ...
- 绑定挂载并行文件系统:务必使用
网络与MPI优化:
- 确保InfiniBand支持:确认宿主机MPI库已启用InfiniBand支持,并且容器内进程通过Hook正确链接到了该库。可以通过在容器内运行
ompi_info | grep btl来检查OpenMPI的传输层是否包含openib。 - 进程绑定:像原生作业一样,使用
--cpu-bind和--mem-bind等Slurm参数或MPI环境变量进行进程与CPU/内存的绑定,减少NUMA效应的影响。
- 确保InfiniBand支持:确认宿主机MPI库已启用InfiniBand支持,并且容器内进程通过Hook正确链接到了该库。可以通过在容器内运行
5.2 常见故障排查
容器启动失败:
permission denied- 检查rootless配置:确认用户已正确配置
/etc/subuid和/etc/subgid。使用podman unshare cat /proc/self/uid_map检查映射是否生效。 - 检查临时目录权限:Sarus的
OCIBundleDir和rootfsDirectory对应的目录,必须对运行用户可写。
- 检查rootless配置:确认用户已正确配置
MPI作业卡住或失败
- SSH连接问题:这是首要怀疑对象。在作业脚本开头加入
export SARUS_SSH_LOG_LEVEL=DEBUG来获取详细的SSH日志。检查计算节点间的SSH免密登录是否正常,以及容器内的sshd是否成功启动(查看/var/log/sarus/ssh*.log)。 - MPI库不兼容:在容器内运行
ldd /path/to/your/mpi/app,查看链接的MPI库是否来自宿主机挂载的路径。对比宿主机和容器内mpirun --version的输出。 - 网络设备未找到:如果MPI报错找不到InfiniBand设备,检查Hook是否成功挂载了
/dev/infiniband目录(如果MPI库需要)。可能需要额外配置Sarus的devices列表。
- SSH连接问题:这是首要怀疑对象。在作业脚本开头加入
GPU不可用
- 检查设备挂载:使用
sarus run --tty --gpu nvidia/cuda:11.8-runtime nvidia-smi测试。如果失败,检查Sarus配置中device部分是否包含了NVIDIA设备文件。 - 驱动兼容性:确保宿主机NVIDIA驱动版本与容器内CUDA Toolkit版本兼容。
- 检查设备挂载:使用
5.3 安全与多租户最佳实践
- 严格的镜像仓库策略:只允许从受信任的内部仓库拉取镜像。对所有入库镜像进行漏洞扫描。
- 资源限制:虽然Slurm控制了作业层面的资源,但仍建议在Sarus或Podman层面使用cgroups对单个容器的资源使用(如内存、PIDs数量)设置上限,防止单个容器耗尽节点资源。
- 审计日志:启用Sarus和Podman的详细日志,记录所有镜像拉取和容器运行事件,便于事后审计和故障追溯。
- 用户教育:培训用户构建安全、高效的HPC容器镜像,避免在镜像中放入敏感信息,鼓励使用多阶段构建。
从我个人的部署和使用经验来看,Sarus Suite的成功应用,30%在于软件本身的正确安装,70%在于对HPC环境和容器技术交织点的深刻理解与细心配置。它不是一个“一键部署”的魔法黑盒,而是一个强大的工具,当你清晰地知道它在每个环节做了什么,你就能驾驭它,让容器化真正为HPC科研与工程赋能。最初的调试阶段可能会遇到各种网络、权限、依赖问题,但一旦打通,它为团队带来的开发效率提升和应用部署的标准化收益将是巨大的。对于正在寻求现代化其HPC软件交付流程的团队来说,投入时间评估和部署Sarus Suite,是一项非常值得的投资。