<!-- title: 内核里没有容器：只是个被骗的进程 -->
<!-- domain: programming -->
<!-- video: https://www.youtube.com/watch?v=VmeJSRF43ig -->

# 内核里没有容器：只是个被骗的进程

## 导语：你以为的"Docker魔法"，其实是Linux的一场骗局

你大概写过无数遍 `docker run`，看着一个环境在毫秒之间起好，心里默认"容器就是个轻量级虚拟机"。这个默认认知，恰恰是理解容器最大的障碍。

State & Flow 频道那支《How Docker Containers Actually Work — Namespaces, Cgroups, and Union Filesystems》之所以值得反复看，是因为它干了一件大多数 Docker 教程刻意回避的事：**把"容器"这个词从内核里彻底删掉**，让你看见底下真正在转的三样东西——namespace、cgroup、联合文件系统。

结论先抛出来：**Linux 内核里根本没有"container"这个对象**。你 `ps` 出来的那个进程，和你 `docker exec` 进去看到的进程，是同一个进程，跑在同一个内核上，挤在同一颗 CPU 上。它之所以显得"被关起来了"，纯粹是因为内核对它撒了一系列谎——告诉它"你是 PID 1""这台机器只有一张网卡""根目录长这样"。容器不是被造出来的，是**被裁剪出来的**。

理解了这一点，你才会明白为什么容器启动只要几毫秒、为什么跨内核架构跑不了、为什么"隔离"和"限流"是两件根本不相干的事。这篇文章就把视频里的内核三件套拆开揉碎，再补上视频没讲透的工程细节，最后落到通信人最关心的场景：5G 核心网为什么非要把网元塞进容器。

## 视频背景卡片

| 项目 | 内容 |
|------|------|
| 视频标题 | How Docker Containers Actually Work — Namespaces, Cgroups, and Union Filesystems |
| 频道 | State & Flow（专注云原生与系统底层原理解读） |
| 时长 | 深度讲解长视频，节奏紧凑、代码与图示穿插 |
| 播放量 | 技术圈高口碑，持续被引用为"最该补的一课" |
| 核心主题 | 容器不是虚拟机，本质是 namespaces + cgroups + union filesystem |

## 一、内核里压根没有"容器"这个东西

这是全片最反直觉的一句话，也是整个容器技术被发明的起点。你去翻 Linux 内核源码，搜 `container`，搜不到一个叫"容器"的系统调用。内核只提供了一批**原语**（primitive）：`clone()`、`unshare()`、`setns()`、cgroup 文件系统、OverlayFS……所谓"容器"，是用户态把这些原语按特定方式组合后**涌现**出来的效果。

Aqua Security 的 Liz Rice 在那场著名的《Containers from Scratch》演讲里，用大约 100 行 Go 代码就"造"出了一个能跑的容器。核心就一句：调用 `syscall.Clone` 时带上 `CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC` 这些标志位，再 `pivot_root` 换个根目录。**就这些**。没有魔法，没有 hypervisor，就是几个系统调用。

这个认知的价值在于：它把"容器"从一个黑盒名词，还原成了一组可解释、可调试、可单独开关的开关。下次你排查"为什么容器里看不到宿主机进程"，你就知道是 PID namespace 在干活，而不是去怀疑 Docker 出 bug。

## 二、容器不是小号虚拟机：差在那一层内核

很多人把容器类比成"瘦身版虚拟机"，这个比喻方便但有害。两者的分野只有一条：**谁拥有内核**。

虚拟机走的是**硬件级虚拟化**：hypervisor（KVM、ESXi）模拟出一套硬件，每个 VM 里跑一个完整的客户机内核（guest kernel）。一个 Ubuntu VM 里，那个 `systemd`、那套网卡驱动、那套调度器，全都是真实存在的第二份拷贝。所以 VM 动辄几个 GB，启动要几十秒——它真的在"开机"。

容器走的是**操作系统级虚拟化**：所有容器**共用宿主机的同一个内核**。容器里那个"内核"根本不存在，它调用的系统调用，直接被宿主机内核接住处理。所以一个 Alpine 镜像可以只有 5MB，容器可以毫秒级启动——因为它只是 `fork` 出一个进程，给它换了一副"眼镜"，根本没开机。

这条分野有一个非常硬核的推论，也是面试常考的点：**容器必须和宿主机共享内核架构**。你不能在 Linux 宿主机上原生跑一个 Windows 容器，因为内核对不上。这恰恰反证了"容器=轻量VM"是错的——如果真有独立的内核，架构本不该是约束。通信圈做 5G 云核心网时，所有 NF（Network Function）都必须基于同一套 Linux 内核镜像，根源就在这里。

## 三、Namespace：骗进程"你独占了整个世界"

如果说容器是一场骗局，namespace 就是骗术本身。它干的事情用一句话概括：**控制一个进程能"看见"什么**。

Linux 内核到现在一共提供了 8 种 namespace，每一种都对应一种"世界观"的裁剪：

- **mnt**（mount，2002 年最早引入）：你看到的挂载点和文件系统树。容器里的 `/` 其实是宿主机某个目录被"换根"后的视图。
- **pid**：进程号。容器里的进程自认为是 PID 1，看不到宿主机上几千个进程。
- **net**：网络栈。独立的网卡（其实是 `veth` 虚拟以太网对）、独立的路由表、独立的 iptables、独立的端口空间。两个容器都能监听 80 端口而不冲突，就靠它。
- **uts**：主机名和域名（UNIX Timesharing System 的缩写）。
- **ipc**：System V 信号量、消息队列、共享内存。
- **user**（2013 年加入）：UID/GID 映射，容器内的 root 在宿主机上是 nobody，这是 rootless 容器的基石。
- **cgroup**（2016 年，内核 4.6）：让进程看到一份"干净的"cgroup 视图。
- **time**（2020 年，内核 5.6）：最年轻的一个，隔离系统时钟偏移。

注意一个被反复忽略的事实：**这 8 种 namespace 是相互独立的**。你可以只给一个进程套上 net namespace（让它有独立网络），却不给它 pid namespace（它能看见宿主机所有进程）。实际上 `ip netns` 创建网络命名空间、Kubernetes 里 `hostNetwork: true`、Docker 的 `--network host` 都是在做"部分隔离"的取舍。namespace 不是非开即关的开关组，而是一个 8 维的布尔向量。

一个最直观的验证：你在容器里 `ps aux` 看到自己是 PID 1，但跑到宿主机上 `ps aux`，能找到同一个进程，只是它的 PID 是 4567。**进程只有一个，只是两副眼镜看到的编号不同。** 这就是"被骗的进程"的字面含义。

## 四、Cgroup：管的是"你能用多少"，跟隔离无关

namespace 解决了"看得见什么"，但完全没解决"能用多少"。一个只套了 namespace 的进程，理论上可以吃光宿主机全部内存、占满所有 CPU、fork 出十万个子进程把系统拖垮。这就是 cgroup（control group）登场的理由。

cgroup 干的是**资源记账与限制**：把进程分组，对每组施加配额。它的控制器（controller）按资源类型拆开：`memory` 限内存、`cpu`/`cpuacct` 限和统计 CPU、`blkio`（v2 里叫 `io`）限磁盘 IO、`pids` 限进程数、`devices` 控制能访问哪些设备文件、`net_cls`/`net_prio` 给网络流量打标签。你在 Docker 里写 `--memory=512m --cpus=0.5`，本质就是往 cgroup 文件系统里写两个数字。

cgroup 有 v1 和 v2 两代，区别不小：v1 每种资源一棵独立的层级树，资源之间没法联动（一个进程在内存树和 CPU 树里是两套路径，常打架）；v2 改成**统一层级**（unified hierarchy），一棵树管所有控制器，2014 年合入内核，到现在 Docker/K8s 才基本完成迁移。这事工程上坑极多，是很多"为什么我的 cgroup 限制不生效"问题的根源。

## 五、隔离和限流，是两条完全独立的轴

这是我这篇解读最想强调、也是视频点破的一个**概念性洞见**：**namespace（隔离）和 cgroup（限流）是正交的两条轴，谁也不包含谁。**

把它们摆在一个二维坐标系里就清楚了：

- 横轴 namespace：**看不见别人**（隔离了视野）。
- 纵轴 cgroup：**用不了那么多**（限制了消耗）。

一个真正的生产容器，必须两条轴都拉满：既看不见宿主机别的进程，也不能吃光内存。但现实中很多人把它们混为一谈，说"Docker 提供了资源隔离"——这句话是错的，或者说至少是不精确的。Docker 提供**隔离（namespace）+限制（cgroup）**，这是两件事。

正交性带来的实战推论很有意思：
- **只隔离不限流**：容器能看到的世界很小，但一个失控的 Java 进程照样能 OOM 整台宿主机，把隔壁容器一起带走（这是 K8s 早期"邻居噪音"问题的根）。
- **只限流不隔离**：一个进程被限了 CPU，但它能 `cat /etc/passwd` 读到宿主机所有用户、能看到所有进程的网络连接——限制毫无安全意义。

通信云核心网里，UPF（用户面功能）转发流量大、内存敏感，**cgroup 限流是性能 SLA 的命根子**；而 AMF/SMF 处理信令、涉及用户数据，**namespace 隔离是安全合规的底线**。同一种技术，两条轴的权重完全不同。理解了正交性，你才会针对性地配 resource request/limit 和 securityContext，而不是无脑复制 YAML。

## 六、镜像不是复制：OverlayFS 靠分层叠加

namespace 和 cgroup 解决了"进程"的问题，但容器还得有个"文件系统"——这就是视频标题里第三个关键词 Union Filesystem（联合文件系统）出场的地方。它解释了一个常被忽略的奇迹：**为什么 100 个容器基于同一个镜像，磁盘上只存一份。**

OverlayFS（以及早期的 AUFS、device-mapper thin pool）的核心是**分层 + 写时复制（Copy-on-Write）**。一个容器运行时的文件系统视图，由几层拼出来：

- **lowerdir**：只读的镜像层。Ubuntu 基础层、你装的 Python 层、依赖层……每一层都是只读的，多个容器共享同一份。
- **upperdir**：容器自己的可写层。容器里所有"改动"——新建文件、修改配置——都落在这里。
- **workdir**：OverlayFS 内部的工作目录。
- **merged**：把上述层叠起来后呈现给容器的统一视图。

关键机制是 **copy_up**：当容器要修改一个来自 lowerdir 的文件时，OverlayFS 不会去动只读层，而是**先把这个文件复制到 upperdir，再在副本上改**。这就是写时复制。

这套机制带来三个工程后果，每一个都和你日常相关：第一，**镜像可以共享层**，pull 一个新镜像时已存在的层会跳过，所以拉镜像才那么快。第二，**容器是临时的**，删掉容器，upperdir 里的改动全没了——这就是为什么"别把数据写进容器，要用 volume"。第三，**写性能有额外开销**，第一次修改一个只读层文件会触发 copy_up，IO 密集场景要留意。Docker 镜像之所以设计成一层层 `RUN` 指令，本质就是在和 OverlayFS 的分层模型对齐，把变动频繁的层放上面，提升共享率。

## 七、光有隔离还不够：seccomp 与 capabilities 补刀

namespace+cgroup+unionfs 已经能跑出一个像样的容器，但安全上还有两个洞，视频点到为止，我展开讲。

第一个洞：**系统调用没拦**。Linux 有大约 300 个系统调用，其中不少是给特权操作用的——`reboot` 重启机器、`ptrace` 窥探别的进程、`mount` 挂载文件系统。即便容器里的 root 映射成了宿主机的 nobody，有些 syscall 仍可能被滥用。**seccomp（secure computing mode）** 就是来堵这个洞的：它用 BPF 过滤器在内核里规定"这个进程只能调哪些 syscall"。Docker 默认的 seccomp profile 大约屏蔽了 44 个危险 syscall。

第二个洞：**root 权限太粗**。传统 Unix 的 root 是个二进制开关，要么全能要么无能。**Linux capabilities** 把 root 拆成了约 40 个细粒度权限，比如 `CAP_NET_BIND_SERVICE`（绑定 1024 以下端口）、`CAP_SYS_ADMIN`（几乎等于万能，尽量别给）、`CAP_NET_RAW`（发原始网络包）。容器安全的核心原则是 **drop all, then add only needed**——先把所有 cap 删掉，再按需加回去。K8s 的 Pod Security Standards 里 `restricted` 级别干的就是这件事。

把 seccomp、capabilities 和前面三件套叠在一起，才是一个真正生产可用的容器安全模型。这也是为什么"容器逃逸"（container escape）一直是云安全的高危话题：只要这些原语里任何一层配错，进程就可能突破谎言、看到真实世界。

## 八、从 docker run 到内核：一整条调用链

把前面所有零件串起来，你执行 `docker run nginx` 时，背后是这么一条链：

```mermaid
flowchart TD
    A["docker run nginx<br/>(CLI 命令)"] --> B["dockerd<br/>(守护进程，解析参数)"]
    B --> C["containerd<br/>(高层运行时，管生命周期)"]
    C --> D["runc<br/>(OCI 低层运行时，符合 runtime-spec)"]
    D --> E["Linux 内核原语"]
    E --> F["namespaces<br/>裁剪视野"]
    E --> G["cgroups<br/>限制资源"]
    E --> H["OverlayFS<br/>分层文件系统"]
    E --> I["seccomp + capabilities<br/>收窄权限"]
    F & G & H & I --> J(("一个'被骗'的普通进程<br/>= 容器"))

    style J fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style E fill:#fff3e0,stroke:#ef6c00
```

这条链对应一个行业标准 **OCI（Open Container Initiative）**，它定义了两份规范：`image-spec`（镜像长什么样）和 `runtime-spec`（容器实例长什么样）。`runc` 是 reference implementation。Docker、containerd、Kubernetes 的 containerd runtime、CRI-O，底层最后都汇聚到 runc，再汇聚到那几个内核原语。理解了这条链，你就理解了为什么"换容器运行时"（比如 Docker 换 containerd）对应用透明——因为 OCI 把接口标准化了。

## 知识结构图：容器的完整拼图

```mermaid
flowchart LR
    subgraph 神话["大众认知的'容器'"]
        M["一个黑盒<br/>≈ 轻量虚拟机"]
    end
    subgraph 真相["内核里真实存在的原语"]
        N["namespaces<br/>8种 · 管视野/隔离<br/>'看不见'"]
        C["cgroups<br/>v1/v2 · 管资源/限制<br/>'用不了'"]
        U["unionfs/OverlayFS<br/>分层+写时复制<br/>'磁盘省一份'"]
        S["seccomp + capabilities<br/>收窄syscall与特权<br/>'干不了坏事'"]
    end
    M -.被还原为.-> N
    M -.被还原为.-> C
    M -.被还原为.-> U
    M -.被还原为.-> S
    N --> P["正交的两条轴"]
    C --> P
    P --> R(("容器 = 普通进程<br/>+ 一组精心组合的内核谎言"))

    style M fill:#ffebee,stroke:#c62828
    style R fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style P fill:#e3f2fd,stroke:#1565c0
```

## 实战启示：知道是"被骗的进程"之后，你能做什么

**对通信工程师（这是写给你看的）：** 5G 核心网从 Release 16 起全面云原生化，AMF、SMF、UPF、PCF 全部以容器形态部署在 Kubernetes 上（即所谓的 CBA，Cloud-native Based Architecture）。理解 namespace+cgroup，你才看得懂为什么 UPF 要单独做资源隔离（cgroup 限 CPU/内存保转发 SLA），为什么不同租户的 NF 要强 namespace 隔离（多租户安全），为什么容器网络（CNI、Calico/VXLAN）本质是在 net namespace 之间架 `veth pair` 和路由。容器不是通信人可以跳过的"互联网技术"，它就是下一代核心网的运行底座。

**对后端/云原生开发者：** 第一，排查问题换个坐标系——容器里 OOM 了，先去宿主机看那个真实 PID 的 cgroup `memory.usage_in_bytes`，而不是只看应用日志。第二，写 Dockerfile 时脑子里要有 OverlayFS 分层图，把变动少的放底层、变动多的放上层，合并 `RUN` 减少层数，缩小镜像。第三，做安全加固时走"默认拒绝"——cap drop all、seccomp default、非 root 用户运行，按需放开，而不是反过来。

**对所有写代码的人：** 这套"内核不提供容器，只提供原语；容器是原语组合的涌现"的设计哲学，是 Unix 哲学的极致体现——**不要造大而全的新概念，要提供正交、可组合的小原语**。你设计自己的系统时，与其定义一个"插件"巨对象，不如想清楚有哪几条独立的轴，让用户自由组合。这正是容器能横扫整个软件交付链的根本原因：它没有发明任何新东西，只是把内核已有的几块乐高，拼出了一个所有人都想要的形状。

## 延伸学习路径

1. **Liz Rice —《Containers From Scratch》**（GopherCon 演讲 + GitHub `lizrice/containers-from-scratch`）：100 行 Go 手搓容器，看完"容器"祛魅彻底完成。
2. **《Linux namespaces》系列 — ENCCS 官方容器教程**（enccs.github.io/containers）：学术机构出品，namespace/cgroup 讲得最严谨免费教材。
3. **Henry S. Warren《Hacker's Delight》与内核文档 `Documentation/cgroups/`**：前者是底层位运算圣典，后者是 cgroup v1→v2 迁移权威说明。
4. **OCI 官网（opencontainers.org）+ runc 源码**：想真正理解 runtime-spec，没有比读 runc 的 `create.go` 更快的路。
5. **Julia Evans 的《How Containers Work》zine**（wizardzines.com）：手绘风把 namespace/cgroup/overlayfs 画得清清楚楚，适合视觉型学习者。

## 一句话总结

**Linux 内核里从来没有"容器"，只有一个被 namespace 捏造了世界观、被 cgroup 限制了配额、靠 OverlayFS 省下磁盘的普通进程——理解了这场精心设计的"骗局"，你就拿到了云原生时代的底层源代码。**

## 参考来源

1. [How Docker Containers Actually Work — Namespaces, Cgroups, and Union Filesystems](https://www.youtube.com/watch?v=VmeJSRF43ig) — 本文解读的核心视频（State & Flow 频道）
2. [Namespaces and cgroups — ENCCS Introduction to Containers](https://enccs.github.io/containers/namespc-cgroup/) — 容器原语权威免费教材，namespace 自 2002 年、容器支持 2013 年合入内核的出处
3. [containers-from-scratch — Liz Rice (GitHub)](https://github.com/lizrice/containers-from-scratch) — 100 行 Go 造容器的原始代码
4. [How Container Runtimes Work Internally: Namespaces, Cgroups, and the OCI Stack](https://letsbuildsolutions.com/blog/system-design/how-container-runtimes-work-internally-namespaces-cgroups-and-the-oci-stack-from-docker-run-to-process-isolation/) — docker→containerd→runc→内核 调用链详解
5. [Open Container Initiative (OCI)](https://opencontainers.org/) — image-spec 与 runtime-spec 标准定义

---
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
