LabHub
学习 学习路径 课程

KCNA — Kubernetes 与云原生入门

从虚拟机到容器 — 舍了什么,得了什么

在 LabHub 中继续学习

一句话总结

容器不是“轻量虚拟机”。它是在共享内核的同时,借助 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)把三部分拆成了规范。

运行时分为两层

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 经常涉及其中三项。

在实际工作中会遇到的情况

作者的家庭实验室集群原本使用 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 开始实践。