Docker存储原理
Docker存储原理
容器不是 “轻量级虚拟机”,而是被 “隔离 + 限制 + 封装” 的 Linux 进程
主要使用的技术为Namespace、CGroups、UnionFS 三大技术
容器的本质
虚拟机(VM)是 “模拟完整硬件 + 独立操作系统内核”,比如在 Windows 上装一个 Linux 虚拟机,里面有自己的 Linux 内核、进程、文件系统,重量大、启动慢;
而 Docker 容器是共享宿主机 Linux 内核,仅为进程 “套上隔离、限制、文件系统的外壳”—— 相当于给一个 Linux 进程 “画了个独立的小圈子”,让它以为自己独占系统,实则资源和内核都与宿主机共享。
这三大技术的作用,就是给这个 “小圈子” 定规则:
- Namespace:圈出 “隔离边界”(进程看不到圈外的东西);
- CGroups:圈出 “资源上限”(进程不能超出圈里的资源);
- UnionFS:圈出 “独立文件系统”(进程只能用圈里的文件,且修改不影响圈外)。
三大技术分别做什么?
1. Namespace:给全局资源划分范围
Namespace(命名空间)是 Linux 内核的隔离机制,它的核心作用是:对进程 “隐藏” 宿主机的部分资源,让进程只能看到自己 “命名空间内” 的资源,从而实现 “容器间互不干扰”。
Docker 用到 6 种关键 Namespace,每种负责隔离一类资源,我们用 “公寓合租” 类比(每个容器是一间房,宿主机是整栋公寓):
Namespace 类型 | 负责隔离的资源 | 类比(公寓合租) | 实例说明 |
---|---|---|---|
Pid Namespace | 进程 ID(PID) | 每间房的 “房间号”:101 房的 “1 号住户” 和 102 房的 “1 号住户” 互不冲突 | 宿主机上 PID=100 的进程,在容器内可能显示为 PID=1(容器的 “init 进程”),容器内看不到宿主机的其他进程 |
Network Namespace | 网络设备、IP、端口 | 每间房有独立的 “路由器和网线”,101 房的 “端口 80” 和 102 房的 “端口 80” 不冲突 | 容器有自己的虚拟网卡(veth)、独立 IP(如 172.17.0.2),需通过 “端口映射”(如宿主机 8080 → 容器 80)才能被外部访问 |
Mnt Namespace | 文件系统挂载点 | 每间房有独立的 “储物柜”,101 房的 “/usr” 目录和 102 房的 “/usr” 目录互不影响 | 容器内的 “/” 根目录是独立的(由 UnionFS 构建),宿主机的 “/home” 目录默认不会出现在容器内 |
User Namespace | 用户 / 用户组 ID(UID/GID) | 每间房的 “住户权限”:101 房的 “管理员”(UID=0),在整栋公寓里可能只是普通住户(UID=1000) | 容器内的 “root 用户”(UID=0),在宿主机上可能映射为普通 UID(如 1000),即使容器内提权,也不会获得宿主机的 root 权限,提升安全性 |
UTS Namespace | 主机名(hostname)和域名 | 每间房有独立的 “门牌号名称”:101 房叫 “我的容器”,102 房叫 “你的容器” | 用 docker run --name my-container 启动容器后,进入容器执行 hostname ,会显示 “my-container”,而非宿主机的主机名 |
IPC Namespace | 进程间通信(如信号、共享内存) | 每间房的 “内部对讲机”:101 房的对讲机只能联系房内人,无法联系 102 房 | 容器内进程用 “共享内存” 通信时,只能和同一容器内的进程交互,不会干扰其他容器的 IPC 资源 |
2. CGroups: 限制资源使用
CGroups(Control Groups,控制组)也是 Linux 内核功能,核心作用是:限制、记录、隔离进程组的资源使用(如 CPU、内存、磁盘 I/O),避免某个容器 “占满宿主机资源”,导致其他容器或宿主机崩溃。
如果说 Namespace 是 “隔离边界”,CGroups 就是 “资源上限”—— 相当于给公寓的每间房设定 “用电上限(2000W)、用水上限(10 吨 / 月)”,避免某间房过度消耗资源影响他人。
Docker 常用的 5 个 CGroups 子系统(资源控制器):
CGroups 子系统 | 作用(限制 / 记录的资源) | 实例说明 |
---|---|---|
cpu | 限制 CPU 使用率(如 “最多用 50% 的 CPU 核心”) | 用 docker run --cpus 0.5 启动容器,该容器最多只能使用宿主机 1 个 CPU 核心的 50% 资源 |
memory | 限制内存使用(如 “最多用 1GB 内存”) | 用 docker run --memory 1g 启动容器,当容器内存使用超过 1GB 时,会被内核 “Kill”(OOM 杀死),避免耗尽宿主机内存 |
blkio | 限制磁盘 I/O 速率(如 “读写最多 100MB/s”) | 用 docker run --device-read-bps /dev/sda:100mb 限制容器对磁盘 /dev/sda 的读速率不超过 100MB/s |
device | 控制容器对宿主机设备的访问权限(如 “禁止使用 USB 设备”) | 默认容器只能访问宿主机的少数设备(如 /dev/null ),需通过 docker run --device /dev/sda 明确授权才能访问磁盘 /dev/sda |
freezer | 暂停 / 恢复容器的进程组(类似 “冻结” 容器) | 执行 docker pause <容器ID> 时,就是通过 freezer 子系统暂停容器内所有进程;docker unpause 则恢复 |
3. UnionFS:容器的 “文件系统积木”—— 让容器 “自带文件”
UnionFS(联合文件系统)是 Docker 存储的核心,它的核心能力是:将多个独立的 “文件系统分支”(目录),以 “只读 + 可写” 的方式合并成一个 “虚拟的统一文件系统”,既节省空间,又能实现 “容器修改不影响基础镜像”。
我们用 “叠放的透明文件夹” 类比:
- 最下层的几个文件夹是 “只读的”(不能改),相当于 Docker 的 “基础镜像层”(如 Ubuntu 镜像的
/bin
/usr
目录); - 最上层的一个文件夹是 “可写的”(能改),相当于 Docker 的 “容器层”(容器运行中创建 / 修改的文件,如日志、配置);
- 我们看到的 “统一文件系统”,是这些文件夹 “叠在一起” 的效果 —— 读取文件时,从上层往下找;修改文件时,只在最上层的 “可写层” 操作(不改动下层只读层)。
Docker 存储的 “分层结构”(基于 UnionFS)
Docker 的文件系统由 “镜像层” 和 “容器层” 组成,结构如下:
- 基础镜像层(只读):比如
ubuntu:22.04
镜像,包含 Ubuntu 系统的所有基础文件(/bin
/etc
/usr
等),这一层是只读的,多个容器可以共享同一基础镜像层(节省磁盘空间); - 中间镜像层(只读):如果基于
ubuntu:22.04
构建新镜像(如安装 Nginx),会新增一层 “安装 Nginx 的层”,这一层也是只读的 —— 比如FROM ubuntu:22.04
+RUN apt install nginx
,就会生成一个 “含 Nginx 的中间层”; - 容器层(可写):启动容器时,Docker 会在最上层添加一个 “可写层”—— 容器内创建的文件(如
/var/log/nginx/access.log
)、修改的配置(如/etc/nginx/nginx.conf
),都会保存在这一层; - 挂载点(可选):如果用
docker run -v /宿主机目录:/容器目录
挂载数据卷,容器内的/容器目录
会直接映射到宿主机目录,绕过 UnionFS 的可写层(数据持久化,容器删除后数据不丢)。
UnionFS 的核心优势:Copy-on-Write(写时复制)
这是 UnionFS 节省空间的关键机制:只有当容器需要修改 “只读层” 的文件时,才会将该文件 “复制到可写层” 进行修改,只读层的原文件始终不变。
举个例子:
- 容器要修改
/etc/nginx/nginx.conf
(该文件在 “Nginx 中间层”,只读); - UnionFS 会先将
nginx.conf
从 “中间层” 复制到 “容器层”(可写); - 容器修改的是 “容器层” 的
nginx.conf
,中间层的原文件依然只读,其他共享该中间层的容器不受影响。
这个机制让 “多个容器共享基础镜像层” 成为可能 ——10 个基于 ubuntu:22.04
的容器,只需要一份 ubuntu:22.04
镜像的磁盘空间,而不是 10 份。