零、这篇文章讲什么
第 01 篇说了 Docker 是"应用 + 环境"打包,image / container / registry 三件套。但当时我留了个疑问:容器到底是靠什么机制,能让一堆互不干扰的服务挤在同一台机器上跑的? 很多人(包括我)一开始以为容器就是"轻量虚拟机"——这篇就是要打破这个误会。
读完你会得到三样东西:
- 一个明确结论:容器不是 Linux 系统,它只是被"骗"了的 Linux 进程
- 三大支柱机制:namespace(隔离)、cgroup(限资源)、overlayfs(分层)
- 一张对比表:容器和虚拟机到底差在哪,以及一个残酷的安全现实
前置知识:第 01 篇的 image / container 概念。不需要系统编程经验。
一、容器到底是不是 Linux?
Docker 不是操作系统,容器也不是。 把话说死:
- Docker 是一个跑在 Linux 上的程序(守护进程 + CLI)
- 容器是运行在宿主机上的普通 Linux 进程
关键在"共享内核"这四个字。所有容器和宿主机共用同一个 Linux 内核,容器里没有自己的内核——这就是它和虚拟机最根本的区别,也是它轻量的来源。
顺带解答一个常见困惑:你在 Windows / Mac 上用 Docker Desktop,底层其实是起了一个轻量 Linux 虚拟机(WSL2),Docker 跑在那里面。所以容器最终还是在 Linux 上跑的——只是这层对你透明了。
二、先看一个被"骗"的进程
假设你在宿主机上 ps aux,会看到成千上万个进程。但容器里的世界不是这样。进到容器里执行:
docker exec -it node-app bash # 进到容器内部
ps -ef # 容器里看进程
hostname # 容器里看主机名
你会发现容器里的进程号从 1 开始、主机名是容器的名字——容器里的进程以为自己生活在一台独立的服务器上,完全不知道自己其实只是宿主机众多进程之一。
这就是容器隔离的本质:不是真的给你一台机器,而是"骗"你,让每个容器都以为自己独占了一台机器。 骗术由三个内核机制组成。
三、三大支柱机制
1. namespace:负责"骗"——隔离
namespace(命名空间)是 Linux 内核的功能,它把系统资源"复制"成多份视图,每个容器看到自己的那一份。隔离的对象是"全局资源",让进程以为这些资源是独占的:
| namespace | 隔离什么 | 效果 |
|---|---|---|
| PID | 进程号 | 容器里 PID 1,宿主机上是几千 |
| Network | 网络栈 | 每个容器有自己的 eth0、iptables |
| Mount | 挂载点 | 每个容器有一套自己的文件系统 |
| UTS | 主机名 | 容器里 hostname 是自己的名字 |
| IPC | 进程间通信 | 容器之间的消息队列互不相通 |
| User | 用户 | 容器里的 root 不等于宿主机 root |
一句话:namespace 负责"看不见"——容器 A 看不见容器 B 的进程、网络、文件系统。
2. cgroup:负责"管"——限资源
namespace 解决"看得见看不见",但它管不了一个问题:资源占用。如果一个容器里的进程狂吃内存,不管 namespace 怎么隔,都会拖垮宿主机。
cgroup(Control Groups,控制组)就是补这块的——限制每个容器能用多少 CPU、内存、磁盘 IO。它是"限速器",保证:
- 一个容器最坏只能吃掉它分到的配额
- 不会出现"一个容器把宿主机搞挂,所有容器陪葬"
一句话:cgroup 负责"不许抢"——给每个容器划好资源上限。
3. overlayfs:负责"省"——分层复用
第 01 篇讲过镜像分层。分层是怎么实现的?靠 overlayfs(联合文件系统):多个只读层叠起来,上面加一个可写层,容器只在这一层写。
image 的只读层们(多个容器共享)
└── 容器专属的可写层(每次改动写在这)
- 多个容器共享底层镜像 → 省磁盘
- 改动只进可写层 → 镜像不可变
- 删容器 = 扔可写层 → 一切回到最初
一句话:overlayfs 负责"省"——用写时复制(copy-on-write)实现镜像复用与容器可写。
四、和虚拟机对比:隔离的不是一个级别
有了三个机制,就能看清容器和虚拟机的本质区别了:
传统虚拟机:
┌────────────── 宿主机 ──────────────┐
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │VM1 │ │VM2 │ │VM3 │ │
│ │完整Guest│ │完整Guest│ │完整Guest│ │ ← 每个都自带内核
│ │OS+内核 │ │OS+内核 │ │OS+内核 │ │
│ └────┬───┘ └────┬───┘ └────┬───┘ │
│ Hypervisor(虚拟硬件层) │
└──────────────┬───────────────────┘
物理机
Docker 容器:
┌────────────── 宿主机 ──────────────┐
│ ┌────┐ ┌────┐ ┌────┐ │
│ │C1 │ │C2 │ │C3 │ ← 只有 app+依赖 │
│ │app │ │app │ │app │ 没有内核 │
│ └─┬──┘ └─┬──┘ └─┬──┘ │
│ └─ Linux 内核(共享)─┘ │
└──────────────┬───────────────────┘
物理机
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 虚拟化对象 | 虚拟硬件 | 虚拟操作系统接口(namespace) |
| 每个实例 | 完整内核 + 用户态 | 只有用户态,共享宿主内核 |
| 启动速度 | 分钟级 | 秒级 |
| 体积 | GB 级 | MB 级 |
| 资源开销 | 每台固定分配,有浪费 | 按需,利用率高 |
| 隔离强度 | 强(内核完全独立) | 弱(共享一个内核) |
一句话:虚拟机隔离的是"内核",容器隔离的是"进程"。
五、残酷的安全现实:容器隔离 ≠ 安全沙箱
这是最容易踩的认知坑。很多人以为容器带"隔离",就能当沙箱隔离不可信代码——不能。
因为所有容器共享同一个内核:
- namespace 挡的是"普通进程看不看得见",不是"攻击者攻不攻得破"
- 一旦宿主机内核有漏洞,攻击者可以从容器里逃逸(container escape),宿主上所有容器全部沦陷
- 虚拟机没有这个问题:Guest OS 被攻破也只影响那一个 VM,Hypervisor 是独立安全边界
所以安全等级上:虚拟机 > 容器。如果跑的是不可信的第三方代码、或者做多租户隔离,业界会用"安全容器"方案:
| 方案 | 思路 | 代表 |
|---|---|---|
| gVisor | 在容器外加一层用户态内核拦截系统调用 | |
| Kata Containers | 每个容器套一个轻量 VM | OpenStack 社区 |
| Firecracker | 微虚拟化,毫秒级启动的轻量 VM | AWS(Lambda / Fargate 就是它) |
六、小结
三大支柱 + 一条安全警告,一句话各一个:
| 机制 | 负责什么 | 一句话 |
|---|---|---|
| namespace | 隔离 | 骗进程"我独占一台机器",负责"看不见" |
| cgroup | 限资源 | 管住每个容器的 CPU/内存上限,负责"不许抢" |
| overlayfs | 分层复用 | 镜像共享 + 容器可写,负责"省" |
容器用"隔离强度"换来了"轻量高效";共享内核是它的本钱,也是它最大的安全软肋。