七种命名空间与容器的真身
一句话总结
内核源码中不存在 struct container 之类的东西。容器只是人们为这样一种状态起的
名字:把命名空间 + cgroup + 安全层 + rootfs组合到一个进程上。其中,
命名空间只负责回答一个问题——这个进程能够看到什么。
为什么需要它
“创建容器”这种说法,让很多人以为内核某处真的有一个叫作容器的盒子。
然而在主机上运行 ps,会原样看到容器里的进程,内核版本也与主机完全相同。
如果真有盒子,就不该如此。实际情况恰恰相反:进程从一开始就普通地存在于主机上,
内核只是向这个进程展示某些资源清单的另一个版本。这不是隔绝,而是限制视野。
这种区别在实际工作中会立刻显现:容器内的 nproc 显示 32,实际上却只能用 0.5 个核;
两个容器各自监听 80 端口;容器内删除的文件仍留在主机上。要预测这些现象,
都必须知道“什么被分隔了,什么没有被分隔”。
它如何运作
命名空间共有 7 类。
| 类型 | 被分隔的内容 |
|---|---|
| pid | 进程号空间。成为 1 号进程后,还要负责处理信号和回收僵尸进程 |
| net | 接口、路由表、iptables 规则、套接字以及端口号空间的全部内容 |
| mnt | 挂载表。镜像 rootfs 之所以显示为 / 的原因 |
| uts | hostname |
| ipc | System V IPC、POSIX 消息队列 |
| user | UID/GID 映射。rootless 容器的核心 |
| cgroup | 让自己的 cgroup 位置看起来像根目录 |
重要的一点是,net 连端口号空间也会完整分隔。因此两个容器各自监听 80 端口也不会冲突。 端口冲突并不发生在容器之间,而发生在发布到主机的那一刻。
每个命名空间都有 inode 编号,对 /proc/<pid>/ns/<종류> 执行 readlink 就会显示该编号。
**编号相同,就属于同一个命名空间。**实际测量会看到如下结果。
호스트 pid:[4026531836] net:[4026531840] user:[4026531837] time:[4026531834]
alpine pid:[4026532500] net:[4026532562] user:[4026531837] time:[4026531834]
只有 user 和 time 相同。这是因为 Docker 默认不使用用户命名空间。 因此容器内的 root 与主机 root 同为 UID 0,而这正是下一门课程将讨论的安全问题之根源。
在实际工作中会遇到的情况
Kubernetes Pod 原样采用了这种结构。Pod 会先启动一个约 1MB、名为 pause 的容器,
由它持有 net/ipc/uts 命名空间。应用容器不会新建这些命名空间,而是加入其中,
仅让 mnt 和 cgroup 按容器分别拥有。因此同一 Pod 内的容器通过 localhost 通信并共享端口,
文件系统却各不相同。
在 Docker 中也能直接构造这种结构。--network container:<이름> 这条命令的含义正是
“加入别人的 net 命名空间”。只用 Docker 复现 sidecar 模式时,就会使用它。
命名空间何时消失
命名空间不是对象,而是只在仍有引用时存在的东西。其中最后一个进程结束, 并且再也没有任何东西持有它时,内核便会回收它。理解这种性质后, 容器运维中一些看似奇怪的现象就能得到解释。
**Pod 已消失,网络设置却仍存在。**这表示某处仍持有该命名空间。
持有方式有三种:在其中运行的进程、保持打开 /proc/<PID>/ns/<종류> 的文件描述符,
以及挂在该路径上的绑定挂载。最后一种正是 ip netns 使用的方式,
因此即使所有进程都已结束,只要名字仍然存在,命名空间也会继续存在。
**容器已经删除,名字却一直保留。**原因相同;在实际工作中,通常是清理流程没有卸载该绑定挂载。
人们也会反过来利用这种性质:提前创建一个没有任何进程的命名空间, 之后再让进程加入。前面看到的 pause 容器做的正是这件事。即使应用容器死掉后重新启动, **网络命名空间仍保持存活,所以 IP 不会改变。**Pod IP 能经受容器重启,原因就在这里。
还要明确一点:命名空间不会划分资源。它只决定能看到什么,能用多少则由 cgroup 决定。 因此,仅靠命名空间无法阻止一个容器耗尽 CPU。容器之所以看起来相互隔离, 是因为两者同时生效;只应用其中一个,另一半仍会原样泄漏。
后续实验要做什么
亲自比较主机与容器的命名空间 inode,确认谁是 PID 1,并逐一检查 hostname、 文件树和接口列表分别因哪个命名空间而不同。最后让两个容器共享 net 命名空间, 亲手复现 Pod 的结构。