基于本文回答
0
评论

Docker 镜像是如何分层(Layering)的?什么是写时复制(Copy-on-Write)机制?

在 Docker 的世界中,镜像分层(Layering)写时复制(Copy-on-Write, 简称 CoW)是两个最核心的技术基石。它们不仅让 Docker 变得极其轻量和快速,还极大地节省了磁盘空间和网络带宽。

下面我们用通俗易懂的语言和图解来深入拆解这两个概念。


一、 Docker 镜像是如何分层(Layering)的?

1. 什么是分层?

Docker 镜像并不是一个单一的大文件,而是由一组只读的层(Layers)叠加而成的。
每一层都代表了 Dockerfile 中的一条指令(如 FROM, RUN, COPY 等)。

2. 联合文件系统(UnionFS)

Docker 利用 联合文件系统(Union File System) 技术,将这些不同的层“挂载”并合并成一个统一的视图。对用户来说,你看不到底层的多个目录,只能看到一个完整的文件系统(比如一个干净的 Ubuntu 系统加上你的应用)。

3. 镜像分层的结构(以一个简单的 Node.js 应用为例)

假设你的 Dockerfile 如下:

plaintext
FROM ubuntu:20.04             # Layer 1: 基础操作系统
RUN apt-get update && apt-get install -y nodejs # Layer 2: 安装 Node.js
COPY . /app                   # Layer 3: 复制你的代码
CMD ["node", "/app/index.js"] # Layer 4: 设定启动命令

在 Docker 内部,它的结构就像建楼房:

plaintext
┌──────────────────────────────────────────────────┐
│  [读写层] 容器层 (Container Layer) - 运行容器时创建  │  <-- 唯一可写的地方
├──────────────────────────────────────────────────┤
│  [只读层] Layer 4: CMD ["node", "/app/index.js"]  │  ^
├──────────────────────────────────────────────────┤  |
│  [只读层] Layer 3: COPY . /app                   │  | 镜像层 (Image Layers)
├──────────────────────────────────────────────────┤  | (一经构建,终身只读)
│  [只读层] Layer 2: RUN apt-get install...        │  |
├──────────────────────────────────────────────────┤  |
│  [只读层] Layer 1: FROM ubuntu:20.04             │  v
└──────────────────────────────────────────────────┘

4. 分层的好处:

  • 共享资源(节省空间):如果你有 10 个镜像都是基于 ubuntu:20.04 构建的,那么在你的宿主机上,ubuntu:20.04 这一层只会被下载和存储一次
  • 快速分发(节省带宽):当你推送(Push)或拉取(Pull)镜像时,只有发生变化的层需要传输。如果本地已经有了基础层,Docker 会直接跳过下载。
  • 缓存构建:在重新构建镜像时,如果某一步之前的指令没有变化,Docker 会直接复用缓存的层,极大地加快构建速度。

二、 什么是写时复制(Copy-on-Write, CoW)机制?

既然镜像的每一层都是只读的,那么问题来了:当我们运行容器并在里面修改文件、写日志或保存数据时,数据写到哪里去了?

这就是 写时复制(Copy-on-Write) 机制发挥作用的地方。

1. 核心原理

当一个容器启动时,Docker 会在最顶部的只读镜像层之上,添加一个薄薄的可写层(Writable Layer),通常被称为“容器层”(Container Layer)

  • 读文件:如果容器需要读取一个文件,它会从上往下在各个镜像层中寻找。一旦找到,就直接读取。
  • 写/修改文件(CoW 触发):如果容器需要修改一个已存在的文件(这个文件存在于底层的只读镜像层中):
    1. Docker 会在底层的只读层中找到该文件。
    2. 将该文件复制(Copy)一份到最顶部的可写层
    3. 容器在可写层中对该文件的副本进行修改(Write)
    4. 底层的原始文件保持不变,但由于联合文件系统的“遮罩”效应,容器只能看到顶部可写层中修改后的新版本文件。
  • 删除文件:如果容器要删除一个文件,它会在可写层创建一个“遮罩文件”(Whiteout),在用户看来这个文件被删除了,但实际上底层镜像中的文件依然完好无损。

2. 图解写时复制

假设我们要修改镜像层中的 config.txt 文件:

plaintext
【修改前】
[可写层] ── (空)
[镜像层] ── config.txt (只读) 

【修改时触发 CoW】
[可写层] ── config.txt (复制到这里并进行修改) <── 容器写入这里
                               ^ (复制)
[镜像层] ── config.txt (原始文件,保持只读)

3. CoW 的好处:

  1. 极速启动:启动一个新容器时,Docker 不需要复制整套镜像文件,只需要创建一个空的可写层。这个过程几乎是瞬间完成的(几毫秒)。
  2. 超高空间利用率:100 个运行相同镜像的容器,可以共享同一个底层的只读镜像。每个容器只占用极少量的空间(只记录它们自己修改过的数据)。

总结

  • 分层(Layering) 解决了镜像的存储和分发问题。通过把镜像拆成多层,实现了资源的共享和复用。
  • 写时复制(CoW) 解决了容器运行时的读写效率问题。它保证了镜像的不可变性,同时允许容器在不污染镜像的前提下,快速、轻量地进行读写操作。
右滑查看面试常问