第2章 核心对比:Singularity vs Docker
说实话,我刚接触容器那会儿,也以为Singularity就是Docker的一个"HPC特别版"。直到在集群上真刀真枪用起来,才明白这两兄弟从根儿上就不是一路人。这一章咱们掰开了揉碎了聊聊——它们到底差在哪儿,以及什么时候该用谁。
2.1 设计哲学——它们生而不同
我个人习惯用一句话概括:Docker为微服务而生,Singularity为科学计算而来。
Docker的目标是打包应用+依赖,实现"一次构建,到处运行"。它假设你拥有root权限,喜欢用daemon管理一切,容器之间最好完全隔离。
Singularity呢?它出生在高性能计算(HPC)环境里。那地方用户没有root,共享文件系统遍地都是,MPI作业跨节点跑,GPU直接透传——你想想看,Docker那套C/S架构在这根本玩不转。
一句话总结:Docker追求"隔离",Singularity追求"集成"。
2.2 架构差异——C/S 与进程级的较量
Docker是典型的C/S架构。你敲docker run,客户端把请求发给后台的dockerd守护进程,dockerd再调containerd、runc层层下发。这一套很成熟,但代价是——你依赖一个常驻的、拥有root权限的daemon。
Singularity没有daemon。你敲singularity exec,它直接挂载SIF镜像文件,把容器作为当前用户的一个普通进程启动。没有中间商赚差价。说白了,Singularity容器的本质就是一个隔离化的进程。
一个小实验:在Singularity容器里执行ps aux,你会看到容器进程的PID和宿主机是连通的——这在Docker里默认看不到。Singularity故意这么设计,因为HPC作业管理系统(比如SLURM)需要看到所有子进程。
2.3 安全模型——权限的取舍
我在HPC集群上折腾了大半年,最深的一个体会是:Docker的安全模型和HPC格格不入。
Docker默认需要root权限来启动daemon。虽然可以通过rootless模式缓解,但那套配置复杂得让人想摔键盘。更关键的是,很多HPC集群的安全策略直接禁止运行Docker daemon——因为它能提权、挂载宿主机目录、修改iptables。你想想看,集群管理员怎么可能放心?
Singularity不一样。它从设计上就不需要daemon,也不需要root。容器以当前用户身份运行,默认不提升任何权限。你在容器里是啥权限,出来还是啥权限。
避坑指南:我曾经在生产环境遇到过Docker容器的SUID提权问题——一个被攻破的容器通过SUID二进制获取了宿主机的root权限。Singularity默认禁用SUID,从机制上杜绝了这类问题。如果你在涉密或高安全等级的环境里跑容器,这一点尤其要重视。
2.4 使用场景——各有所长
没有银弹。两种工具各有主场:
| 场景 | 推荐工具 | 原因 |
|---|---|---|
| 微服务、Web应用、CI/CD | Docker | 生态成熟,编排工具多,镜像小 |
| HPC、科学计算、AI训练 | Singularity | 无需root,MPI/GPU直通,与调度器集成 |
| 多用户共享集群 | Singularity | 无daemon,不留特权口子 |
| 桌面应用、开发环境 | Docker | 交互方便,端口映射顺手 |
| 复现老旧科研软件 | Singularity | 直接打包现有系统,SIF单文件易分发 |
2.5 命令对比——从 Docker 到 Singularity
如果你用过Docker,上手Singularity其实很快。命令结构高度相似,但细节上有不少坑。我个人习惯在迁移时先做一个"命令映射表",对照着练几遍就熟了。
| 操作 | Docker | Singularity |
|---|---|---|
| 拉取镜像 | docker pull ubuntu:22.04 |
singularity pull docker://ubuntu:22.04 |
| 运行容器 | docker run -it ubuntu:22.04 bash |
singularity exec ubuntu_22.04.sif bash |
| 交互式Shell | docker run -it --rm ubuntu:22.04 |
singularity shell ubuntu_22.04.sif |
| 挂载目录 | -v /host:/container |
--bind /host:/container |
| 构建镜像 | docker build -t myapp . |
singularity build myapp.sif myapp.def |
| 查看容器 | docker ps |
singularity instance list |
来看一个实际迁移的例子。假设你有一个Docker容器跑Python训练脚本:
# Docker 方式
docker run -it --rm --gpus all \
-v /data:/data \
-v /home/user/project:/workspace \
nvcr.io/nvidia/pytorch:22.12-py3 \
python /workspace/train.py
换成Singularity是这样的:
# Singularity 方式
singularity exec --nv \
--bind /data:/data \
--bind /home/user/project:/workspace \
pytorch_22.12.sif \
python /workspace/train.py
结构几乎一样?对的。但注意两个关键区别:
- GPU标志:Docker用
--gpus all,Singularity用--nv(NVIDIA)或--rocm(AMD) - 自动挂载:Singularity默认不挂载任何宿主机路径,你要手动用
--bind指定。而Docker在某些配置下会自动挂载部分目录
避坑指南:我曾经帮团队从Docker迁移到Singularity时踩过一个坑——Singularity默认把$HOME挂载进容器,并且没有写保护。结果一个训练脚本把临时文件写到了用户的$HOME里,把集群用户目录撑爆了。后来我们在所有容器里强制加了一句:--no-home,同时用--bind单独指定工作目录。记住:永远不要在Singularity容器里依赖$HOME的行为,显式指定挂载点才是王道。
2.6 本章总结:一张表说清楚
下面这张SVG把全章的核心差异浓缩在一起。你可以把它当作选型时的"速查地图":
最后用一张速查表收尾,方便你以后翻回来快速对比:
| 维度 | Docker | Singularity |
|---|---|---|
| 架构模式 | C/S(Client-Daemon) | 进程级(直接运行) |
| 是否需要root | 是(默认,rootless模式复杂) | 否 |
| 是否有常驻daemon | 是(dockerd) | 否 |
| 容器隔离粒度 | 强隔离(独立PID/net/mnt命名空间) | 弱隔离(与宿主机共享更多命名空间) |
| GPU直通 | --gpus all |
--nv / --rocm |
| MPI支持 | 需额外配置 | 原生集成(Hybrid MPI模型) |
| 镜像格式 | 分层镜像(layer tar包) | 单文件SIF(只读挂载) |
| 核心适用场景 | 微服务、CI/CD、Web应用 | HPC、科学计算、AI/ML训练 |
搞清楚这些差异,你就能在项目选型时少走弯路。我个人的建议是:别硬用Singularity跑Web服务,也别非拿Docker去HPC集群上折腾——工具选对,事半功倍。