现代系统几乎都跑在云上、容器里。你可能天天用 Docker,却未必想过:Docker 凭什么能做到"秒级启动、互相隔离"? 它和虚拟机有什么本质区别?这一课,我们把 Docker 的"黑魔法"拆开看——你会发现,它调用的全是 Linux 内核早就有的能力。这是第八阶段(虚拟化与容器)的唯一一课,也是全书倒数第三课。

从"一栋楼 vs 一套房"说起

要把"一台机器当多台用",有两条路:

  • 虚拟机(VM):在真机器上,用软件模拟出一台完整的电脑,再在上面装操作系统;
  • 容器(Container):不模拟硬件,而是在同一个内核里,划出几个"互相看不见的小房间"。

生活类比:

虚拟机 = 一栋楼里装多套带独立水电表的房子(每套都有自己的水电表、自己的内核);容器 = 同一套房子里,用隔断分出多个工位(共享一套水电,只是彼此看不见)。

这就是两者的本质分野。


29.1 虚拟化 vs 容器

虚拟化(VM)容器
隔离层模拟整台硬件共享内核,隔离进程视图
开销大(每个 VM 一套 OS)小(秒级启动、几乎无损耗)
代表VMware、KVMDocker、Podman
一句话理解:虚拟机是"软件模拟出一台真电脑"(每个 VM 都有自己的内核);容器是"同一个内核里划出多个互相看不见的小房间"。
  • 虚拟机:用 hypervisor(虚拟机监视器) 模拟 CPU、内存、磁盘等整套硬件,每个 VM 里跑一个完整的操作系统内核。隔离最彻底,但开销巨大——每个 VM 都要一份 OS,启动慢、占资源;
  • 容器共享宿主机的内核,只是通过内核的 namespace/cgroup 让进程"以为"自己独享一台机器。启动秒级、开销几乎为零
一句话:VM 隔离强但重,容器轻但共享内核。 这也是为什么现代互联网大量用容器——能在一个内核上跑成百上千个"小房间"。

29.2 namespace——"隔离"

一句话理解:namespace 让进程看到不同的世界——每个 namespace 里的进程以为自己拥有独立的 PID、网络、文件系统、用户,其实底层都共享同一个内核。

容器的"隔离"靠 namespace。它给进程提供一套独立的"视图":进程在 namespace 里,只能看到属于自己的那部分资源。

常见的七种 namespace:

namespace隔离什么
PID进程号(容器里 PID 从 1 开始)
NET网络栈(独立 IP、端口)
MNT挂载点(独立文件系统视图)
UTS主机名
USER用户/权限
IPC进程间通信资源
Cgroup资源配额的视图
例子:在容器里 ps,只能看到自己那少得可怜的进程,PID 从 1 开始——因为 PID namespace 把它隔离了。它看不到宿主机上其他进程。

一句话:namespace 管"隔离"——让进程以为自己独占一个世界。


29.3 cgroup——"限资源"

一句话理解:隔离只解决"看不见",cgroup 解决"别乱抢"——限制一组进程最多用多少 CPU、内存、磁盘 I/O。

namespace 让进程"看不见别人",但没解决"抢资源"——一个容器里的进程照样能把整台机器的 CPU 吃满,影响所有容器。

cgroup(control group,控制组) 就是干这个的:

  • 把进程分组
  • 给某组设 cpu.maxmemory.max配额
  • 超了就被限速,甚至触发 OOM(内存超限被杀)。
一句话:namespace 管"隔离",cgroup 管"配额",两者合起来就是容器的两个基石。

29.4 镜像与分层文件系统

一句话理解:Docker 镜像是一层层"只读"文件系统叠起来的(OverlayFS),每层只记录差异,共享底层镜像,所以省空间、秒级启动。

Docker 镜像为什么能秒级启动、还特别省空间?靠分层文件系统

  • 镜像由多层只读文件系统叠成(底层技术叫 OverlayFS);
  • 每一层只记录和上一层的差异
  • 多个镜像可以共享同一层(比如都用 ubuntu 基础镜像,那这一层只存一份)。
例子:docker image ls 看到的镜像大小,远小于各层之和——因为层是复用、去重的。10 个镜像可能共享同一个 70MB 的 ubuntu 底层,只各自多一层几十 KB 的差异。

29.5 Docker 的内核依赖清单

一句话理解:Docker 不是魔法,它调用的全是 Linux 内核既有能力——clone 带 namespace 标志创建容器进程、cgroup 限额、OverlayFS 做镜像层、chroot/pivot_root 换根目录。

把 Docker 拆开,它底下就四样东西:

  1. clone + namespace 标志:创建容器进程时,带上各种 namespace 标志,让它"进自己的小房间";
  2. cgroup:给这个容器进程组设 CPU/内存配额;
  3. OverlayFS:把镜像层叠起来;
  4. chroot / pivot_root:把容器的根目录切到镜像里,让它只看到镜像里的文件系统。
一句话:Docker = namespace(隔离)+ cgroup(限额)+ OverlayFS(镜像)+ chroot(换根)。它没有发明任何新内核技术,只是把这套繁琐的流程自动化、标准化了。

动手实验:亲手"造"一个容器

# 1. 用 unshare 造一个"独立世界"(PID + 挂载 + 主机名隔离)
sudo unshare --pid --fork --mount --uts --mount-proc bash
hostname my-container          # 改主机名,不影响宿主机
ps -ef                         # 只看到容器里的进程
exit

# 2. 用 cgroup 限制 CPU(v2)
sudo mkdir /sys/fs/cgroup/demo
echo "50000 100000" | sudo tee /sys/fs/cgroup/demo/cpu.max   # 最多用 50% CPU
echo $$ | sudo tee /sys/fs/cgroup/demo/cgroup.procs
# 跑个死循环,观察 CPU 被压在 50%
yes > /dev/null &
top -bn1 | head -5

# 3. 限制内存
echo "100000000" | sudo tee /sys/fs/cgroup/demo/memory.max   # 100MB
一句话理解unshare 演示了"隔离"(容器前半段),写 cgroup 文件演示了"限额"(容器后半段)——Docker 无非是把这些自动化了。
  • 第一条 unshare 命令,让你进到一个独立的 PID/挂载/主机名世界里,ps 只能看到自己——这就是 namespace 的隔离效果;
  • 后面写 /sys/fs/cgroup/demo/cpu.max,把某个进程组的 CPU 上限压到 50%——这就是 cgroup 的限额效果。
体会一下:你刚才手动做了一遍 Docker 干的事。Docker 只是把这些步骤包装成了 docker run

深入点:两个进阶话题

① 容器的安全边界弱于 VM

容器共享内核,一旦内核被攻破,所有容器沦陷;VM 有硬件级隔离,更安全。所以高安全等级场景仍用 VM,或两者结合(VM 里跑容器)。

这也解释了为什么"多租户高隔离"场景(比如不同公司共用一台物理机)更倾向 VM,而"同一家公司内部跑微服务"用容器就够了。

② OCI 与 runc

Docker 底层用 containerd + runcrunc 本质就是帮你把 unshare/cgroup 那套流程打包成标准(OCI 规范),Kubernetes 时代这些组件已经标准化。

OCI(Open Container Initiative)是一套容器标准runc 是它的一个参考实现。这保证了不同厂商的容器工具能互通——Kubernetes 能调度 Docker、containerd、Podman 等不同运行时,靠的就是这套标准。


小结与思考题

这一课,我们拆开了 Docker 的"黑魔法":

  • 虚拟化 vs 容器:VM 模拟整台硬件(重、隔离强),容器共享内核(轻、隔离弱);
  • namespace:管"隔离",让进程以为独占一个世界;
  • cgroup:管"配额",限制 CPU/内存/IO;
  • 分层文件系统(OverlayFS):层叠只读镜像,省空间、秒级启动;
  • Docker 内核依赖清单:namespace + cgroup + OverlayFS + chroot。

留三个问题:

  1. 虚拟机和容器最本质的区别?
  2. namespace 和 cgroup 各解决什么问题?
  3. Docker 依赖内核的哪些特性?
到这里,第八阶段(虚拟化与容器,29 课)完成。至此,你从"进程怎么跑"一路学到了"容器怎么隔离",理论已经齐了。但真正的高手,不只是"懂原理",更要会动手排障——系统卡了、内存满了、程序挂了,你怎么定位问题在哪?下一课,我们进入第九阶段:实战收束,先学一套"用命令解剖操作系统"的排障功夫,最后亲手写一个迷你 shell,把全书知识合流。

标签: 计算机基础, 操作系统, Linux

添加新评论