欢迎光临
我们一直在努力

大型 Docker 开发镜像磁盘排查:containerd、缓存与容量规划

大型 Docker 开发镜像磁盘排查:containerd、缓存与容量规划

摘要:给出大型开发镜像的磁盘排查顺序,区分 containerd 镜像数据、snapshot、BuildKit cache、导出归档和源码构建输出。

在这里插入图片描述

文章目录

  • 大型 Docker 开发镜像磁盘排查:containerd、缓存与容量规划
    • 1. 第一层先看文件系统
    • 2. 第二层看宿主机真实目录
    • 3. 第三层看 Docker 逻辑对象
    • 4. 构建过大镜像以后单独看 BuildKit cache
    • 5. 为什么 builder prune 不会把 50 GiB 镜像变成 27 GiB
    • 6. inactive 不等于“不需要”
    • 7. image prune 和 system prune 的范围不同
    • 8. 不要直接删除 `/var/lib/containerd`
    • 9. 导出归档经常是被忽略的一大块
    • 10. 源码 build 目录也可能很大
    • 11. data-root 迁走后为什么根分区还可能增长
    • 12. 一个 80 GiB 根分区为什么很容易不够
    • 13. 更实用的容量规划方式
    • 14. 推荐的排查顺序
    • 15. 最后再决定处理策略
      • 参考资料

当一个二十多 GiB 的开发镜像运行在 Docker Engine 29+ 新主机上时,磁盘问题往往比容器运行问题更早出现。

例如根分区只有约 80 GiB,却同时存在:

大型开发镜像
containerd content
unpacked snapshot
BuildKit cache
源码 build 输出
镜像导出归档

这时如果只看到:

/dev/sdaX 90%

然后立刻运行 docker system prune -a,很容易删掉重新构建代价很高的开发镜像。

更好的方式是先回答:

空间到底被哪一类数据占用了?

1. 第一层先看文件系统

df -h /

它回答的是:

整个根文件系统还剩多少空间?

它不会告诉你 Docker 哪个对象在占空间。

如果镜像归档放在其他分区,还要分别看:

df -h /path/to/archive

大型镜像导出时,Docker 数据和归档文件可能位于不同文件系统,不能只检查一个路径。

2. 第二层看宿主机真实目录

containerd image store 环境下,至少检查:

sudo du -sh /var/lib/containerd /var/lib/docker

这回答的是宿主机目录实际占用。

Docker 当前文档说明,containerd image store 下的 image contents 和 container snapshots 默认在:

/var/lib/containerd

所以新 Docker 环境中只看 /var/lib/docker 已经不够。

3. 第三层看 Docker 逻辑对象

docker system df

需要更详细时:

docker system df -v

它让你从 Docker 对象视角看:

Images
Containers
Local Volumes
Build Cache

du 和 docker system df 不要求数字完全一致。

一个是文件系统目录视角,一个是 Docker 对象和共享关系视角。

4. 构建过大镜像以后单独看 BuildKit cache

如果刚执行过大型 docker buildx build,再看:

docker buildx du

大型 SDK 构建经常产生很大的 BuildKit cache。

它和“最终镜像本身”不是同一个对象:

Build Cache
构建阶段复用

Final Image
docker run 使用

如果 cache 明确可回收,可以:

docker builder prune

这通常是大型镜像构建完成后的第一类清理对象。

5. 为什么 builder prune 不会把 50 GiB 镜像变成 27 GiB

如果当前 containerd image store 需要同时保存:

image content
unpacked snapshot

那么它们属于最终镜像运行体系。

执行:

docker builder prune

只会处理 BuildKit cache。

所以即使清掉 20 GiB 构建缓存,最终镜像相关的几十 GiB 仍然可能存在。

这不是 prune 失败,而是你清理的是不同对象。

6. inactive 不等于“不需要”

大型开发镜像有一个危险特点:平时不一定有常驻容器。

所以 Docker 可能显示:

ACTIVE 0

但这只说明现在没有容器使用它,不代表镜像应该删除。

一个 20+ GiB 开发镜像可能构建几个小时,甚至需要重新复制大型 SDK。

因此看到 inactive 后不要直接:

docker system prune -a

更合理的是先确定:

这个镜像是否是正式开发环境?
是否已经有可靠离线备份?
是否能快速重新构建?

7. image prune 和 system prune 的范围不同

较温和:

docker image prune

更激进:

docker image prune -a

范围更广:

docker system prune -a

越往后,自动判断“未使用”的对象范围越大。

对于普通 CI 临时环境,这可能很方便;对于手工制作的大型 SDK 镜像,应该更保守。

建议先:

docker system df -v

确认对象,再做针对性清理。

8. 不要直接删除 /var/lib/containerd

即使:

sudo du -sh /var/lib/containerd

显示几十 GiB,也不要执行:

sudo rm -rf /var/lib/containerd/*

containerd 的 content、metadata 和 snapshot 之间存在引用关系。

绕过 Docker/containerd 管理层手工删除,可能导致:

镜像 metadata 仍然存在
底层 blob 已损坏
snapshot 引用不一致
容器无法启动

底层目录不是普通“缓存目录”。

清理应优先通过 Docker CLI 或明确的官方迁移流程完成。

9. 导出归档经常是被忽略的一大块

例如你为了迁移镜像,在:

~/images/

放了:

cross-dev.tar
cross-dev.tar.zst

如果原始 TAR 没删,可能同时多占:

20+ GiB TAR
+
10+ GiB 压缩归档

它们完全不属于 Docker,因此:

docker system df

不会显示。

这就是为什么排查不能只看 Docker CLI。

可以检查:

du -sh ~/images
find ~/images -maxdepth 1 -type f -size +1G -ls

大型镜像场景里,“导出文件忘了删”非常常见。

10. 源码 build 目录也可能很大

如果开发容器使用 bind mount:

宿主机源码 -> /workspace

那么容器生成的:

build/
*.o
*.a
*.so
打包产物

都可能实际写在宿主机项目目录。

这些也不属于 Docker image store。

所以完整排查还应该看:

du -sh ~/work/demo-app
du -sh ~/work/demo-app/build

不要看到“是容器编译产生的”就认为一定在 /var/lib/docker。

11. data-root 迁走后为什么根分区还可能增长

Docker 常见配置:

{
"data-root": "/mnt/docker-data"
}

但 Docker 当前官方文档明确说明:在 containerd image store 下,data-root 不会自动改变 containerd image contents 和 snapshots 的位置。

于是可能变成:

/mnt/docker-data Docker 其他数据
/var/lib/containerd 镜像内容和 snapshot

如果你的目标是彻底把大型镜像数据迁出根分区,就必须把 containerd 数据位置也纳入设计,而不是只修改 Docker data-root。

这类存储迁移涉及 daemon 停止、数据复制和配置一致性,不建议在磁盘快满时临时手工移动目录;应按 Docker/containerd 官方数据目录配置方式操作。

12. 一个 80 GiB 根分区为什么很容易不够

假设:

系统自身 15 GiB
开发镜像相关数据 45~55 GiB
BuildKit cache 10 GiB
源码和 build 5 GiB

总量已经可能达到:

75~85 GiB

如果这时还在根分区导出:

cross-dev.tar.zst 10+ GiB

很容易直接失败。

因此对超大型开发镜像,磁盘规划应该比“镜像 SIZE + 5 GiB”保守得多。

13. 更实用的容量规划方式

可以按几类数据分别估算:

A. 操作系统和普通工具
B. containerd image content
C. unpacked snapshots
D. BuildKit cache 峰值
E. 源码和构建产物
F. 镜像导出归档

其中 F 最适合放到另一块盘或网络存储,因为它只在迁移时需要。

如果这台机器长期维护大 SDK 镜像,更合理的硬件布局是:

系统根分区
放 OS 和普通工具

大容量数据分区
放 Docker/containerd 数据
或至少放离线镜像归档

14. 推荐的排查顺序

以后遇到 Docker 把磁盘吃满,可以按下面顺序:

#mermaid-svg-MZlcQ853bmcUO81Q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MZlcQ853bmcUO81Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MZlcQ853bmcUO81Q .error-icon{fill:#552222;}#mermaid-svg-MZlcQ853bmcUO81Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MZlcQ853bmcUO81Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MZlcQ853bmcUO81Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MZlcQ853bmcUO81Q .marker.cross{stroke:#333333;}#mermaid-svg-MZlcQ853bmcUO81Q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MZlcQ853bmcUO81Q p{margin:0;}#mermaid-svg-MZlcQ853bmcUO81Q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-MZlcQ853bmcUO81Q .cluster-label text{fill:#333;}#mermaid-svg-MZlcQ853bmcUO81Q .cluster-label span{color:#333;}#mermaid-svg-MZlcQ853bmcUO81Q .cluster-label span p{background-color:transparent;}#mermaid-svg-MZlcQ853bmcUO81Q .label text,#mermaid-svg-MZlcQ853bmcUO81Q span{fill:#333;color:#333;}#mermaid-svg-MZlcQ853bmcUO81Q .node rect,#mermaid-svg-MZlcQ853bmcUO81Q .node circle,#mermaid-svg-MZlcQ853bmcUO81Q .node ellipse,#mermaid-svg-MZlcQ853bmcUO81Q .node polygon,#mermaid-svg-MZlcQ853bmcUO81Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MZlcQ853bmcUO81Q .rough-node .label text,#mermaid-svg-MZlcQ853bmcUO81Q .node .label text,#mermaid-svg-MZlcQ853bmcUO81Q .image-shape .label,#mermaid-svg-MZlcQ853bmcUO81Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-MZlcQ853bmcUO81Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MZlcQ853bmcUO81Q .rough-node .label,#mermaid-svg-MZlcQ853bmcUO81Q .node .label,#mermaid-svg-MZlcQ853bmcUO81Q .image-shape .label,#mermaid-svg-MZlcQ853bmcUO81Q .icon-shape .label{text-align:center;}#mermaid-svg-MZlcQ853bmcUO81Q .node.clickable{cursor:pointer;}#mermaid-svg-MZlcQ853bmcUO81Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MZlcQ853bmcUO81Q .arrowheadPath{fill:#333333;}#mermaid-svg-MZlcQ853bmcUO81Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MZlcQ853bmcUO81Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MZlcQ853bmcUO81Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MZlcQ853bmcUO81Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MZlcQ853bmcUO81Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MZlcQ853bmcUO81Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MZlcQ853bmcUO81Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MZlcQ853bmcUO81Q .cluster text{fill:#333;}#mermaid-svg-MZlcQ853bmcUO81Q .cluster span{color:#333;}#mermaid-svg-MZlcQ853bmcUO81Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MZlcQ853bmcUO81Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MZlcQ853bmcUO81Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-MZlcQ853bmcUO81Q .icon-shape,#mermaid-svg-MZlcQ853bmcUO81Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MZlcQ853bmcUO81Q .icon-shape p,#mermaid-svg-MZlcQ853bmcUO81Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MZlcQ853bmcUO81Q .icon-shape .label rect,#mermaid-svg-MZlcQ853bmcUO81Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MZlcQ853bmcUO81Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MZlcQ853bmcUO81Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MZlcQ853bmcUO81Q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

df -h:文件系统还剩多少

du:/var/lib/containerd 与 /var/lib/docker

docker system df -v:镜像/容器/volume

docker buildx du:BuildKit cache

检查导出归档和源码 build

针对确认对象清理或迁移

对应命令:

df -h /
sudo du -sh /var/lib/containerd /var/lib/docker
docker system df -v
docker buildx du
du -sh ~/images ~/work/demo-app/build 2>/dev/null

这五步基本能把“大空间去哪了”分成明确类别。

15. 最后再决定处理策略

如果主要是 BuildKit cache:

docker builder prune

如果是明确不需要的旧镜像:

docker image rm <image>

如果是离线归档:

转移到移动硬盘 / NAS
或确认备份后删除本地副本

如果是 containerd image store 的最终镜像本身:

它不是“垃圾缓存”

此时真正的解决方式可能是扩大数据分区、迁移 containerd 数据位置,或者重新评估是否必须长期保留如此巨大的完整 SDK 镜像。

关键原则只有一句:

先确认数据身份,再清理;不要用最激进的 prune 去替代诊断。

参考资料

  • Docker Docs:containerd image store with Docker Engine — https://docs.docker.com/engine/storage/containerd/
  • Docker Docs:Docker daemon data directory — https://docs.docker.com/engine/daemon/
  • Docker Docs:Storage drivers — https://docs.docker.com/engine/storage/drivers/
赞(0)
未经允许不得转载:171主机测评 » 大型 Docker 开发镜像磁盘排查:containerd、缓存与容量规划
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址