Skip to content

Docker 不是虚拟机,它是“进程隔离 + 文件系统快照”

当你执行 docker run -p 3000:3000 myapp,Docker 并没有启动一台完整的虚拟计算机。它没有模拟 CPU、内存、硬盘和网卡,也没有加载一个完整的操作系统内核。它所做的,远比这更轻量、更高效,也更贴近操作系统本身的能力。

Docker 的本质,是利用 Linux 内核原生机制,对普通进程进行封装与隔离,使其“看起来像”一个独立的系统环境。这个过程不依赖虚拟化硬件,而是建立在三个核心内核技术之上:cgroupsnamespacesUnionFS

一、Docker 的三大基石

1. cgroups:限制你能用多少资源

cgroups(Control Groups)是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组的资源使用,包括 CPU、内存、磁盘 I/O、网络带宽等。

当你运行一个容器并指定 --memory=512m--cpus=1.5 时,Docker 实际上是在创建一个 cgroup,并将容器内的所有进程加入其中。这个 cgroup 就像一个“资源围栏”,确保容器内的进程无法消耗超过设定的内存或 CPU 配额。

例如:

bash
docker run -it --memory=100m ubuntu:22.04

即使容器内运行一个无限分配内存的程序,一旦超过 100MB,cgroup 会触发 OOM(Out of Memory)机制,直接终止进程,而不会影响宿主机或其他容器。

这说明了什么?容器不是虚拟机,它不拥有独立的物理资源,而是共享宿主机的资源池,通过 cgroups 实现资源的精细化控制

2. namespaces:让你“以为”自己是独立的

namespaces 是 Linux 提供的另一项核心隔离机制。它使得一组进程可以拥有自己独立的“视图”,包括 PID、网络、文件系统挂载点、用户 ID、主机名等。

Docker 容器之所以能拥有独立的进程列表、网络栈和主机名,正是因为它运行在多个隔离的命名空间中:

  • pid namespace:容器内的进程只能看到自己命名空间中的进程。ps aux 显示的 PID 从 1 开始,但宿主机上它可能是一个普通的进程。
  • network namespace:容器拥有独立的网络接口、IP 地址、端口空间。你可以让多个容器监听 80 端口,只要它们在不同的网络命名空间中。
  • mount namespace:允许容器拥有独立的文件系统挂载视图,比如将宿主机的某个目录挂载为 /data
  • uts namespace:容器可以拥有自己的主机名和域名,而不影响宿主机。
  • user namespace:将容器内的 root 用户映射为宿主机上的非特权用户,增强安全性。

这些命名空间共同作用,使得容器内的进程“以为”自己运行在一个独立的操作系统中。但实际上,它们只是宿主机上的普通进程,被“骗”了。

3. UnionFS:镜像的分层艺术

如果说 cgroups 和 namespaces 解决了“运行时隔离”,那么 UnionFS(联合文件系统)则解决了“镜像构建与分发”的问题。

Docker 镜像不是单个大文件,而是由多个只读层(layer)叠加而成。每一层对应 Dockerfile 中的一条指令,比如 FROMCOPYRUN。这些层通过 UnionFS 技术合并成一个统一的文件系统视图。

例如:

dockerfile
FROM node:18-alpine
COPY package.json .
RUN npm ci
COPY . .

这条构建流程会产生至少四层:

  1. 基础镜像层(node:18-alpine)
  2. COPY package.json 产生的文件层
  3. RUN npm ci 生成的 node_modules 层
  4. COPY . . 产生的应用代码层

每一层都是只读的,并且可以被多个镜像共享。当你修改了应用代码并重新构建时,Docker 只需重建最后几层,前面的层(如基础镜像、依赖安装)可以直接复用缓存。这极大提升了构建效率。

当你 docker run 时,Docker 会在这些只读层之上添加一个可写层(container layer)。所有运行时的文件修改(如日志写入、临时文件)都发生在这个顶层。一旦容器删除,可写层也随之消失,底层镜像不受影响。

这就是“文件系统快照”的含义:镜像是静态的、分层的快照;容器是基于快照启动的、带有一个可写层的运行实例。

二、Docker 与虚拟机的本质区别

维度虚拟机(VM)Docker 容器
架构Guest OS + Hypervisor + Host OS直接运行在 Host OS 上,共享内核
启动速度秒级到分钟级毫秒级到秒级
资源开销高(每个 VM 都运行完整 OS)低(仅隔离进程,共享内核)
隔离性强(硬件级隔离)较强(内核级隔离,但共享内核)
镜像大小GB 级别MB 级别(甚至 KB)
应用部署密度低(受限于资源)高(单机可运行数百容器)

虚拟机模拟的是“硬件”,而 Docker 模拟的是“操作系统环境”。前者厚重但隔离彻底,后者轻盈但依赖宿主机内核。

三、为什么说 Docker 是“进程隔离 + 文件系统快照”?

当你运行 docker run nginx,Docker 做了什么?

  1. 从镜像仓库拉取 nginx 镜像(一组分层的只读文件系统快照);
  2. 在宿主机上创建新的 cgroup 和多个 namespaces;
  3. 启动一个进程(nginx 主进程),将其加入上述 cgroup 和 namespaces;
  4. 将镜像的各层通过 UnionFS 挂载为根文件系统;
  5. 在最上层添加一个可写层,供运行时使用。

最终,你看到的“容器”,本质上就是一个被隔离的进程,运行在一个由分层快照构建的文件系统环境中。

四、理解本质的意义

许多开发者使用 Docker 仅停留在 docker rundocker-compose up 的层面,却对“为什么能跨环境运行”“为什么镜像可以复用”“为什么容器重启后数据会丢失”缺乏理解。

一旦你明白 Docker 的本质是“进程隔离 + 文件系统快照”,这些问题便迎刃而解:

  • 跨环境一致性:因为镜像是文件系统快照,包含了应用运行所需的一切依赖。
  • 镜像复用:因为分层机制允许共享基础层(如 node:18-alpine)。
  • 数据丢失:因为容器的可写层是临时的,除非使用 volume 挂载到宿主机。

更重要的是,这种理解为你深入优化 Dockerfile、调试容器网络、设计 CI/CD 流水线、乃至过渡到 Kubernetes 打下了坚实基础。

结语

Docker 不是魔法,它是对 Linux 内核能力的精巧封装。它没有发明新的技术,而是将 cgroupsnamespacesUnionFS 这些早已存在的机制,组合成一种全新的应用交付范式。

当你下一次编写 Dockerfile 或调试容器网络时,请记住:你操作的不是一个虚拟机,而是一个被精心隔离的进程,运行在一个由分层快照构建的文件系统之上。

这才是 Docker 的真相。