Docker 镜像的多层结构是如何实现的?
2026/9/7 19:27:19 网站建设 项目流程

一、什么是镜像的多层结构?

1.1 直观理解

# Dockerfile FROM ubuntu:20.04 # 层1: 基础系统 RUN apt-get update && \ # 层2: 安装软件 apt-get install -y python3 COPY app.py /app/ # 层3: 复制文件 CMD ["python3", "/app/app.py"] # 层4: 元数据

每一行指令都会创建一个新层

+---------------------+ | 容器层 (可写) | ← 容器启动时添加 +---------------------+ | 层4: CMD命令 | ← 镜像层4(元数据) +---------------------+ | 层3: COPY app.py | ← 镜像层3(文件) +---------------------+ | 层2: apt-get install| ← 镜像层2(修改) +---------------------+ | 层1: ubuntu:20.04 | ← 基础镜像层 +---------------------+

1.2 生活中的类比

想象一本漫画书

  • 每层是一张透明的胶片:每张胶片上画了一部分内容
  • 叠加在一起:所有胶片叠起来就是完整的画面
  • 可以复用:多个漫画可以共享相同的背景胶片
  • 修改只影响上层:如果要改某个细节,只需要覆盖新的胶片在上层

二、多层结构的核心技术

2.1 UnionFS(联合文件系统)

UnionFS 是 Docker 镜像分层的基础,它允许将多个目录联合挂载成一个虚拟文件系统。

# UnionFS 的工作原理# 目录A: /var/lib/docker/overlay2/layer1# 目录B: /var/lib/docker/overlay2/layer2# 目录C: /var/lib/docker/overlay2/layer3# 通过 UnionFS 挂载成一个目录mount-toverlay overlay\-olowerdir=/layer3:/layer2:/layer1\/merged# 在 merged 目录看到的是三个层的合并视图

2.2 OverlayFS(Docker 现在使用的存储驱动)

Docker 主要使用OverlayFS实现多层结构:

# OverlayFS 结构# lowerdir: 只读层(镜像层)# upperdir: 可写层(容器层)# workdir: 工作目录# merged: 最终视图mount-toverlay overlay\-olowerdir=/lower1:/lower2:/lower3,\upperdir=/upper,\workdir=/work\/merged

实际例子

# 查看容器的文件系统层dockerinspect<容器>|grep-A10"GraphDriver"{"GraphDriver":{"Data":{"LowerDir":"/var/lib/docker/overlay2/123.../diff","MergedDir":"/var/lib/docker/overlay2/456.../merged","UpperDir":"/var/lib/docker/overlay2/789.../diff","WorkDir":"/var/lib/docker/overlay2/abc.../work"},"Name":"overlay2"}}

三、多层结构的实现原理

3.1 写时复制(Copy-on-Write)

这是 Docker 最巧妙的设计之一:

# 1. 镜像层都是只读的# 2. 容器启动时在最上层加一个可写层# 3. 读文件:从上往下找,找到第一个就返回# 4. 写文件:复制到可写层再修改# 读文件示例cat/etc/nginx/nginx.conf# 查找顺序: 可写层 → 层4 → 层3 → 层2 → 层1# 修改文件echo"new config">/etc/nginx/nginx.conf# 1. 在可写层复制一份 nginx.conf# 2. 修改可写层的副本# 3. 以后再读,从可写层返回

3.2 层的存储结构

# Docker 存储目录/var/lib/docker/ ├── overlay2/ │ ├── l/# 符号链接│ ├──123.../# 层ID│ │ ├── diff/# 实际文件内容│ │ ├──link# 短链接名│ │ ├── lower# 下层信息│ │ └── work/# 工作目录│ ├──456.../ │ └──789.../ ├── image/ │ └── overlay2/ │ ├── repositories.json# 镜像仓库信息│ ├── layerdb/# 层数据库│ └── distribution/# 分发信息

3.3 层的元数据

# 查看镜像的层信息dockerhistorynginx:latest IMAGE CREATED CREATED BY SIZE abc123def4562hours ago /bin/sh-c#(nop) CMD ["nginx" "-g" "daemon… 0Bdef456abc1232hours ago /bin/sh-c#(nop) STOPSIGNAL SIGQUIT 0B789abc123def2hours ago /bin/sh-c#(nop) EXPOSE 80 0B456def789abc2hours ago /bin/sh-c#(nop) ENTRYPOINT ["/docker-entr… 0B123abc456def2weeks ago /bin/sh-c#(nop) COPY file:09... in / 1.2kB............

每层包含

  • 实际文件:这一层添加/修改的文件
  • 元数据:指令信息、环境变量等
  • 层ID:唯一标识(基于内容哈希)

四、多层结构的优势

4.1 存储效率

# 多个镜像共享基础层 FROM ubuntu:20.04 # 层1(120MB) RUN apt-get install -y python3 # 层2(50MB) FROM ubuntu:20.04 # 复用相同的层1 RUN apt-get install -y nodejs # 不同层2(40MB)

效果

  • 10 个基于 Ubuntu 的镜像,只存一份 Ubuntu 层
  • 磁盘空间节省 90% 以上

4.2 传输效率

# 推送镜像时只推送变化的层dockerpush myapp:1.0# 输出:# Layer 1: 已存在,跳过# Layer 2: 已存在,跳过# Layer 3: 推送新层# 拉取镜像dockerpull myapp:1.0# 只下载本地没有的层

4.3 构建缓存

# 利用层缓存加速构建 FROM node:16 COPY package*.json ./ # 如果 package.json 没变,这层缓存 RUN npm install # 直接使用缓存的 node_modules COPY . . # 只有这层重新构建

缓存命中效果

  • 第二次构建只需几秒(而不是几分钟)
  • CI/CD 流程大幅加速

五、层的存储和分发

5.1 层的存储格式

# 每一层压缩后是一个 tar 包# /var/lib/docker/overlay2/123.../# └── diff/# ├── etc/# ├── usr/# └── var/# 导出镜像查看层dockersave myapp:1.0-omyapp.tartar-xvfmyapp.tar# 看到多个 layer.tar 文件,每个对应一层

5.2 层的分发过程

构建镜像

计算每层哈希

层是否存在?

复用现有层

压缩新层

上传到仓库

其他节点拉取

本地是否有?

复用本地层

下载缺失层


六、实际操作验证

6.1 查看镜像层

# 查看镜像分层dockerinspect nginx:latest|jq'.[0].RootFS'{"Type":"layers","Layers":["sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f","sha256:9df5e5c6c8f0bdfb0a7264f69d368af23c92b059a8ae82a9a622ac19b26f5a1a",...]}# 查看层大小dockerhistory--no-trunc nginx:latest

6.2 查看 OverlayFS 挂载

# 查看容器的 Overlay 挂载mount|grepoverlay# 查看各层内容ls/var/lib/docker/overlay2/l/ls/var/lib/docker/overlay2/<层ID>/diff/

6.3 验证写时复制

# 运行容器dockerrun-d--nametestnginx# 找到容器的 upper 目录UPPER=$(dockerinspecttest|jq-r'.[0].GraphDriver.Data.UpperDir')# 查看目录内容ls$UPPER# 修改容器内文件dockerexectestbash-c"echo test > /usr/share/nginx/html/test.html"# 查看 upper 目录(文件出现在这里)ls$UPPER/usr/share/nginx/html/

七、最佳实践

7.1 优化层数

# ❌ 太多层 FROM ubuntu RUN apt-get update RUN apt-get install -y curl RUN apt-get install -y nginx RUN rm -rf /var/lib/apt/lists/* # ✅ 合并 RUN FROM ubuntu RUN apt-get update && \ apt-get install -y curl nginx && \ rm -rf /var/lib/apt/lists/*

7.2 利用缓存

# 把变化少的放前面 FROM python:3.9 COPY requirements.txt . # 少变 RUN pip install -r requirements.txt # 少变 COPY . . # 多变放最后

7.3 清理不必要的文件

# 安装后清理缓存 RUN apt-get update && \ apt-get install -y curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*

7.4 多阶段构建

# 构建阶段(大) FROM maven:3.8 AS builder COPY . . RUN mvn package # 运行阶段(小) FROM openjdk:11-jre-slim COPY --from=builder target/*.jar app.jar # 最终镜像只有最后一层

八、常见问题

Q1:层数太多会影响性能吗?

# 理论上有影响(文件查找要遍历所有层)# 但实际影响很小(内核有缓存)# 建议:合理合并层,但不必过度优化

Q2:删除文件为什么镜像大小不变?

FROM ubuntu RUN apt-get install -y big-package # 层1:添加100MB RUN rm -rf /big-package # 层2:标记删除 # 最终镜像还是 100MB,因为层1的文件还在 # 需要在同一层删除

Q3:如何查看每层的大小?

# 使用 dive 工具dive myapp:latest# 或使用 docker historydockerhistory--no-trunc myapp:latest

九、总结

概念说明
多层结构镜像由多个只读层叠加而成
UnionFS联合文件系统,合并多个目录
OverlayFSDocker 使用的存储驱动
写时复制修改文件时复制到可写层
层缓存复用已有层加速构建
层共享多个镜像共享相同基础层

一句话理解
Docker 镜像的多层结构通过UnionFS/OverlayFS实现,每层只记录相对于上一层的差异,利用写时复制技术,实现了高效的存储、传输和构建缓存

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询