从虚拟机到容器 — 舍了什么,得了什么
一句话总结
容器不是“轻量虚拟机”。它是在共享内核的同时,借助 Linux 的两个功能(namespace 与 cgroup) 把进程限制起来。因此它速度更快,隔离强度却弱于 VM。这一句话是整个 KCNA 范围的根基。
为什么需要它
虚拟机会模拟硬件。它要完整启动另一套 guest OS,启动需要数十秒, 内核镜像与系统文件还会占用磁盘和内存。相当于为了部署一项服务,同时部署一整套操作系统。
真正的问题不只是“沉重”,而是部署单元与运行单元不一致。开发者制作的是应用, 交给运维的却是 VM 镜像。“在我的笔记本上能运行”正是在这个落差中产生的。
容器反过来提出问题:内核已经在主机上,直接复用它,只改变应用所看到的世界不就行了吗? 而改变这个“所见世界”的内核功能早已存在。
它如何运作
namespace——能看到什么
| namespace | 隐藏的内容 | 在容器中呈现的样子 |
|---|---|---|
| PID | 进程列表 | 自己的进程显示为 PID 1 |
| NET | 网络接口与端口 | 每个容器都有独立 IP 和端口空间 |
| MNT | 挂载树 | 镜像文件系统显示为根目录 |
| UTS | 主机名 | 容器名称成为主机名 |
| IPC | 共享内存与信号量 | 看不到其他容器的 IPC |
| USER | UID/GID 映射 | 内部是 root,外部是非特权用户 |
cgroup——能使用多少
namespace 切分的是“视野”,cgroup 切分的是“份额”。它按组限制 CPU 时间、内存上限、块 IO 和 PID 数量。
超过内存上限时,内核的 OOM killer 会在该组中杀死进程。Kubernetes 的
resources.limits.memory 最终就是作为这个 cgroup 值下发的。
**只有其中一项还构不成容器。**只有 namespace,旁边的容器仍可能耗尽内存,导致大家一起死亡; 只有 cgroup,则彼此的进程和文件全都可见。
OCI——谁持有标准
早期,“容器 = Docker”。如果由一家厂商同时掌握格式和运行方式,生态就无法成长, 因此 OCI(Open Container Initiative)把三部分拆成了规范。
- Image Spec——层与 manifest 的格式。因此用
docker build制作的镜像能在任意运行时上运行。 - Runtime Spec——接收展开后的文件系统(rootfs)和
config.json,再启动进程的规范。 - Distribution Spec——与 registry 通信的 HTTP API。
运行时分为两层
kubelet --(CRI/gRPC)--> containerd --(OCI Runtime Spec)--> runc --> 리눅스 커널
containerd 是高级运行时,负责接收并解包镜像、管理快照、跟踪生命周期。
runc 是低级运行时,读取 config.json,创建 namespace 与 cgroup,再 exec 进程,随后便立即退出。
容器运行期间,runc 进程并不存在。
containerd 的设计原则可以归纳为四点:简单(专注做好一件事)、插件化、兼容 OCI、提供 gRPC API。 所有功能都是插件,因此可以把 snapshotter 从 overlayfs 替换为 devmapper。
12-factor——适合放进容器的应用形态
12-factor 早于容器出现,但如今实际上可理解为“应用在容器中良好运行的条件”。 KCNA 经常涉及其中三项。
- 配置来自环境——各环境使用相同镜像,差异应来自注入的值。
- 进程无状态——状态放到所连接的后端服务中,容器必须能随时死亡。
- 可丢弃性(disposability)——快速启动,收到 SIGTERM 时从容完成清理并退出。
在实际工作中会遇到的情况
作者的家庭实验室集群原本使用 cri-dockerd 作为运行时。重建集群时迁移到了 containerd 1.7.27。这不是偏好问题,而是去掉了一个层级。 cri-dockerd 路径是 kubelet → cri-dockerd → dockerd → containerd → runc,包含两个额外的中间桥梁; 直接使用 containerd 时,kubelet → containerd → runc 就结束了。完成相同工作,却减少了两个进程。
还有一点。该家庭实验室重建后把内核统一到了 6.14。容器共享内核这件事, 在运维中实际意味着:节点内核版本就是容器可用功能的上限。 像基于 eBPF 的网络这种依赖内核功能的技术栈,内核版本太低时根本无法启用。
接下来阅读什么
本模块没有实验。下一篇读物会介绍 kubelet 与 containerd 之间的翻译器 CRI,
之后的模块将连接真实集群,从运行 kubectl api-resources 开始实践。