3、镜像分层机制:基础镜像层、增量层、层叠与合并原理、UnionFS与OverlayFS
聊到镜像回滚,绕不开的一个核心概念就是「分层」。说白了,Docker 镜像之所以能那么轻量、能秒级回滚,全靠这套分层机制在背后撑着。我刚开始接触容器时,也觉得镜像就是个「大压缩包」,后来踩了几次坑才明白——分层才是精髓。
3.1 基础镜像层:一切的地基
基础镜像层,就是最底下那一层。它通常是一个精简的操作系统核心,比如 Alpine、Ubuntu、CentOS 的 rootfs。这一层是只读的,永远不会被修改。
举个例子,你写 FROM ubuntu:22.04,Docker 就会把 ubuntu:22.04 这个镜像的所有层拉下来,作为你新镜像的「地基」。我习惯把基础镜像层比作「毛坯房」——墙、地板、水管都在,但还没装修。
3.2 增量层:每次修改都只存「差异」
你在 Dockerfile 里每写一条 RUN、COPY、ADD,Docker 都会生成一个新层。这个层只记录「相对于上一层的改动」,而不是整个文件系统的快照。
比如你执行:
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nginx
COPY index.html /var/www/html/
CMD ["nginx", "-g", "daemon off;"]
这里一共会产生 4 层(基础层 + 安装层 + 复制层 + CMD 元数据层)。每一层都只存增量——安装 nginx 那层只存新增或修改的文件,不会把 ubuntu 的几千个文件再复制一遍。
3.3 层叠与合并原理:堆叠的艺术
这么多层堆在一起,怎么让容器看到「一个完整的文件系统」?答案是:层叠与合并。
Docker 会把所有层按照从下到上的顺序堆叠起来。当容器读取一个文件时,它会从最上层开始找,找到就返回;如果上层没有,就往下找。这叫做「写时复制(Copy-on-Write)」的读取策略。
我画了一张图,帮你理解这个堆叠过程:
合并的过程其实不复杂。Docker 把每一层看作一个「增量补丁」,最终合并成一个完整的文件系统视图。你想想看,如果每次都要完整复制整个文件系统,那一个镜像动辄几个 GB,根本没法玩。
3.4 UnionFS 与 OverlayFS:背后的引擎
分层堆叠的想法很美好,但谁来实现它?答案是联合文件系统(Union Filesystem)。
| 特性 | UnionFS(早期) | OverlayFS(主流) |
|---|---|---|
| 内核支持 | 需要额外模块 | Linux 内核 3.18+ 原生支持 |
| 层数限制 | 理论上无限制,但性能随层数下降 | 最多 128 层(实际够用) |
| 性能 | 较差,尤其大量小文件场景 | 接近原生文件系统性能 |
| 写时复制 | 支持,但实现较复杂 | 支持,且效率更高 |
| 当前状态 | 基本被淘汰 | Docker 默认驱动(overlay2) |
OverlayFS 的工作原理,说白了就是「把多个目录合并挂载成一个目录」。它有两个核心目录:lowerdir(只读下层)和 upperdir(可写上层),然后通过 merged 目录呈现合并后的视图。
我记得有一次排查容器磁盘 IO 异常,发现是 overlay2 的层数太多(一个镜像叠了 60 多层),导致每次文件查找都要遍历所有层。后来优化了 Dockerfile,把频繁变动的层合并,问题就解决了。
3.5 分层机制与回滚的关系
理解了分层,回滚的原理就一目了然了。每个镜像层都是只读的、不可变的。当你回滚时,只需要把容器引用的「层指针」指向旧的层即可。不需要复制数据,不需要重新构建,几乎是瞬间完成。
我习惯把镜像层比作 Git 的 commit。每个层都有一个唯一的 SHA256 摘要,层与层之间通过指针串联。回滚就是「把 HEAD 指针移回旧的 commit」——数据还在,只是引用变了。