什么是 Docker 镜像的“只读层”和容器的“可读写层”?
在 Docker 的世界里,“只读层”(Read-Only Layer)和“可读写层”(Read-Write Layer) carbon 是理解 Docker 镜像和容器工作原理的核心概念。
它们共同构成了 Docker 的 联合文件系统(UnionFS)。简单来说:镜像(Image)是只读的,而容器(Container)是在镜像之上加了一个可读写的层。
下面我们用通俗的语言和图解来详细拆解这两个概念。
1. 什么是 Docker 镜像的“只读层”?
当你编写 Dockerfile 并构建镜像时,每一条指令(如 FROM, RUN, COPY)都会生成一个“层”(Layer)。这些层堆叠在一起,就形成了 Docker 镜像。
- 本质:这些层是绝对只读的(Read-Only)。一旦镜像构建完成,任何人都无法修改这些层中的内容。
- 共享机制:因为它们是只读的,所以可以被安全地共享。例如,如果你有 10 个运行 Ubuntu 的容器,它们在宿主机上其实共享同一份 Ubuntu 镜像的只读层,这极大地节省了磁盘空间。
- 比喻:就像一本已经印刷出版的书。书里的字(代码、系统文件)是固定死的,你不能直接在印刷好的书页上改字。
2. 什么是容器的“可读写层”?
当你运行一个容器时(使用 docker run 命令),Docker 会在镜像的“只读层”之上,最顶端添加一个薄薄的新层。这个层被称为“容器层”(Container Layer)或“可读写层”(Read-Write Layer)。
- 本质:所有对容器运行时的修改(比如写日志、创建新文件、修改已有配置、删除文件)都只会发生在这个可读写层中。
- 生命周期:这个可读写层是与容器同生共死的。当你删除容器(
docker rm)时,这个可读写层也会被彻底删除,里面产生的数据也会丢失(除非使用了数据卷 Volume)。 - 比喻:就像在刚才那本印刷书上盖了一张透明的塑料胶片(写字板)。你可以在胶片上用马克笔写字、画画。从上面看下去,书的内容和你的涂鸦融为一体,但你其实没有伤到书本一分一毫。
3. 它们是如何协同工作的?(写时复制 - Copy-on-Write)
既然镜像层是只读的,那如果我在容器里修改一个已有的镜像文件,会发生什么?
这里引入了 Docker 核心的 写时复制(Copy-on-Write, CoW) 机制:
- 读取文件(Read):容器需要读取文件时,会从上往下在各层中寻找。如果在最顶部的可读写层找不到,就去下面的只读镜像层找,直接读取。
- 修改文件(Write/Modify):
- 如果容器想要修改一个来自只读层的文件(比如
/etc/nginx/nginx.conf)。 - Docker 会自动将这个文件从底部的只读层复制一份到顶部的可读写层。
- 然后,容器在可读写层对这个副本进行修改。
- 底部的原始文件依然完好无损,但对于容器来说,它只能看到可读写层中修改后的新版本(新版本遮挡了旧版本)。
- 如果容器想要修改一个来自只读层的文件(比如
- 删除文件(Delete):
- 如果容器要删除一个只读层的文件,Docker 并不会真的从只读层删掉它(因为删不掉)。
- Docker 会在可读写层创建一个特殊的“遮挡标记”(Whiteout file),让容器觉得这个文件已经被删除了。
4. 对比总结表
| 特性 | 镜像的“只读层” (Image Layers) | 容器的“可读写层” (Container Layer) |
|---|---|---|
| 读写属性 | 只读 (Read-Only) | 可读写 (Read-Write) |
| 数量 | 可以有多个层(堆叠) | 只有一个(位于最顶层) |
| 共享性 | 多个容器可共享同一份镜像层 | 每个容器独占自己的可读写层 |
| 生命周期 | 永久保存,除非手动删除镜像 | 随容器创建而创建,随容器销毁而销毁 |
| 占用空间 | 通常较大(包含操作系统、依赖库等) | 通常非常小(仅保存运行时的增量修改) |
5. 最佳实践启示
理解了这两个概念,在实际开发中会有以下指导意义:
- 不要在容器的可读写层保存重要数据:因为容器一旦重建,可读写层的数据就没了。重要数据(如数据库文件、用户上传的图片)应该挂载到 Volume(数据卷) 中,绕过可读写层,直接写在宿主机上。
- 避免在容器运行期间产生大量垃圾文件:因为可读写层会随着写入的数据增多而变大,导致容器臃肿。
- 合理构建 Dockerfile:在构建镜像时,尽量把清理垃圾的命令(如
rm -rf /var/lib/apt/lists/*)和安装命令写在同一条RUN指令中,否则清理命令只会发生在新的只读层,而无法减小前一个只读层的大小。
右滑查看面试常问