镜像里哪些字节是白占的?dive 之类的工具是怎么算「浪费空间」的?这篇不装 dive,自己用 Python 读 docker save 的每一层,按路径跟踪文件被覆盖和删除的过程,算出浪费量和效率。
共 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 的实际算法和输出我没有对照。
镜像 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)。