隔离边界在哪里
一句话总结
VM 通过硬件边界实现隔离,容器则通过**内核功能(namespace/cgroup)**实现隔离。其余差异都可以从这句话推导出来。
为什么需要了解这些
“该用容器还是 VM?”至今仍是常见问题。答案并不取决于工作负载本身,而取决于需要哪一种隔离。
工作原理
[VM] [컨테이너]
앱 앱
게스트 라이브러리 라이브러리
게스트 커널 <- 별개! (호스트 커널을 공유)
가상 하드웨어 namespace + cgroup
하이퍼바이저 컨테이너 런타임
호스트 커널 호스트 커널
容器没有客户机内核。因此启动速度快(几十毫秒)、几乎没有内存开销,而且部署密度高。但代价是,一个内核漏洞就可能彻底击穿隔离。
| 项目 | VM | 容器 |
|---|---|---|
| 内核 | 每个客户机各自独立 | 与宿主机共享 |
| 启动时间 | 几十秒 | 几十毫秒 |
| 内存开销 | 客户机内核 + 虚拟机监控器 | 几乎没有 |
| 密度 | 每台宿主机数十个 | 每台宿主机数百个 |
| 隔离强度 | 强(硬件边界) | 弱(内核边界) |
| 不同 OS/内核 | 可以 | 不可以 |
| 镜像大小 | GB | MB |
何时选择哪一种
应当选择 VM 的情况
- 在同一宿主机上运行彼此不信任的租户(多租户)
- 需要不同的内核版本或不同的 OS(Windows、依赖旧内核)
- 需要加载内核模块或修改内核参数
- 合规要求硬件级隔离
应当选择容器的情况
- 需要大量运行同一组织内可信的工作负载
- 发布周期短,而且启动时间很重要
- 希望采用基于镜像的不可变基础设施
两者之间——microVM
Firecracker、Kata Containers 等技术填补了二者之间的空白。它们在保留 VM 隔离边界的同时,将启动时间和开销降低到接近容器的水平。通过极度精简设备模型(只保留少量 virtio 设备)并缩短启动路径,可以在约 100 毫秒内启动。AWS Lambda 就运行在 Firecracker 之上。
Kata Containers 又向前迈了一步:它保留容器接口(OCI/CRI),内部实际启动轻量 VM。从 Kubernetes 视角看,它只是一个普通 Pod,实际上却由 VM 边界隔离。可以通过 RuntimeClass 为不同工作负载分别选择,因此能够实施“只对这个命名空间使用强隔离”等策略。
生产现场中的常见情况
“我需要在容器里修改内核参数,但无法操作。” 这是必然的。由于内核与宿主机共享,在容器内执行 sysctl -w 会影响整台宿主机,因此大多数情况下会被禁止。如果确实需要,应在节点级设置(DaemonSet、节点引导),或改用 VM。
这个实验环境本身就是例子。 容器移除了 capability,因此无法执行 mount、tcpdump、iptables。本课程没有绕过这些限制,而是把限制本身作为教材——理解某项操作为什么需要特权,也就是理解隔离结构。
密度与资源管理为何不同
VM 和容器管理资源的方式也不同,这些差异在运维中经常令人意外。
VM 的内存会被预先占用。 分配 4GB 的 VM,无论内部实际使用多少,都会在宿主机上占用相应容量(虽然气球驱动等机制可以返还一部分,但并非即时完成)。相较之下,容器只占用实际使用的内存。 限额只是上限;没有使用的部分可以由其他容器使用。因此,容器的实际总用量远低于请求量总和是正常现象,这正是其高密度的来源。
但容器也要承担过量分配的风险。 如果所有限额之和大于节点内存,而多个容器又同时大量用内存,节点就会进入内存压力状态,内核会选择并终止进程。此时被终止的并不一定是绝对用量最大的进程,而是相对于请求量超用最多的进程,因此未设置请求量的工作负载会最先被牺牲。
CPU 的性质又有所不同。 CPU 与内存不同,稍后还能重新分配,所以超出限额时不会终止进程,而会通过前面介绍的节流使其变慢。因此,通常建议CPU 限额留得宽裕,内存限额设得准确。
在 VM 内运行容器时,两层资源管理会相互叠加。 分配给 VM 的内存会成为节点的全部内存,内部容器再共享这些内存。如果 VM 层启用了内存回收机制,节点并不知道自身内存已经减少,仍会继续调度,最终导致难以预测的故障。这正是通常建议在虚拟化节点中关闭内存回收功能的原因。
前面介绍的 microVM 也是两者的折中方案。它采用 VM 隔离,但内存开销很小,可以在不过多牺牲密度的情况下获得内核边界。
下一步要做什么
下一个课程将介绍 rootless 容器。深入理解容器为何能在没有特权的情况下运行(user namespace)后,本模块所说的“通过内核边界隔离”就会落实为具体的系统调用和文件。