镜像拉取与缓存:解锁 Singularity pull/cache 的本地管理
镜像拉取,说白了就是你从远程仓库把容器镜像搬到本地。
但这里面有个坑——每次 pull 都重新下载,网络差的时候能把人急死。
我个人习惯用 缓存 解决这个问题。
Singularity 的缓存机制能让你只下载一次,后续直接复用。
我们直接进正题。
1. 一个标准的 pull 流程
先看最基础的用法。
singularity pull docker://ubuntu:22.04
这句命令会从 Docker Hub 拉取 ubuntu:22.04 镜像,然后自动转换成 Singularity 的 .sif 格式。
默认文件名是 ubuntu_22.04.sif。
如果你想指定名字:
singularity pull my_ubuntu.sif docker://ubuntu:22.04
嗯,这里要注意。
pull 不仅仅是从 Docker Hub 拉,它支持常见仓库,比如 oras://、shub://、library://。
我在项目中用过 oras:// 从私有仓库拉镜像,速度挺稳。
小提示
singularity pull 默认会验证镜像签名。
如果你信任来源,可以加 --disable-cache 跳过缓存,但不推荐。
2. 缓存机制:核心设计
Singularity 的缓存,说白了就是你本地的一个大仓库。
第一次 pull 时,镜像下载到 ~/.singularity/cache。
第二次 pull 同一个镜像,直接读缓存,不再下载。
这个机制叫 内容寻址存储。
每个缓存块用 SHA256 哈希命名,你不用担心重复。
我画了张图,你看了就明白。
这张图展示了完整的 pull → check cache → (hit/miss) → use/store 路径。
我做的项目里,缓存命中率能到 80% 以上,省了不少流量。
3. 缓存目录结构
默认缓存路径是 ~/.singularity/cache。
里面长这样:
~/.singularity/cache/
├── blob/
├── oci-tmp/
├── net/
└── shub/
解释一下:
- blob/ :存储镜像的原始数据层,以哈希命名
- oci-tmp/ :OCI 格式的临时文件
- net/ :从网络拉取的镜像文件
- shub/ :Singularity Hub 专用缓存
你想想看,如果不清理,缓存会越来越大。
我见过有人把 80GB 缓存撑满,导致系统告警。
警告
缓存目录尽量放在大容量分区。
我曾经在 /home 分区磁盘写满时 pull 镜像,直接失败了。
解决方法:设置环境变量 SINGULARITY_CACHEDIR 指向另一个分区。
4. 关键参数一览
我整理了一个表格,常用参数都在这里。
| 参数 | 作用 | 我的建议 |
|---|---|---|
--disable-cache |
强制不缓存,每次都重新下载 | 只在测试新版本时用 |
--dir |
指定输出目录,不写当前目录 | 推荐配合 --force 用 |
--force |
覆盖已存在的 .sif 文件 | 确保你不会拿到旧文件 |
--tmpdir |
指定临时目录 | 磁盘空间不足时特别有用 |
举个例子,你可以在空间有限的系统里这样拉:
singularity pull --tmpdir /big_disk/tmp --dir /big_disk/images my_img.sif docker://ubuntu:22.04
5. 本地镜像管理
拉下来只是第一步,管理才是重点。
我主要用以下几个操作。
- 复制与重命名 :直接用
cp或mv,但要注意.sif是只读的,复制后需要重新签名。 - 签名验证 :
singularity verify my_image.sif,保证镜像没被篡改。 - 转换格式 :
singularity build --sandbox my_image/ my_image.sif,转成沙盒方便调试。 - 镜像清理 :
singularity cache clean,谨慎使用,会清空所有缓存。
我曾经在项目交付前忘记签名,结果对方跑不起来。
从那以后我每次发布都加一步 verify。
核心要点
缓存管理是镜像拉取的生命线。
用好 SINGULARITY_CACHEDIR 和定时清理,能省下很多时间。
6. 性能调优与架构考量
如果你维护多个节点,可以考虑 共享缓存。
做法是在 NFS 上放一个公共缓存目录。
export SINGULARITY_CACHEDIR=/shared/singularity/cache
这样所有节点第一次拉取后,其他节点直接走缓存。
但需注意 NFS 的 I/O 延迟,有时候还不如本地拷贝。
另一种方案是 分布式缓存,用 Harbor 或 Nexus 做私有仓库。
我建议优先用 oras:// 协议,吞吐量比 Docker Registry 高。
下面是几种场景的对比:
| 场景 | 推荐方案 | 不推荐 |
|---|---|---|
| 单个工作站 | 本地缓存 + 定期清理 | 无限制的缓存积累 |
| 多节点集群 | NFS 共享缓存 | 每个节点各自拉取 |
| 跨地域部署 | 私有仓库 + oras | 直接拉 Docker Hub |
不同架构选不同策略,没有银弹。
7. 实战案例:构建 Conda 环境镜像
我们看个完整的例子。
假设你要拉一个带 Miniconda 的镜像:
singularity pull conda_env.sif docker://continuumio/miniconda3:latest
拉完后看看缓存有没有成功写入:
ls ~/.singularity/cache/blob/
你会看到一堆类似 sha256:abc123... 的文件。
下次再拉同样的镜像,速度就快多了。
如果镜像更新了,加个 --force 强制覆盖:
singularity pull --force conda_env.sif docker://continuumio/miniconda3:latest
注意:--force 只覆盖 .sif,不更新缓存。
要清空旧缓存,得手动删或者用 cache clean。
最佳实践
每次拉取前先检查 singularity cache list 看看镜像是否在缓存里。
避免重复下载。
8. 常见问题与避坑
我遇到过的大坑,列出来给你们参考。
- 缓存目录写满 :解决方案是设置
SINGULARITY_CACHEDIR到另一块盘。 - 缓存损坏 :偶尔会遇到哈希不一致,删掉对应
blob/文件重拉。 - pull 超时 :网络不稳的话加
--retry参数,默认重试三次。 - 镜像未签名 :加
--allow-unsigned临时跳过,但不安全。
最后说个我自己的教训。
我曾经在 GitLab CI 里每次都 pull 镜像,忘了设缓存,结果跑了 3 小时才完成。
加了 SINGULARITY_CACHEDIR 后,构建时间直接缩到 15 分钟。
嗯,这一章就这些。