当你第一次接触 Docker 时,最直观的感受往往是启动速度快得离谱。相比于虚拟机动辄几分钟的引导过程,Docker 容器几乎是瞬间就出现在了你的终端里。这种差异让很多人误以为 Docker 是一种极其轻量级的虚拟机,但这实际上是一个根本性的认知误区。虚拟机是在模拟硬件,而 Docker 是在复用内核。

Docker 的本质非常朴素,它并没有创造全新的黑科技,而是将 Linux 内核中早已存在的功能------主要是 Namespaces、Cgroups 和 UnionFS------进行了标准化的封装和编排。它就是一个精明的搬运工,把这些分散的技术组合在一起,构建了一个让进程以为自己拥有一台独立计算机的"谎言"。
制造隔离的幻觉:Namespaces
在宿主机看来,容器内运行的程序其实只是一个普通的进程。你可以通过命令在宿主机上清晰地找到它的身影,看到它的进程 ID。但在容器内部,这个程序却觉得自己是系统里唯一的王,拥有独立的进程 ID(通常是 PID 1),拥有独立的主机名,甚至拥有独立的网络接口。
这种"楚门的世界"般的隔离效果,归功于 Linux 的 Namespaces(命名空间)技术。Linux 内核允许我们将系统的全局资源封装在不同的 Namespace 中,原本属于全局可见的资源,现在变成了只有特定进程才能看到的局部资源。
当你启动一个容器时,Docker 实际上是调用了内核的 clone 系统调用,并传入了特定的标志位,创建了一组新的 Namespace。
c
clone(child_stack=0, flags=CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS | SIGCHLD)
通过 PID Namespace,容器内的进程看不到宿主机的其他进程;通过 Mount Namespace,容器拥有了独立的文件系统挂载点;通过 Network Namespace,容器获得了隔离的网卡和路由表。这层看不见的墙,保证了即便你在容器里执行了 rm -rf /,宿主机的系统文件依然安然无恙。
物理资源的阀门:Cgroups
如果只有 Namespaces,容器虽然看不见邻居,但它依然是个危险的租户。它可能会无限制地吞噬 CPU 周期,或者耗尽物理内存,导致宿主机和其他容器卡死。为了解决这个问题,Docker 使用了 Linux 的 Cgroups(Control Groups)技术。
Cgroups 就像是一个精准的阀门,它负责限制、记录和隔离进程组所使用的物理资源。当你使用 Docker 限制某个容器只能使用 512MB 内存或 1.5 个 CPU 核心时,Docker 就会在系统的 /sys/fs/cgroup/ 目录下相应的控制器中写入配置文件。
Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
内核会根据这些配置,强行限制进程的资源消耗。一旦容器内存使用超标,内核的 OOM Killer(内存溢出杀手)就会毫不留情地将其终结,从而保护宿主机的安全。这也解释了为什么在 Docker 中查看资源统计如此方便,因为数据直接来源于内核的计数器。
像洋葱一样的存储:UnionFS
Docker 另一个让人印象深刻的特性是镜像非常小,且下载速度快。这得益于它独特的分层存储架构,也就是 UnionFS(联合文件系统)。传统的虚拟机镜像是一个巨大的单体文件,包含完整的操作系统,而 Docker 镜像则是由一系列只读层堆叠而成的。
当你拉取一个 Ubuntu 镜像时,你下载的不是一个大文件,而是几个不同的层。这些层是只读的,并且可以在不同的镜像之间共享。如果你有十个基于 Ubuntu 的不同应用,磁盘上只需要存储一份 Ubuntu 的基础层。
bash
docker history nginx:latest
当容器启动时,Docker 会在这些只读层的最顶端,挂载一个薄薄的"读写层"。容器内发生的所有文件修改、新增,实际上都只发生在这个读写层中。
这采用了一种被称为"写时复制"(Copy-on-Write)的策略。如果你要修改底层的一个文件,Docker 并不会真的去改动只读层,而是将该文件复制到顶部的读写层,然后进行修改。容器读取文件时,看到的是读写层中的版本,底层的原始文件被"遮挡"了,但依然完好无损。
实际上的一行命令
理解了这些原理,再回看 Docker 的架构,你会发现它是一个标准的 C/S 模型。你敲击的命令行工具只是一个客户端,它将你的意图打包成 REST API 请求,发送给守护进程(Docker Daemon)。
现在的 Docker 架构已经高度解耦。Daemon 负责接收请求,containerd 负责管理容器的生命周期,而最终真正与内核交互、创建 Namespaces 和 Cgroups 的,是一个名为 runc 的轻量级工具。
所以,当你输入下面这行命令时,系统内部发生了一系列精密的连锁反应:客户端连接服务端,服务端检查镜像层,利用 UnionFS 堆叠文件系统,通过 runc 请求内核创建隔离空间,最后设置资源阀门,进程随即启动。
bash
docker run -d -p 80:80 nginx
这就是 Docker 的全部秘密。它不是什么深奥的魔法,而是对 Linux 现有能力的极致利用,解决了软件开发中最头疼的"环境一致性"问题,让代码在任何机器上都能以相同的姿态运行。