Docker
容器基础
容器本质上就是一个被 Linux Namespace 隔离了视图、被 Cgroups 限制了资源、基于联合文件系统(rootfs)运行的普通宿主机进程 所谓的“进入容器”或“容器间互通”,底层均是对内核 Namespace 归属的动态调整与共享
底层核心
-
Cgroups(Control Groups,控制组) : Linux 内核提供的物理资源配额与度量机制
- 作用: 给进程的资源使用画“边界”,限制其能够占用的 CPU 算力、内存大小、磁盘 I/O 和网络带宽等,防止单个容器吃光宿主机资源
-
Namespace(命名空间): Linux 内核提供的资源隔离机制 作用: 为进程制造“障眼法”,让进程误以为自己独占了系统的某种全局资源(如进程 PID、网络栈、挂载点等)
- Mount Namespace(挂载命名空间)
- 含义: Linux 实现的第一个 Namespace 类型(标志位为
CLONE_NEWNS)。 - 作用: 隔离进程看到的文件系统挂载点列表。在当前 Mount Namespace 内执行的
mount或umount操作,仅在该命名空间内部可见,不会影响宿主机及其他命名空间。
- 含义: Linux 实现的第一个 Namespace 类型(标志位为
- PID Namespace:隔离进程编号,容器内部拥有独立的
PID 1。 - Network Namespace:隔离网络设备与端口,独立 IP、路由表。
- UTS Namespace:隔离主机名。
- IPC Namespace:隔离进程间通信(共享内存、信号量)。
- User Namespace:隔离用户与用户组 ID。
- Mount Namespace(挂载命名空间)
3.UnionFS(Union File System,联合文件系统): Linux 下一种分层、轻量级且高性能的文件系统技术 作用: 为进程提供“分层合并视图”,将多个目录叠加挂载到同一目录下,对外呈现为一个统一的文件系统。
- 分层堆叠: 镜像各层以只读状态堆叠在底层,实现多容器共享基础镜像,极大节省磁盘空间并加速镜像分发。
- 写时复制: 修改只读层的某个文件时,系统会先将该文件复制到顶部的可读写层作更改,底层镜像文件保持不变
- 删除屏蔽: 不会真正删除, 而是在可读写层创建一个白障屏蔽标记, 向容器屏蔽该文件
目录切换的系统调用/命令
-
chroot只改进程指针: 仅修改当前进程记录的根目录路径,旧根目录依然挂载在系统里。如果有 root 权限,通过保留的文件描述符或fchdir就能用cd ../轻易逃逸回宿主机。 -
pivot_root交换挂载点: 在 Mount Namespace 级别把新目录切换为根挂载点,并将旧宿主机根目录移到指定子目录。容器随后会直接umount卸载旧根,彻底断开与宿主机文件系统的物理连接,无法逃逸。
文件系统概念
-
rootfs(Root File System,根文件系统)----最终产物 / 运行环境实体
- 含义: 操作系统根目录
/下所包含的所有标准目录和文件集合(如/bin、/etc、/proc、/lib等)。 - 特点: 它只包含操作系统的文件与配置(躯壳),不包含 Linux 内核(灵魂)。容器运行时挂载在根目录上的就是 rootfs,它也是“容器镜像”的物理本质
- 实现原理: 镜像分层与合并(OverlayFS / UnionFS) -> 挂载隔离(Mount Namespace) -> 视图切换(pivot_root / chroot)
- 含义: 操作系统根目录
-
UnionFS(Union File System,联合文件系统)----抽象层 / 一类技术的概念统称
-
含义: 一种为 Linux 设计的分层、轻量级的高性能文件系统。一类技术的统称
-
作用: 支持将不同物理路径下的多个目录内容“联合挂载(Union Mount)”到同一个挂载点下,呈现为一个统一合并后的目录视图。
-
核心机制: 分层堆叠、写时复制(Copy-on-Write)、删除标记(Whiteout)
-
AuFS(Advanced UnionFS)----早期 unionfs 的实现
- 含义: 早期 Docker
- 特点: 率先实现了成熟的多层堆叠与写时复制能力,但代码复杂,且始终未合并进 Linux 官方内核主线
-
OverlayFS----现代主流实现(Docker 默认
overlay2) -
含义: Linux 内核官方原生支持的分层文件系统(以
overlay/overlay2存储驱动呈现) -
特点:
- 内核原生: 已合入 Linux 主线,稳定且无需第三方补丁
- 架构精简: 仅由底层只读目录
lowerdir、顶层读写目录upperdir、工作目录workdir及合并视图merged组成 - 高性能: 相比 AUFS,文件跳转更少,内存与 inode 消耗极低
-
Docker的三层结构
比喻: 只读层就像光源, 可读写层就是遮光的卡片, 透视合并光射出来就是容器最终看到的完整图案 (rootfs)
核心设计思想:从“全量复制”到“增量分层”
- 痛点: 传统模式下,每次软件修改都要保存一份完整的 rootfs(几百 MB 到几 GB),导致存储极度浪费、版本碎片化。
- 解法: 像 Git 一样只记录增量。每一层(Layer)只保存相比上一层发生变化的文件。
- 技术支撑: UnionFS(联合文件系统思想),它能将多个不同物理目录“重叠挂载”到同一个目录下,合成一个统一的文件树视图。
容器 rootfs 的三层架结构
当一个容器运行起来时,存储驱动会将 /var/lib/docker/overlay2/<layer-id>/ 下的相关物理目录, 通过内核系统调用联合挂载到统一视图目录. 整个文件系统从上到下由三部分组成:
| 层级名称 | 位置与挂载权 | 内容与作用 | 对 docker commit 的影响 |
|---|---|---|---|
| 可读写层 (Read-Write Layer) | 最顶层rw(可读写) | 存放容器运行期间所有的增、删、改。初始为空。 | 会被提交:通过 commit 可将其固化为一个新的只读镜像层。 |
Init 层 (*-init Layer) | 中间夹层ro+wh(只读+白障) | 存放当前容器实例独立的 ip、主机名... (如 /etc/hosts、/etc/resolv.conf、hostname)。 | 不会被提交:专门隔离出来,避免容器专属的临时配置污染新镜像。 |
| 只读层 (Read-Only Layers) | 底部所有层ro+wh | 镜像的基础层(如纯净 Ubuntu 的 5 个基础层)。 由所有从该镜像启动的容器共享。 | 完全不受影响:多容器共用底包,节省巨量磁盘空间。 |
核心操作机制:写时复制与白障(Whiteout)
只读层不可修改,那容器内发生文件改动时底层如何处理?
- 新建文件:直接写在最顶部的“可读写层”。
- 修改只读文件(Copy-on-Write):把底层的旧文件先复制一份到“可读写层”,然后在可读写层上修改,上层文件会遮挡下层同名文件。
- 删除只读文件(Whiteout / 白障):
- 底层文件是只读的,无法真删。
- 机制:AuFS 在顶部的“可读写层”创建一个带前缀的掩码文件(例如删除
foo时生成.wh.foo)。 - 效果:在最终联合挂载的视图里,
.wh.foo会把下层的foo彻底遮挡隐藏,应用感知上文件已被删除。
总结
Docker 镜像的精髓在于 “分层存储、联合呈现、写时复制、白障遮蔽”:
- 只读层保证了基础镜像的不可变性与多容器共享;
- Init 层巧妙解决了单机专属配置与镜像提交的解耦;
- 可读写层配合 Whiteout 实现了轻量级的动态修改与增量分发。
Volum机制
允许将宿主机上指定的目录或者文件,挂载到容器里面进行读取和修改操作。
-
解决的核心矛盾:容器 rootfs 的隔离性使容器与宿主机无法直接共享文件,且容器销毁后可读写层数据即丢失。Volume 用于实现数据持久化与主机-容器数据共享。
-
声明方式与存储位置:
- Bind Mount(
docker run -v /home:/test):直接将指定的宿主机绝对路径/home挂载进容器。
- Bind Mount(
-
实现时机与关键技术:
- 容器初始化进程(
dockerinit)在开启 Mount Namespace 之后、执行chroot(或pivot_root)之前执行挂载。(在rootfs准备好之后, chroot执行之前, bind mount 在宿主机挂载一个Volum) - 使用 Linux
mount --bind(绑定挂载),本质上是将挂载点目录的dentry指针重定向到宿主机目录的inode。 - 由于挂载发生在容器的 Mount Namespace 内,该挂载点对宿主机不可见。(类似容器内部的一个指针指向宿主机的一个地址, 而宿主机没有这个指针, 所以宿主机不可见)
- 容器初始化进程(
-
为何
docker commit不会提交 Volume 内的数据:docker commit发生在宿主机空间,宿主机看不到容器内部的 Bind Mount 映射关系,只能看到可读写层下原本为了作为挂载点而创建的空目录/test。因此,新镜像只会包含一个空的/test目录。