bigywater.tech
Docker · 87

镜像里的浪费空间:同一个文件在多层里重复

镜像里哪些字节是白占的?dive 之类的工具是怎么算「浪费空间」的?这篇不装 dive,自己用 Python 读 docker save 的每一层,按路径跟踪文件被覆盖和删除的过程,算出浪费量和效率。

26.2 MB
被后面的层覆盖或删除的文件字节(总和 45.5 MB 里)
42.4%
镜像的空间效率 = 有效字节 ÷ 所有层的文件字节总和
87.0 → 34.6 MB
把覆盖、删除和 chmod 合并成一条 RUN 后的镜像体积

逐层统计

实测 · 本机执行输出
  共 7 层(history 里产生层的有 7 条)
  层 0:tar 9.1 MB;ADD alpine-minirootfs-3.20.10-aarch64.tar.gz / # buildkit;大文件:['lib/libcrypto.so.3(4.6MB)'];whiteout:无
  层 1:tar 0.0 MB;RUN /bin/sh -c echo salt-1791642044.49531 # buildkit;大文件:无;whiteout:无
  层 2:tar 10.5 MB;RUN /bin/sh -c mkdir /data && dd if=/dev/urandom of=/data/a.;大文件:['data/a.bin(10.5MB)'];whiteout:无
  层 3:tar 5.2 MB;RUN /bin/sh -c dd if=/dev/urandom of=/data/b.bin bs=1M count;大文件:['data/b.bin(5.2MB)'];whiteout:无
  层 4:tar 10.5 MB;RUN /bin/sh -c dd if=/dev/urandom of=/data/a.bin bs=1M count;大文件:['data/a.bin(10.5MB)'];whiteout:无
  层 5:tar 0.0 MB;RUN /bin/sh -c rm /data/b.bin # buildkit;大文件:无;whiteout:['data/.wh.b.bin']
  层 6:tar 10.5 MB;RUN /bin/sh -c chmod 600 /data/a.bin # buildkit;大文件:['data/a.bin(10.5MB)'];whiteout:无

读 docker save 的每一层 tar(c14 的做法),列出每层加入的大文件和 whiteout。只改权限的 chmod 也会在新层里放一份完整的文件(c20 里同样看到整个文件被复制)。

浪费怎么算

实测 · 本机执行输出
  data/a.bin:层 2 里的 10.5 MB 在层 4 被覆盖
  data/b.bin:层 3 里的 5.2 MB 在层 5 被删除
  data/a.bin:层 4 里的 10.5 MB 在层 6 被覆盖
  所有层里的文件字节总和 45.5 MB;被后面层覆盖或删除的(浪费)共 26.2 MB;有效 19.3 MB;效率 = 有效 ÷ 总和 = 42.4%
  容器里实际可见的 /data 下的文件:-rw------- 10485760 a.bin

按路径顺序跟踪:同一路径在后面的层里再次出现,前一个版本就被覆盖;出现 whiteout,则被删除。被覆盖或删除版本的大小之和就是浪费。这是我自己的简化算法(只统计普通文件的大小,没有处理硬链接、目录权限等),dive 的实际算法和输出我没有对照。

合并 RUN

实测 · 本机执行输出
  镜像 Size:浪费版 87.0 MB(层数 7),合并版 34.6 MB;最终容器里看到的文件相同:-rw------- 10485760 a.bin

把生成、覆盖、删除、改权限放进同一条 RUN,最终的层里只有最后的状态,镜像体积从 87.0 MB 降到 34.6 MB(其中 alpine 基础层 13.6 MB 加上 10 MiB 文件的压缩与解包两份,见 c18)。

分步演示

镜像浪费空间
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Docker Desktop(Engine 29.8.2,容器跑在 Docker Desktop 自带的 Linux 虚拟机里,内核与宿主机的 macOS 不同);镜像为 linux/arm64;需要看虚拟机内部时用特权容器 + nsenter;计时用 time / date +%s%N,数字每次运行会有小幅抖动。分析脚本是我自己写的(tarfile 读取 docker save 的 OCI 布局,逐层统计);没有安装或运行 dive;文件用 /dev/urandom 生成;镜像体积取 docker image inspect 的 Size;实验后已删除镜像
    • 没有验证:dive 本身的输出与算法、硬链接和稀疏文件的统计、目录与权限变化的统计、压缩后传输体积的浪费、同一路径被不同类型(文件改成目录)替换的情形
    京ICP备2026067502号-1京公网安备11010502063487号