镜像拉取与缓存:解锁 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 哈希命名,你不用担心重复。

我画了张图,你看了就明白。

Singularity 镜像拉取与缓存流程 拉取请求 检查本地缓存 缓存命中 → 直接使用 缓存未命中 → 远程拉取 远程仓库 本地缓存存储 说明 缓存位置:~/.singularity/cache

这张图展示了完整的 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 分钟。

嗯,这一章就这些。