镜像压缩:离线、零依赖的 Docker/OCI 镜像层合并工具 oci-squash

166 阅读6分钟

项目地址:GitHub - lyon-v/oci-squash

背景与痛点

容器镜像在持续迭代中会快速膨胀:

  • 基于 apt install、构建产物、临时文件的层不断堆叠,最终镜像体积变大。
  • 多层带来重复元数据与存储负担,拉取/推送耗时增加,CI/CD 变慢。
  • 在离线/半离线环境,需要在“只有镜像 tar 包”的前提下进行瘦身与整合。

现有工具常见问题:

  • 依赖 Docker 守护进程或第三方库,不适合离线环境。
  • 对 OCI/Docker 不同布局兼容性不一,或导出后不能稳定 docker load
  • 白化文件(whiteout)与不透明目录(opaque)处理不完善,可能导致文件系统状态不一致。

方案

OCI-Squash 通过“直接操作镜像 tar 包”的方式,把多层合并为更少的层,既减少体积,也简化历史记录;全程使用 Python 标准库,无需 Docker 守护进程,适用于离线与安全敏感场景。

功能亮点

  • 零依赖与离线友好:仅用 Python 标准库工作于镜像 tar 包
  • Docker 与 OCI 双支持:自动识别 index.jsonmanifest.json
  • 稳定输出:始终生成 Docker 风格层结构,保证 docker load 可靠
  • 元数据正确性:重算 diff_ids、保留/更新 config、history
  • 白化处理:支持 whiteout 与 opaque 目录语义
  • 灵活标记:支持设定新镜像 tag、commit message
  • 体积报告:比较原/新 tar 包大小与百分比变化
  • 简单易用:单命令完成合并导出

工作原理(简述)

  • 解包镜像 tar,自动识别格式(Docker/OCI)
  • 读取 config/manifest,重建完整层序列(含虚拟空层)
  • 选定需要合并的层(支持按“自顶向下”数量或指定层 ID)
  • 进行文件系统重组,正确处理 whiteout/opaque 语义
  • 写出新的 Docker 风格层结构与 manifest.jsonrepositories
  • 重新计算 diff_ids,更新 config、rootfs、history
  • 打包成可被 docker load 直接导入的 tar

快速开始

安装:

pip install oci-squash
oci-squash -h

一条命令完成合并:

# 镜像导出为压缩包
docker save -o source.tar lyonv/ubuntu-dev:latest

oci-squash -f 8 -t myrepo/myimage:squashed -m "squashed" -o squashed.tar source.tar

导入 Docker 并查看历史:

docker load -i squashed.tar
docker history myrepo/myimage:squashed

端到端演示

以下为对 lyonv/ubuntu-dev:latest 的一次真实合并过程。

  1. 查看原始历史:
docker history lyonv/ubuntu-dev:latest
IMAGE          CREATED         CREATED BY                                      SIZE      COMMENT
b23aed85a512   10 hours ago    RUN /bin/sh -c rm -f /bigfile # buildkit        0B        buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c dd if=/dev/zero of=/bigfile b…   105MB     buildkit.dockerfile.v0
<missing>      10 hours ago    WORKDIR /app && USER appuser &&     echo Ver…   0B        buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c apt-get clean &&     rm -rf /…   4.1kB     buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c mkdir -p /app/{conf,logs} /va…   0B        buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c apt-get install -y --no-insta…   2.83MB    buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c apt-get install -y --no-insta…   78.1MB    buildkit.dockerfile.v0
<missing>      10 hours ago    RUN /bin/sh -c ln -snf /usr/share/zoneinfo/A…   58.4MB    buildkit.dockerfile.v0
<missing>      11 months ago   sh -c sleep 36000                               4.1kB     build img
<missing>      15 months ago   /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      15 months ago   /bin/sh -c #(nop) ADD file:e7cff353f027ecf0a…   79.5MB    
<missing>      15 months ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      15 months ago   /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      15 months ago   /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      15 months ago   /bin/sh -c #(nop)  ARG RELEASE                  0B        
  1. 保存、合并并导入:
docker save -o source.tar  lyonv/ubuntu-dev:latest
oci-squash -f 8 -t lyonv/ubuntu-dev:squashed -m squashed -o squashed.tar source.tar
docker load -i squashed.tar

示例输出(节选):

2025-08-30 17:18:56,654 cli.py:106        INFO  Extracting tar: source.tar
2025-08-30 17:18:56,859 cli.py:109        INFO  Detected format: oci
2025-08-30 17:18:56,859 cli.py:116        INFO  Attempting to squash last 8 layers
2025-08-30 17:18:57,965 cli.py:162        INFO  Exporting to: squashed.tar
2025-08-30 17:18:58,393 cli.py:164        INFO  Done. New image id: sha256:1ae6a77f5b834850e9a9b2c4e3a6f8f715efb8c46af92635a49640c53e3db347
2025-08-30 17:18:58,393 cli.py:171        INFO  Original tar size: 299.76 MB
2025-08-30 17:18:58,393 cli.py:172        INFO  Squashed tar size: 143.15 MB
2025-08-30 17:18:58,393 cli.py:175        INFO  Tar size decreased by 52.24 %
2025-08-30 17:18:58,484 cli.py:186        INFO  Squashed image Done.

Loaded image: lyonv/ubuntu-dev:squashed
  1. 查看合并后的历史:
docker history lyonv/ubuntu-dev:squashed
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
927444bd87b4   31 seconds ago                                                   79.9MB    squashed
<missing>      11 months ago    sh -c sleep 36000                               4.1kB     build img
<missing>      15 months ago    /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B        
<missing>      15 months ago    /bin/sh -c #(nop) ADD file:e7cff353f027ecf0a…   79.5MB    
<missing>      15 months ago    /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      15 months ago    /bin/sh -c #(nop)  LABEL org.opencontainers.…   0B        
<missing>      15 months ago    /bin/sh -c #(nop)  ARG LAUNCHPAD_BUILD_ARCH     0B        
<missing>      15 months ago    /bin/sh -c #(nop)  ARG RELEASE                  0B        

结果:tar 体积从 299.76 MB 降至 143.15 MB,降低 52.24%。

适用场景

  • CI/CD 合并层以减少镜像体积与层数,缩短推拉时间
  • 离线/内网环境,只能操作镜像 tar 包的瘦身与归档
  • 基础镜像清理:将中间构建层(下载、解压、临时文件)合并掉
  • 制作交付镜像:减少层历史暴露,便于合规与分发

与其他方案对比

  • 相比依赖 Docker 守护进程的工具:OCI-Squash 完全离线、零依赖
  • 相比仅支持 Docker 布局的工具:同时支持 Docker 与 OCI,输出稳定可 docker load
  • 注重元数据正确性:重算 diff_ids、保留历史语义、处理 whiteout/opaque

限制与注意事项

  • 需要足够磁盘空间用于解包与重打包
  • 当前以 Linux 镜像为主;特殊平台/层格式请先测试
  • 不进行安全扫描/合规检查(可与其他工具配合)
  • 合并层不会“神奇压缩”所有文件,更多是消除中间层冗余与历史

路线图

  • 更丰富的进度与统计输出
  • 多架构/多平台镜像体验优化
  • 更细粒度的层选择与排除策略
  • 更多安装方式支持(conda、homebrew 等)

开源与参与

如果你也被“镜像层越堆越厚、部署越来越慢”困扰,不妨试试 OCI-Squash,用一次简单的离线合并,换来持续的轻量与高效。