Docker 入门教程(六):联合文件系统(UnionFS)

发布于:2025-07-01 ⋅ 阅读:(21) ⋅ 点赞:(0)

🐳 Docker 入门教程(六):联合文件系统(UnionFS)


一、联合文件系统(UnionFS)

联合文件系统(Union File System,简称 UnionFS)是一种支持将多个只读层合并成一个文件系统视图的文件系统技术。Docker 正是通过它,实现了镜像分层构建与容器写层隔离。

换句话说,UnionFS 允许我们把多个只读目录“叠在一起”,让用户看到的是“一个统一的文件系统”。

它是镜像、容器分离的根本支撑。


二、Docker 镜像的层级结构

Docker 镜像并不是一个打包好的压缩文件,而是由多个只读层(layer)按顺序叠加而成的。

每一层都只包含自上一次变更以来的“文件系统差异”(增删改文件),例如:

FROM ubuntu            # 第1层:基础镜像层
RUN apt update         # 第2层:系统更新命令
RUN apt install nginx  # 第3层:安装 nginx
COPY . /app            # 第4层:拷贝项目代码

Docker 会为每一条指令生成一层(layer),每层是只读的,并存储为 SHA256 哈希命名的目录。

镜像分层的意义

  1. 高效复用:多个镜像可以共享相同的底层层(如基础镜像层)
  2. 按需下载:拉取镜像时会分层下载,避免重复传输
  3. 构建缓存:如果上一层没变化,Docker 会重用缓存而非重建镜像层
  4. 只读安全:镜像不能被容器改写,天然具备版本一致性

当你运行一个容器时,Docker 会将镜像层挂载到容器中,并在最上层添加一个“可写层”

容器文件系统结构:

[ 写层 ] ← 运行时动态生成,容器唯一
[ 镜像层 4 ] ← COPY 指令
[ 镜像层 3 ] ← 安装依赖
[ 镜像层 2 ] ← 环境配置
[ 镜像层 1 ] ← 基础系统

这个结构就是典型的 UnionFS 挂载叠加形式,最上面一层是写层,其它都是只读的。


三、写层(Copy-on-Write)

容器在运行时的所有写操作(包括新增文件、修改配置、生成日志)都写入最上方的写层

镜像层是只读的,操作都写入写层——或者叫做容器层?

而对文件的读取操作,则从顶层依次向下查找第一个匹配项。

文件删除/修改时的行为(非常重要)

  • 删除文件:其实不会真的从下层删除,而是在写层记录“白名单”(whiteout)屏蔽该文件。
  • 修改文件:先把下层文件复制到写层,再修改(copy-on-write)

因此,镜像层始终保持不变,容器之间互不影响。

*联合文件系统的技术支持

Docker 支持多种联合文件系统驱动(取决于操作系统):

驱动类型 系统支持 特点
overlay2 推荐默认(现代 Linux) 高性能,内核直接支持
aufs Ubuntu 较老版本 最早使用,已过时
btrfs / zfs 可选高级驱动 支持快照、更复杂的挂载

你可以通过如下命令查看当前使用的存储驱动:

docker info | grep Storage

四、镜像构建缓存机制 = 层级缓存机制

在你执行 docker build 时,每一条 Dockerfile 指令生成的层会被缓存(只要内容没有变化)。

Docker 会根据上下文(比如文件 hash)决定是否使用缓存。

这意味着:

  • 如果你在 Dockerfile 的前几层频繁改动,会导致所有后续层都重新构建
  • 所以我们会说:“尽量把不变的层写在前面”

容器生命周期下的数据命运

操作 写层会怎样? 镜像层是否保留?
容器运行 写层存在 镜像只读保留
容器停止 写层仍在 镜像不变
容器删除 写层随容器删除 镜像不变
镜像被删除 镜像层被移除(如果未被其它容器使用) 容器无法重新启动

UnionFS 的局限与演进

  • 写层性能相对较低(尤其是随机写入时)
  • 对高频 IO 的容器(数据库)建议使用挂载卷(Volume)代替
  • 容器中的数据默认是临时的、不持久的

这就是为什么你运行完一个容器后,重启发现数据全没了 —— 因为写层随容器消失了。


五、从镜像到容器,再到新镜像的完整闭环

在理解 UnionFS 分层结构之后,我们可以进一步掌握 Docker 最核心的使用流程:镜像 → 容器 → 新镜像 → 分享

这个过程遵循如下原则:

  1. 镜像层始终只读:任何 docker pull 拉下来的镜像都是不可变的,它就像一个快照模板,不会因为运行或改动而被修改。

  2. 容器运行时增加一个可写层:当我们执行 docker run 时,Docker 会在镜像顶部叠加一个写层(Writable Layer),所有运行时的变动(文件操作、配置更改等)都记录在这一层中。

  3. 容器运行中的更改,仅影响写层:即使你在容器中删除、修改了镜像文件,实际上只是对写层进行“遮盖”或“拷贝修改”,镜像层依然保持原样不动。

  4. 通过提交或构建形成新镜像:一旦你在容器中完成了配置或开发,可以使用如下方式生成新的镜像:

    • docker commit 容器ID 新镜像名:将当前容器快照为新的镜像层
    • docker build:从 Dockerfile 定义新的镜像构建流程
  5. 新镜像仍由只读层组成:无论你是 commit 还是 build,最终产生的镜像都是只读层的组合,底层机制依旧是 UnionFS。

  6. 镜像可分发、复用:将新镜像 docker push 到远程仓库后,其他人可以 docker pull 下来,并基于你提交的状态继续运行容器、做进一步更改。

工作流图示(逻辑流程):

    [ 原始镜像层 ]          ← docker pull
           ↓
    +------------------+
    | 容器写层(可写) |   ← docker run(运行时修改)
    +------------------+
           ↓
    [ 新镜像层 ]             ← docker commit / build
           ↓
     推送到远程仓库           ← docker push
           ↓
    其他人拉取并运行         ← docker pull + run(再加写层)

示例说明

你运行一个 Python 镜像,安装 Flask,然后发布成镜像:

docker pull python:3.10
docker run -it python:3.10  # 安装 flask...
docker commit 容器ID my-python:flask
docker push my-python:flask

其他人:

docker pull my-python:flask
docker run -it my-python:flask  # 在这个基础上继续开发

这就形成了一个典型的“从模板 → 修改 → 发布新模板 → 再次复用”的开发链路。

这一机制正是 Docker 能够高效实现镜像复用、层级缓存、团队协作和 CI/CD 自动化的核心。

镜像层只读、可复用;容器层临时、可变动;新镜像则是将变动固化为新只读层,构成可传播的构建快照。

这一设计体现了 Docker 对文件系统和资源管理的高度抽象能力,是其轻量、高效、模块化的关键所在。
各级开发人员、运维人员、CI 系统可以在同一个基础镜像之上,逐层构建、逐层定制、逐层复用,实现真正的模块化构建流程。


网站公告

今日签到

点亮在社区的每一天
去签到