如何借助 Overlay2 机制理解镜像分层共享的底层设计

作者:袖梨 2026-07-12
Overlay2是Docker镜像分层共享的物理基础,通过lowerdir(多个只读镜像层)、upperdir(容器可写层)和workdir(内部辅助目录)联合挂载为merged统一视图,并依托diff_id与cache-id哈希映射实现跨镜像层复用。

Overlay2 是 Docker 镜像分层共享的物理基础,它把“镜像由多层组成”这个抽象概念,落地为 Linux 内核支持的文件系统挂载行为。理解它,就等于看清了镜像为什么能复用、为什么拉取快、为什么构建可缓存。

Overlay2 的三层结构:lowerdir、upperdir、workdir

每个运行中的容器背后,都对应一个 Overlay2 挂载点,由三个关键目录协同工作:

  • lowerdir:多个以冒号分隔的只读目录,对应镜像的所有只读层(从基础镜像到最终构建层),按顺序叠加;这些目录实际存放在 /var/lib/docker/overlay2/<hash>/diff
  • upperdir:单个可写目录,存放容器运行时产生的所有新增、修改或删除的文件;该目录独立于镜像层,专属于当前容器
  • workdir:Overlay2 内部必需的辅助目录,用于跟踪上层变更(如 whiteout 文件),不可直接操作

三者合并后,通过 merged 目录对外提供统一视图——这就是容器看到的根文件系统。

镜像层如何被复用:哈希寻址 + 元数据映射

Overlay2 不靠路径名识别层,而是依赖内容哈希(diff_id)和缓存 ID(cache-id)实现跨镜像共享:

  • 相同 Dockerfile 指令(如 RUN apt-get install nginx)生成完全一致的文件变更集 → 计算出相同 diff_id
  • Docker 将该 diff_id 映射为唯一的 cache-id,并存入 /var/lib/docker/image/overlay2/layerdb/sha256/<cache-id>
  • 只要本地存在该 cache-id 对应的 /var/lib/docker/overlay2/<hash> 目录,无论多少个镜像引用它,都复用同一份物理数据

例如两个不同应用镜像都基于 alpine:3.18,它们的 base layer 在 lowerdir 中指向同一个 /var/lib/docker/overlay2/abc123.../diff,不重复存储。

构建与运行时的分层行为差异

分层不是静态快照,而是随生命周期动态参与:

  • 构建阶段:每条 RUN/COPY 指令执行后,Docker 将临时容器的 upperdir 打包为新层,生成 diff_id 并存入 layerdb;若该层已存在,则跳过执行,直接复用
  • 运行阶段:启动容器时,Docker 把镜像所有只读层(lowerdir)+ 新建空 upperdir + workdir 一起挂载到 merged;文件读取从上往下查,写入触发 Copy-on-Write —— 修改文件先复制到 upperdir 再编辑

这意味着:镜像层永远只读、不可变;容器层仅记录增量;底层未改动,上层就不会冗余复制。

验证分层共享的实操线索

不需要深入代码,几条命令就能印证设计:

  • 查看某镜像使用的层:docker image inspect nginx | jq '.[0].RootFS.Layers' → 输出一串 sha256:xxx,即各层 diff_id
  • 定位某层物理路径:cat /var/lib/docker/image/overlay2/layerdb/sha256/<diff_id>/cache-id → 得到 cache-id,再进 /var/lib/docker/overlay2/<cache-id>/diff 看真实文件
  • 观察两个镜像是否共用某层:find /var/lib/docker/overlay2 -name "diff" -path "*/<cache-id>/*" | wc -l → 若返回 1,说明该层只被一个镜像引用;若多个镜像共用,此 cache-id 会被多个镜像的元数据指向

这种基于内容哈希与内核挂载的组合,让分层不只是逻辑划分,而是真正可验证、可复用、可隔离的存储事实。

相关文章

精彩推荐