Docker核心技术:Docker原理之Union文件系统
云原生学习路线导航页(持续更新中)
- 相关内容快捷链接
- 应用架构演进
- 容器技术要解决哪些问题
- Docker的基本使用
- Docker原理之Namespace
- Docker原理之Cgroups
本文是 Docker核心技术 系列文章:主要讲解了docker文件系统的基本原理,包括:docker镜像的分层结构、docker容器的rootfs核心机制、unionFS的核心原理等,并以overlayFS 为例讲解了文件系统联合的过程,最后介绍了oci标准与docker引擎架构变化
- 希望大家多多 点赞 关注 评论 收藏,作者会更有动力继续编写技术文章
1.Docker容器镜像的创举
-
Docker容器最大的创新,就是 使用镜像分层,解决了部署时文件分发的通用难题。
-
Dockerfile每一条命令都是一个镜像层,都有一个唯一的checksum。拉取每层镜像前,会先检查这个checksum是否存在,存在的话直接使用缓存,不用再拉取了
-
假如OS基础镜像层有500MB,你每次变更业务代码只有5MB,那么部署时拉取新镜像就做到了 增量拉取,速度和资源都得到了节约
-
-
为什么说是docker容器的创举?
- 以前,比如java程序部署时,每个公司有自己的file server,把代码或jar包放到file server。在服务器上手动get或编写agent获取到jar包,然后部署。
- 不同的应用,需要写不同的脚本实现
- 使用docker,docker repository相当于file server,docker client相当于agent。docker pull的时候还可以增量拉取。把流程标准化了
2.Docker 容器 rootFS 的核心机制
从 Linux核心技术:Linux文件系统与bootFS/rootFS 可知,docker 容器文件系统中,只包括rootfs层,不包括bootfs层
2.1.分层结构(关键差异)
- 相比于linux rootfs结构,docker rootfs 采用了分层结构:
- 镜像层(只读):容器启动时,以镜像的 rootfs 为不可变基础层,确保镜像文件无法被运行时修改。
- 容器层(可写):在镜像层之上叠加一个可写层(称为容器层),所有文件变更(如写入、删除)均在此层进行。
2.2.联合挂载(Union Mount)
- Docker 使用 联合文件系统(如 Overlay2、AUFS)将多层文件系统合并为单一视图。
- 比如使用 Overlay2 实现联合文件系统:
- lowerdir:对应镜像的只读层。
- upperdir:容器的可写层,记录运行时变更。
- merged:用户看到的统一文件系统视图。
2.3.写时复制(CoW)与 用时分配
- 读取:直接访问底层镜像文件(无性能损耗)。
- 修改:将目标文件从镜像层复制到容器层后再修改,原镜像文件保持只读。
2.4.总结:docker容器文件系统的优势
优势 | 说明 |
---|---|
镜像不可变性 | 镜像层始终只读,确保同一镜像启动的容器环境一致,避免依赖冲突。 |
资源高效利用 | 多个容器共享同一镜像层,减少磁盘占用(如 100 个 CentOS 容器共享一个基础层)。 |
快速部署 | 容器启动时无需复制完整镜像文件,仅需创建可写层,秒级启动。 |
版本追溯 | 通过分层机制记录每次镜像构建的变更(Dockerfile 指令对应层),便于调试和回滚。 |
3.UnionFS 联合文件系统详解
3.1.Union文件系统是什么
- Union文件系统(Union File System,简称UnionFS)是一种 分层叠加 的文件系统技术,它通过将多个目录或文件系统(包括只读和可写层)合并到同一挂载点,形成一个逻辑统一的文件视图。其核心特点包括:
- 分层存储:文件系统由多层组成,底层通常是只读的基础文件(如Docker镜像的基础层),上层为可写层(如容器运行时修改)。
- 写时复制(CoW):修改文件时,系统会先将文件从只读层复制到可写层进行变更,原文件保持不变,确保高效存储和隔离性。
- 优先级叠加:访问文件时,高层内容覆盖低层同名文件,用户看到的是最终叠加结果。
3.2.容器存储驱动
3.2.1.容器存储驱动是什么
- 容器存储驱动是容器引擎(如 Docker、Containerd)的核心组件,负责管理容器文件系统的存储和操作
- 核心功能包括:
- 分层管理:实现镜像分层(只读层)与容器运行时层(可写层)的叠加。
- 写时复制(CoW):保证镜像不可变,仅在容器层修改文件时复制数据。
- 数据持久化:支持通过卷(Volume)或绑定挂载实现数据持久化。
- 性能优化:根据场景选择不同驱动,平衡 I/O 性能与资源消耗。
3.2.2.容器存储驱动发展历史
- 早期阶段(2013-2015)
- AUFS 作为 Docker 初始驱动,解决了分层存储需求,但依赖非主线内核补丁,兼容性差。
- 标准化阶段(2015-2017)
- OverlayFS 进入 Linux 主线内核(3.18+),逐步取代 AUFS 成为主流。
- Device Mapper 因支持块存储特性,成为企业级场景的选择。
- 多样化阶段(2018 至今)
- Btrfs/ZFS 提供高级功能(快照、压缩),但资源消耗较高。
- Containerd 作为独立运行时,简化驱动支持(如默认放弃 AUFS)。
- 现状:OverlayFS 2已经成为主流,容器驱动的不二之选
3.2.3.主流存储驱动特点与对比
驱动名称 | 支持场景 | 核心优点 | 核心缺点 |
---|---|---|---|
AUFS | Docker(仅 Ubuntu/Debian) | 成熟的分层设计,节省存储空间 | 非主线内核支持,性能在密集 I/O 场景差 |
OverlayFS | Docker/Containerd(Linux 3.18+) | 内核原生支持,性能均衡,兼容性好 | 不支持页缓存共享,多层叠加性能下降 |
Device Mapper | Docker/Containerd(RHEL/CentOS) | 块级存储,适合生产环境高负载 | 配置复杂,需预分配存储池,空间利用率低 |
Btrfs | Docker(社区版 Debian/Ubuntu,企业版 SLES)/Containerd | 支持快照、压缩,适合开发测试 | 稳定性风险,修复复杂问题需重启 |
ZFS | Docker(Linux/FreeBSD) | 数据完整性校验,支持高级存储池管理 | 内存占用高,需专用内核模块 |
3.2.4.不同存储驱动优缺点对比
- Overlay:kernel自带,不需要额外安装,使用容器只需要额外安装docker/containerd等
3.3.OverlayFS
3.3.1.OverlayFS 是什么
- OverlayFS是一种联合文件系统,核心功能就是 通过覆盖机制实现上下层文件的联合,得到一个终态的文件系统
- Overlay:覆盖的意思
- OverlayFS的分层
- lower层:对应容器的镜像层,dockerfile的每一条指令都是一个新的只读层,合并在一层统一为lower层
- upper层:对应容器的容器可写层
- 合并层:最终mount到容器中的层,对应容器能够看到的目录和文件。
- OverlayFS文件覆盖策略
- 如果一个文件 只在下层 存在,则会直接被合并
- 如果一个文件 只在上层 存在,则会直接被合并
- 如果一个文件 在上下层 都存在,则上层会覆盖下层,优先级更高
3.3.2.OverlayFS 文件系统练习
mkdir upper lower merged work
echo "from lower" > lower/in_lower.txt
echo "from upper" > upper/in_upper.txt
echo "from lower" > lower/in_both.txt
echo "from upper" > upper/in_both.txt
# mount
sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
# 查看 overlay 文件系统生成结果
tree
.
├── lower
│ ├── in_both.txt
│ └── in_lower.txt
├── merged
│ ├── in_both.txt
│ ├── in_lower.txt
│ └── in_upper.txt
├── upper
│ ├── in_both.txt
│ └── in_upper.txt
└── work├── index└── work6 directories, 7 filescat merged/in_both.txt
from uppercat merged/in_lower.txt
from lowercat merged/in_upper.txt
from upper
- mount 是 Linux 系统中用于将存储设备(如硬盘、U盘、ISO 镜像等)或远程文件系统挂载到指定目录的命令。挂载后,用户可以通过该目录访问存储设备中的内容。
mount -t <文件系统类型> -o <选项> <设备路径> <挂载点目录>
- 上述 mount 命令的参数解释
- lowerdir:只读的底层目录(可多个),文件按顺序从下到上叠加(/lower1 在最底层,/lower2 在其上)。
- upperdir:可写的上层目录,所有修改(新增、删除、修改文件)会记录在此。
- workdir:供 OverlayFS 内部使用的临时目录,必须与 upperdir 在同一文件系统。
- /merged:最终的挂载点,用户在此访问合并后的文件系统视图。
3.3.3.如何进入 docker 容器的mount ns
- 从上面的学习和练习,可以看出,docker容器的文件系统,其实就是docker image lower层+容器可写层,使用mount命令合并之后的merged文件系统。然后这个merged系统被挂载到了容器的mount ns中,所以容器进程能看到和主机不一样的文件系统
- 那么如何进入容器的 mount ns,查看merged之后的文件系统?
- 在 Docker核心技术:Docker原理之Namespace 中,学习了使用nsenter命令进入ns的方法
nsenter -t ${pid} -m
即可进入一个进程的mount ns- 比如:
[root@VM-226-235-tencentos /proc/11913/ns]# nsenter -t 11913 -m -bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8) root@VM-226-235-tencentos:/# root@VM-226-235-tencentos:/# ls bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var zgy
3.3.4.如何查看一个 docker 容器的overlay细节?
- docker inspect ${container-id},输出中GraphDriver展示了overlay细节
- GraphDriver 是 Docker 用于管理容器镜像分层存储的核心驱动模块,负责将镜像的只读层与容器的可写层叠加为统一视图。其输出信息包括:
- 存储驱动类型(如 overlay2、aufs),决定了分层合并的实现方式
- 具体存储路径(如 UpperDir、MergedDir),指向容器可写层和合并后的文件系统目录
- 镜像层 ID,记录镜像分层结构(如 LowerDir 中的多个只读层)
- 如:
3.4.Docker 容器采用Union文件系统 + mount ns的好处
- 采用 Union文件系统 解决了文件更新效率的问题
- 采用 mount ns 解决了docker容器可移植性问题。
- 一个容器,只需要把自己所依赖的所有命令、文件、软件…都放入自己的mount ns,然后将mount ns打包压缩,就可以移植到任何的可兼容系统中
- 容器不需要关心底层os是否包含某些命令和软件,它出生就自带了
4.OCI 容器标准
4.1.OCI 标准是什么
- docker作为先行者和商业公司,具有盈利性质,产品更新迭代很快
- google等作为后来者,想要分市场,只能通过标准化来介入。OCI就是这样的背景下,由google牵头,找大厂背书,搞出来的一个容器标准。
- docker小公司抵挡不住google的攻击,妥协后也接受了OCI标准,并把自己的容器运行时抽取为containerd
4.2.docker 引擎架构
- 早期的docker架构
- docker daemon是主进程,任何的容器进程都是docker daemon作为父进程 fork出来的子进程
- 此时如果docker daemon自身要升级,杀掉父进程,所有的子进程就都会有问题
- 改进后的docker架构
- 后来引入了一个containerd组件,并引入了一个 dockershim 的概念。containerd只是一个单纯的daemon,它在创建容器进程的时候,会同时创建一个dockershim进程,该进程作为容器进程的主进程。
- 这样 容器进程 与 containerd 解藕开了,升级containerd不会影响到dockershim,也就不会影响到具体的容器进程。而dockershim本身非常轻量级,基本不需要升级
- 查看下容器的进程树结构,验证是否是这样的:
- 验证是否每个容器都有一个docker shim?
5.docker问题解析
5.1.容器的1号进程是什么
- 容器的1号进程是它的entrypoint进程
- 为什么有些容器应用进程就是1号,有些容器应用进程不是1号呢?
- 有些容器entrypoint直接就指定了应用进程
- 有些容器是在entrypoint中指定一个启动脚本,由脚本拉起应用进程,所以应用进程就不是1号了