先看一个框架图,有疑问我们可以评论区或者加微信群一起讨论
第一章:容器的本质——它从来不是“虚拟机”
在讨论容器之前,必须先纠正一个常见但具有误导性的认知:容器并不是“轻量虚拟机”。虚拟机的核心在于硬件虚拟化 + 独立内核,而容器既不虚拟硬件,也不复制内核。它所做的事情更接近于:在同一个内核之上,构造多个彼此隔离的进程运行环境。
从内核视角看,系统中不存在“container”这一数据结构。所有容器最终都会落到 task_struct 上,也就是普通进程。容器不过是在创建进程时,附加了一组额外的约束与重映射信息,这些信息决定了进程“能看到什么”和“能使用多少资源”。因此可以用一个极度压缩但非常准确的表达式来描述容器:
container = process + namespace + cgroup + rootfs
这四个组成部分分别承担不同职责:进程是执行载体,Namespace 负责视图隔离,Cgroup 负责资源限制,rootfs 决定文件系统呈现。换句话说,容器并不是一种新资源,而是对已有资源的一种重新组织方式。
理解这一点,很多现象会变得清晰。比如有,为什么容器启动极快?因为它并没有启动一个“系统”,而只是启动了一个进程;为什么容器之间可以共享镜像?因为底层并没有复制完整文件系统,而是通过分层复用;为什么容器安全性有限?因为所有容器共享同一个内核,一旦内核被突破,所有隔离都失效。
进一步从执行路径来看,一个容器的启动,本质上就是一次特殊的进程创建过程。运行时调用 clone() 创建子进程,并在这个过程中指定 Namespace 标志;随后将进程加入对应的 Cgroup;再切换 rootfs;最后执行目标程序。这一过程并没有引入新的调度模型,也没有改变内核的执行语义,只是对“进程运行环境”做了重构。
所以啊,容器的本质就是一句话:容器是对进程运行上下文的系统性约束与重命名
这种设计带来的最大价值是“更轻”。它避免了虚拟机的硬件虚拟化开销,同时保留了足够的隔离能力,使得系统可以在同一内核之上高密度运行多个应用实例。这也是为什么容器能够成为云原生基础设施的核心。
但这种“轻量”是有代价的。由于缺乏内核级隔离,容器的安全边界天然弱于虚拟机;同时,资源调度完全依赖 Cgroup 与调度器的实现质量。如果这些机制设计不当,就会出现资源争抢、性能抖动等问题。
因此容器并不是一个单一技术,而是一组机制的组合体。接下来要理解的,是这组机制中最核心的两个:Namespace 与 Cgroup。
第二章:Namespace——隔离的本质“视图重构”
Namespace 的设计初衷不是限制资源,主要是是解决一个问题:如何让不同进程看到不同的“全局状态”。在传统 Unix 模型中,系统资源是全局共享的,例如 PID 空间、挂载点、网络栈等。Namespace 的出现,本质上是将这些全局资源“虚拟化”为多个实例,使不同进程组可以拥有独立视图。
实现方式并不复杂:内核将原本的全局结构改为“按 namespace 挂载”。当进程访问这些资源时,不再直接访问全局对象,而是通过当前 namespace 的指针进行间接引用。这样,同一段代码,在不同 namespace 中会访问到不同的数据,容器其实主要就是命名空间,理解这个很多面试中就会一语道破天机。
Namespace 的创建依赖 clone() 或 unshare(),通过一组标志位指定需要隔离的资源类型,例如:
clone(CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWUTS, ...);
这些标志对应不同的隔离维度。其中最关键的几个包括:
PID Namespace:隔离进程编号
Mount Namespace:隔离文件系统视图
Network Namespace:隔离网络栈
UTS Namespace:隔离主机名
IPC Namespace:隔离进程通信
以 PID Namespace 为例,它通过为每个 namespace 维护独立的 PID 映射表,使同一个进程在不同层级中拥有不同的 PID。容器内部看到的 PID 1,实际上是宿主机中的某个普通进程。这种“编号重映射”不仅实现了隔离,还为容器提供了“init 进程”的语义基础。
Mount Namespace 则通过复制 mount tree,使每个容器拥有独立的文件系统视图。配合 pivot_root 和 bind mount,可以构造出完全不同的 rootfs。容器内部看到的“/”,实际上只是宿主机某个目录的重新映射。
Network Namespace 更进一步,它为每个容器提供独立的网络协议栈,包括网卡、路由表、iptables 等。容器之间的通信通过 veth pair 连接,数据包在 namespace 边界被转发到宿主机,再通过 bridge 或路由发送出去。
这里Namespace 并不会复制数据,而是复制“引用”。多个 namespace 之间仍然共享同一内核和底层资源,这也是其轻量性的来源。但这也意味着,Namespace 只能提供“逻辑隔离”,而不能提供“资源隔离”。如果一个容器疯狂占用 CPU,其他容器仍然会受到影响。
因此,Namespace 解决的是“你看到什么”,而不是“你能用多少”。要解决后者,就必须引入下面这一套Cgroup机制。
第三章:Cgroup——资源控制的真正落点
Cgroup(Control Group)的出现,是为了解决一个非常现实的问题:在多进程环境中,如何对资源进行可控分配与隔离。Namespace 让进程“看起来独立”,但如果没有资源限制,这种独立是没有意义的,因为任何一个进程都可以耗尽系统资源。
Cgroup 的核心思想是:将进程组织为层级结构,并在每个层级上附加资源控制策略。与传统的“按进程控制”不同,Cgroup 是“按组控制”,这使得它非常适合容器场景。
在实现上,Cgroup 通过虚拟文件系统暴露接口:
每个子目录代表一个控制组,里面包含各种资源控制文件。容器运行时只需要将进程 PID 写入对应目录,即可完成绑定:
echo <pid> > cgroup/.../tasks
Cgroup 支持多种资源控制子系统:
CPU:通过 CFS 调度器控制时间片
Memory:限制内存使用并触发回收
Blkio:限制磁盘 IO 带宽
Pids:限制进程数量
以 CPU 为例,Cgroup 并不是简单地“限制使用率”,而是通过调整调度权重与时间片来影响调度器行为:
cpu.cfs_quota_us = 50000cpu.cfs_period_us = 100000
表示该组在每 100ms 内最多使用 50ms CPU 时间。
内存控制则更为复杂。每次内存分配都会调用 try_charge_memcg(),检查当前 cgroup 是否超出限制。如果超出,内核会尝试回收内存,若仍失败,则触发 OOM kill。也就是说,Cgroup 的控制是嵌入在内核关键路径中的,而不是事后统计。
这一点很重要啊:Cgroup 不是一个“监控工具”,他是一个调度与分配机制的参与者。它直接影响 CPU 调度、内存分配和 IO 行为。
在容器场景中,Cgroup 的作用可以总结为一句话:把资源从“共享”变为“可分配”,这会让多个容器能在同一系统中稳定共存,也不会互相拖垮了。
第四章:Namespace 与 Cgroup 的协同机制
单独使用 Namespace 或 Cgroup 都无法构成完整的容器模型。Namespace 只能隔离视图,但无法限制资源;Cgroup 可以限制资源,但无法提供独立环境。只有两者结合,才能形成闭环。
可以用一个简单的对比说明一下:
仅 Namespace: 看起来像隔离 → 实际资源共享 → 不安全仅 Cgroup: 资源受限 → 但环境混杂 → 不可用两者结合: 既隔离环境 → 又控制资源
这种协同关系体现在容器创建流程中。运行时首先创建 Namespace,使进程进入独立视图;然后将进程加入 Cgroup,限制其资源使用;最后再配置文件系统与网络,使其具备完整运行环境。
从内核路径看,这种协同是“松耦合”的。Namespace 和 Cgroup 并不知道彼此的存在,它们只是分别作用于进程的不同属性。但在运行时的编排下,它们形成了统一的语义:一个既独立又受控的执行单元。
这种设计的优点是灵活性。可以根据需求选择不同的隔离与控制策略,例如只使用部分 Namespace,或调整 Cgroup 配额。缺点是复杂性比较高,需要运行时正确组合这些机制,否则很容易出现安全或性能问题,这种安全问题已经网上爆出很多了,有兴趣的可以查查。
第五章:运行时——把机制变成产品
内核只提供能力,她不提供“容器”概念。真正把容器变成可用产品的是运行时(runtime)。典型架构包括:
docker(CLI)
containerd(管理)
runc(执行)
其中 runc 是最接近内核的部分,它直接调用系统调用完成容器创建。
一个典型的启动流程如下:
docker run → containerd → runc → clone → setns → cgroup attach → pivot_root → execve
每一步都对应内核机制:
clone:创建新进程并进入 namespace
setns:加入已有 namespace
cgroup attach:绑定资源控制
pivot_root:切换文件系统
execve:启动目标程序
文件系统层通常使用 OverlayFS,通过多层只读镜像叠加一个可写层,实现高效复用:
lowerdir(镜像层)upperdir(写层)merged(最终视图)
这种设计使容器启动几乎不需要复制数据,从而实现秒级启动,这种文件系统是很多架构都使用的,研究内核的朋友多关注学习下。
第六章:总结---容器的全部能力可以归结为三层:
Namespace → 隔离视图Cgroup → 控制资源Runtime → 编排执行
它的本质是重构进程运行环境。这种重构带来的最大价值,是在不引入额外内核的前提下,实现高密度部署与快速启动。
我们也要知道容器的边界:
我建了一个嵌入式Linux技术群,专门聊难题分析和求职面试,欢迎大家一起加入,共同解决工作中的疑难杂症问题
