Loading

Docker 容器中的 Overlay 联合挂载实战


一、本节目标与学习思路

上一节我们手动完成了 Overlay 联合挂载实验,验证了三层结构的工作原理。本节直接用 Docker 引擎启动一个容器,去观察 Docker 中 Overlay 联合挂载的真实情况。目前不需要跟着操作,只需要跟着走一遍流程,建立基本印象。等后面学习 Docker 具体使用时再动手实践。先把难点全部铺垫好,后面越学越简单。


二、启动容器并查看 Overlay 挂载结构

启动容器

使用已有的 centos:7 镜像启动一个容器:

docker run -d --name test centos:7 sleep 100000000
  • -d:容器在后台运行
  • --name test:指定容器名为 test
  • centos:7:使用的镜像
  • sleep 100000000:容器启动后运行的第一个进程。如果不指定一个长期运行的进程,第一个进程一旦结束,整个容器就会退出

查看容器的 Overlay 挂载详情

容器启动后,通过 docker inspect 查看容器的详细信息,重点关注 Overlay 联合挂载部分。在输出的 GraphDriver 字段中可以看到熟悉的四个目录:

字段 含义
LowerDir 镜像层,只读,可以有多个目录(冒号分隔)
UpperDir 容器层,可写,记录所有改动
MergedDir 展现层,联合挂载后的合并结果
WorkDir 工作目录,存放临时文件

这和我们手动挂载时通过 -o 参数指定的 lowerdirupperdirworkdir,以及最终挂载到的 merged 目录是完全对应的。


三、LowerDir 的两个目录详解

在这个 centos:7 容器中,LowerDir 指定了两个目录(用冒号分隔):

第一个目录:init 目录

浏览这个目录,里面有 dev 目录和 etc 目录,etc 下有三个关键文件:

  • hostname
  • hosts
  • resolv.conf

为什么把这三个文件单独拎出来? 因为这三个文件跟网络和主机名信息有关。每个容器启动时可能指定了不同的网络环境和主机名,所以 Docker 引擎会把这三个文件单独挂载,在容器启动时根据容器的网络配置动态修改它们的值,保证每个容器的主机名、网络信息各不相同。

第二个目录:rootfs 目录

浏览这个目录,里面就是 bindevetchome 等——一套完整的 rootfs,就是 centos:7 镜像里打包的那套 Linux 目录结构。

其他目录的初始状态

  • UpperDir:初始情况下什么都没有,只有发生改动才会写入
  • WorkDir:存放临时文件,当前也是空的
  • MergedDir:展现层,内容来自 LowerDirUpperDir 的合并结果。当前 UpperDir 为空,所以 MergedDir 展示的就是 LowerDir 里的全部内容

通过 df 命令也可以看到这个挂载点,文件系统类型为 overlay


四、在容器内验证增删改操作

通过 docker exec 进入容器:

docker exec -ti test sh

进入容器后,看到的就是 merged 层(展现层)的内容。

1. 新增文件

echo 6666 > /etc/a.txt

回到宿主机查看 UpperDir,可以看到 etc 目录下出现了新增的 a.txt,内容正是 6666

2. 删除文件

rm -rf /etc/profile

查看 UpperDir,出现了一个名为 profile 的文件,但用 ll 查看会发现它是一个字符设备文件(c 开头),代表该文件已被删除。实际上 LowerDir 里的 profile 仍然存在,并没有被真正删除。

3. 修改文件

vi /etc/passwd    # 删掉 root 的密码字段

查看 UpperDiretc 目录下出现了 passwd 文件,内容为修改后的版本。

这些行为与我们手动挂载 Overlay 时的表现完全一致


五、容器销毁与数据持久化问题

理解了 LowerDir(只读)和 UpperDir(可写)的关系后,需要意识到一个重要问题:

一旦容器被销毁,UpperDir 里的所有改动就丢失了。 LowerDir(镜像层)的内容因为是只读的、来自镜像,所以一直存在。但 UpperDir 里的改动随容器生命周期结束而消失。

这就引出了一个常见需求:如果你基于一个基础镜像(如 centos:7)启动容器,在里面安装了 Java 环境、部署了公司的应用程序,这些改动都记录在 UpperDir 里。你希望把这些改动保存下来,制作成一个新镜像,这样拿着新镜像到另一台机器上直接启动容器,就自带了完整的运行环境,不需要再手动部署。


六、通过 commit 导出新镜像

导出操作

先退出容器(不销毁),然后用 docker commit 将容器导出为新镜像:

docker commit test my_image:v1.0
  • test:容器名
  • my_image:v1.0:新镜像的名称和版本标签

commit 的本质就是把当前容器的 LowerDir + UpperDir 两层内容合在一起,导出为一个新的 rootfs(即新镜像)。

查看新镜像:

docker images | grep my_image

此时即使删除原来的容器,改动也不会丢失,因为已经保存到新镜像中了:

docker container rm -f test

验证新镜像的内容

将新镜像导出为 tar 包并解压查看:

docker save my_image:v1.0 -o aaa.tar
mkdir ddd
tar -xmf aaa.tar -C ddd/
cd ddd/

解压后会发现里面有两个文件夹(两层),而原来的 centos:7 镜像只有一个文件夹(一层)。

第一层(如 3d 开头的目录):解压其中的 layer.tar,里面是 bindevetcmedia 等——就是 centos:7 基础镜像的那套完整 rootfs。

第二层(如 66 开头的目录):解压其中的 layer.tar,只有 etcroot 两个目录。etc 下面有我们之前改动的文件:

  • a.txt(新增的文件)
  • passwd(修改过的文件)
  • 至于被删除的 profile,因为本身就标记为"不存在",commit 导出时直接不保存

这就印证了:新镜像 = 原基础镜像的一层 + UpperDir 改动的一层。


七、镜像的分层机制

用新镜像启动容器

docker run -d --name my_test my_image:v1.0 sleep 100000000

docker inspect 查看这个新容器的 LowerDir,会发现:

  1. init 目录(固定存在):存放 hostshostnameresolv.conf 三个文件,用于动态修改
  2. 第一层 rootfs 目录:我们之前 UpperDir 里的改动内容(a.txtpasswdprofile 等)
  3. 第二层 rootfs 目录centos:7 基础镜像的完整 rootfs

现在 LowerDir 从原来的两个目录变成了三个目录(init + 两层 rootfs)。

为什么镜像会有多层?

镜像的层数取决于它的构建历史:

centos:7 基础镜像(1层)
    ↓ 启动容器,做改动,commit
my_image:v1.0(2层)
    ↓ 再启动容器,再做改动,再 commit
更新的镜像(3层)
    ↓ ...

每一次 commit 就是把当前的 UpperDir 固化为新的一层,叠加到已有的层之上。实际使用中,你拿到的镜像可能已经被别人处理过很多次,经历了多次 commit,所以看到 LowerDir 有很多层是正常的。

后面做镜像并不会手动来回 commit,会有更简单的方式(如 Dockerfile),后续课程会专门介绍。


八、本节总结

要点 说明
Docker 中的 Overlay 结构 与手动挂载完全一致:LowerDir(只读) + UpperDir(可写) → MergedDir(展现)
init 目录 Docker 固定会将 hostnamehostsresolv.conf 单独提取到一个目录中挂载,便于动态修改
容器内看到的 就是 MergedDir 展现层的内容
UpperDir 的生命周期 容器销毁后 UpperDir 里的改动就丢失了
docker commit 将容器的 LowerDir + UpperDir 导出为新镜像,改动被固化为新的一层
镜像分层 每次 commit 增加一层,LowerDir 中可能有多层目录
其他联合文件系统 除 Overlay/Overlay2 外还有 AUFS 等,可自行了解各自特性

从下一节开始,正式学习 Docker 容器引擎的具体操作。 前面铺垫的 namespace、chroot、Overlay 联合文件系统等底层原理,将帮助大家在学习具体用法时不再有疑惑,理解更加深入。

posted @ 2026-06-19 09:55  知杏  阅读(20)  评论(0)    收藏  举报