容器实现原理 —— 导读(namespace + cgroup + rootfs 三大支柱)
这是"容器"文档簇的导读。承接内核底座 namespaces-cgroups:容器不是虚拟机,而是一组被 namespace 隔离视图、被 cgroup 限额、和宿主共享同一个内核的普通进程。本篇站在框架层把"一个容器是怎么拼出来的"讲透——三大支柱、启动全流程、镜像/容器/运行时的概念澄清——每块深入之处点到对应分篇。
零、一句话认知
容器 = 一个受隔离(namespace)、受限额(cgroup)、看到独立文件系统(rootfs+overlayfs)的普通进程,和宿主共享同一个内核——不是虚拟机(没有 guest 内核)。 记住这一条,下面所有机制都是围绕"怎么把一个普通进程装扮成'它以为自己独占一台机器'"展开的。
一、三大支柱(核心)
一个容器靠三件事撑起来:namespace 决定"看得见什么"(隔离)、cgroup 决定"能用多少"(限额)、rootfs+overlayfs 决定"文件系统长什么样"(镜像视图)。三者正交,都作用在普通进程上。
1. namespace —— 隔离(看得见什么)
8 种 namespace 各裁剪一类系统视图,让进程以为自己独占系统。这块在内核底座篇已完整展开,这里只列一句话:
| namespace | 一句话 |
|---|---|
| PID | 容器内进程从 PID 1 编号,看不到宿主/别的容器 |
| NET | 独立网卡/IP/端口/路由,容器有自己的 eth0 |
| MNT | 独立挂载树,容器的 / 是自己的镜像 |
| UTS | 独立主机名/域名 |
| IPC | 隔离共享内存/信号量/消息队列 |
| USER | uid/gid 映射,容器内 root ≠ 宿主 root(安全关键) |
| CGROUP | 隐藏容器所属 cgroup 的真实层级 |
| TIME | 独立的系统时钟偏移 |
一句话:namespace 是"隔离视图",8 种各管一类,让进程看到被裁剪过的系统。种类、内核细节、
unshare/nsenter观测见 namespaces-cgroups §一。
2. cgroup —— 限额(能用多少)
cgroup 把一组进程归到一起,对它们的资源用量限额 + 计量:
| 控制器 | 限什么 |
|---|---|
| cpu / cpuset | CPU 配额(cpu.max)、权重、绑核 |
| memory | 内存上限(memory.max),超限触发 cgroup 内 OOM |
| io | 块设备读写带宽/IOPS |
| pids | 最多创建多少进程(防 fork 炸弹) |
一句话:cgroup 是"资源限额",K8s 的 CPU/内存 limit 就落在
cpu.max/memory.max。v1/v2 层级、观测命令见 namespaces-cgroups §二;配置走/sys/fs/cgroup,见 kernel-tuning。
3. rootfs + overlayfs —— 镜像(文件系统视图)
前两根支柱在底座篇讲透了,这根本篇展开。容器要有自己的 /(根文件系统),但又不能每起一个容器就拷贝一整份几百 MB 的镜像——秒启动、省空间靠的是镜像分层 + overlayfs 叠加。
- 镜像分层(只读 layers):一个镜像是若干只读层叠起来的。比如
FROM ubuntu是一层、apt install是一层、COPY 应用又是一层。每层只记录相对上一层的增量改动。多个镜像/容器共享底层公共层(如都基于 ubuntu 就只存一份),这就是省空间的来源。 - overlayfs 叠加:overlayfs 是 Linux 内核的联合挂载(union mount)文件系统,把**多个只读的 lowerdir(镜像层)+ 一个可写的 upperdir(容器可写层)**叠成一个统一视图(merged),挂到容器的
/。 - copy-on-write(CoW):容器读文件时直接读下面的只读层;一旦要写某个文件,才把它从只读层"上拷"(copy-up)到可写层再改——只读层始终不变、可被所有容器共享。这与 process-creation §二讲的 fork CoW 是同一种"谁写谁复制"的省资源思想,只是一个作用在物理内存页、一个作用在文件。
- pivot_root 切进去:rootfs 叠好后,runtime 用
pivot_root(或早期的chroot)把容器进程的根目录切换到这个 merged 视图——从此容器进程眼里的/就是镜像根,再也看不到宿主的文件系统。
一句话:镜像是一叠只读层,overlayfs 把只读层 + 每容器一份的可写层叠成
/,写时才 copy-up——只读层共享所以省空间、不用拷贝所以秒启动,最后pivot_root把进程切进这个根。
二、一个容器怎么拼出来(启动全流程)
把三大支柱按顺序装到一个进程上,就得到容器。以 runc(OCI 运行时)视角看,起一个容器大致是这几步:

- clone(CLONE_NEW)*:创建带一组新 namespace 的进程——这就是隔离视图的起点(clone flags 见 process-creation §三)。
- 挂 overlayfs rootfs + pivot_root:把镜像只读层 + 可写层叠成容器的
/,切根进去(本篇 §一.3)。 - 配网络:创建
veth pair(虚拟网线对),一端塞进容器的 net namespace 当eth0、另一端接到宿主网桥(如docker0),容器由此能通网。 - 加入 cgroup 设限:把进程放进新 cgroup 节点,设好 CPU/内存/IO/pids 上限。
- execve 入口:执行镜像里指定的入口程序(如
nginx)。至此进程"看到裁剪的系统 + 独立 rootfs + 资源有上限"——就是一个容器。
一句话:runc 起容器 = clone 出带新 namespace 的进程 → 叠 rootfs 切根 → 配 veth 通网 → 塞进限额 cgroup → execve 入口,五步把三大支柱装到一个普通进程上。
三、镜像 vs 容器 vs 运行时(概念澄清)
日常说"容器"其实混了几个层次的概念,澄清如下:
| 概念 | 是什么 | 类比 |
|---|---|---|
| 镜像(image) | 静态的、只读的分层文件包(rootfs 各层 + 元数据),存在磁盘/仓库里 | 程序的"可执行文件" |
| 容器(container) | 镜像的一个运行实例——由镜像叠出可写层 + namespace/cgroup 跑起来的进程 | 可执行文件跑起来的"进程" |
| 运行时(runtime) | 负责把镜像变成运行中容器的软件,分层:见下 | 加载并运行程序的"加载器/调度器" |
| OCI 标准 | Open Container Initiative:统一了镜像格式和运行时接口,让镜像和运行时可互换 | "可执行文件格式(如 ELF)+ 系统调用约定" |
运行时是分层的(从底到高):
- runc:最底层的 OCI 运行时,真正调
clone/pivot_root/cgroup 那些系统调用把容器跑起来。 - containerd:上一层的容器管理守护进程,管镜像拉取、生命周期,调用 runc 干脏活。
- dockerd(Docker Engine):再上层,提供 docker CLI、构建镜像、网络/存储编排,底下用 containerd。
一句话:镜像是静态分层文件(像可执行文件)、容器是它的运行实例(像进程)、运行时(dockerd→containerd→runc 逐层下沉)负责把前者变成后者,OCI 标准让镜像格式和运行时接口通用可换。
四、路由表 —— 容器簇怎么读
| 想搞清的问题 | 去哪篇 |
|---|---|
| 容器和虚拟机到底差在哪、KVM 原理、安全容器选型 | container-vs-vm |
| 容器性能开销、和裸机差多少、怎么测 | performance |
| namespace 8 种细节 + cgroup v1/v2 + 观测命令(内核底座) | namespaces-cgroups |
| clone/CLONE_NEW* flags、fork 的 CoW 原理 | process-creation |
cgroup 配置入口(/sys/fs/cgroup) | kernel-tuning |
为什么容器内 /proc/cpuinfo/meminfo 是宿主值(监控坑) | procfs |
五、一句话总结
容器 = 一个受 namespace 隔离视图、受 cgroup 限额、看到 overlayfs 叠出的独立 rootfs 的普通进程,和宿主共享同一个内核(非虚拟机)。三大支柱:namespace 管"看得见什么"(8 种视图裁剪)、cgroup 管"能用多少"(CPU/内存/IO/pids 限额)——这两根是内核底座、详见 namespaces-cgroups.md;rootfs+overlayfs 管"文件系统视图"——镜像是一叠只读层,overlayfs 把只读层+每容器可写层叠成
/,写时才 copy-up(与 fork 的 CoW 同思想),所以共享省空间、免拷贝秒启动,最后 pivot_root 切根进去。runc 起容器五步:clone(CLONE_NEW)→ 叠 rootfs 切根 → veth 通网 → 塞进限额 cgroup → execve 入口。概念上镜像(静态分层文件)/容器(运行实例)/运行时(dockerd→containerd→runc 逐层下沉)分工明确,OCI 统一格式与接口。容器 vs 虚拟机看 container-vs-vm.md,性能看 performance.md。*