核心思想
Namespace 的作用是隔离视图--不同 Namespace 中的进程看到不同的系统资源(进程列表,网络设备,文件系统等),但它们实际上运行在同一个内核上.这就是容器"轻量级隔离"的根基.
Namespace 类型总览
| Namespace | 隔离内容 | 系统调用标志 | 内核版本 |
|---|---|---|---|
| PID | 进程 ID | CLONE_NEWPID |
2.6.24 |
| Network | 网络设备,IP,端口,路由表 | CLONE_NEWNET |
2.6.29 |
| Mount | 文件系统挂载点 | CLONE_NEWNS |
2.4.19 |
| UTS | 主机名和域名 | CLONE_NEWUTS |
2.6.19 |
| IPC | 进程间通信(信号量,消息队列,共享内存) | CLONE_NEWIPC |
2.6.19 |
| User | 用户和用户组 ID | CLONE_NEWUSER |
3.8 |
| Cgroup | Cgroup 根目录视图 | CLONE_NEWCGROUP |
4.6 |
| Time | 系统时钟 | CLONE_NEWTIME |
5.6 |
其中前六种是 Docker 容器使用的核心 Namespace,后两种(Cgroup / Time)较新,容器场景中使用较少.
PID Namespace - 进程隔离
- 每个 PID Namespace 有独立的进程编号空间
- 子 Namespace 中的第一个进程 PID 为 1(类似 init 进程)
- 嵌套关系:父 Namespace 可以看到子 Namespace 的进程,反之不行
- 容器中
ps aux只能看到容器内的进程,就是 PID Namespace 的功劳
类比:PID Namespace 就像公寓楼的各个单元--每个单元内部的房间编号从 1 开始,互不冲突.你在自己单元里看不到隔壁的房间.
|
为什么需要 --fork?
进程的 PID Namespace 归属在它被创建(fork)的那一刻就固定了,之后不能更改--就像你出生在哪个国家,国籍就是哪个.unshare 进程本身属于旧 NS,它不能把自己"传送"到新 NS 里.加了 --fork 后,unshare 会 fork 一个子进程,这个子进程"出生"在新 NS 中,PID 为 1,然后由它来执行 bash.
为什么需要 --mount-proc?
ps 命令查看进程列表时,并不是直接询问内核内存,而是读取 /proc 这个特殊的虚拟文件系统(每个数字子目录对应一个进程,如 /proc/1/,/proc/1234/).如果不重新挂载,新 NS 里的 /proc 仍然是宿主机的那份,ps 看到的还是宿主机全部进程.--mount-proc 在新 NS 中重新挂载 /proc,让它只反映当前 NS 的进程.
Network Namespace - 网络隔离
每个 Network Namespace 拥有独立的:
- 网络设备(网卡)
- IP 地址
- 端口范围
- 路由表
- iptables 规则
- /proc/net 目录
容器间网络通信通常通过 veth pair(虚拟以太网对)+ bridge(网桥)实现(详见第 04 篇).
|
关键理解
Docker 容器的网络隔离就是 Network Namespace 的直接应用.每个容器有自己的网络栈,通过 veth pair 连接到宿主机的 docker0 网桥,实现容器间和容器与外网的通信.
Mount Namespace - 文件系统隔离
- 隔离文件系统挂载点,每个 Namespace 可以有独立的挂载视图
- 是最早实现 的 Namespace(2002 年,Linux 2.4.19)
- Docker 利用 Mount Namespace 让容器拥有独立的根文件系统(rootfs)
|
类比:Mount Namespace 就像给每个容器配了一套独立的"文件柜".容器里新放的文件,修改的文件,外面完全看不到,也不会影响其他容器的文件柜.
UTS Namespace - 主机名隔离
- 隔离主机名(hostname)和域名(domainname)
- 让每个容器可以有自己的主机名
- UTS = Unix Timesharing System,历史命名
|
IPC Namespace - 进程间通信隔离
- 隔离 System V IPC 和 POSIX 消息队列
- 不同 IPC Namespace 中的进程无法通过共享内存,信号量,消息队列通信
- 防止容器间通过 IPC 机制发生意外的数据泄露
|
User Namespace - 用户隔离
- 隔离用户 ID 和组 ID
- 关键特性:容器内的 root(UID 0)可以映射到宿主机的普通用户
- 这是实现无特权容器(Rootless Container) 的基础
|
安全意义
User Namespace 是容器安全的重要防线.即使攻击者在容器内获取了 root 权限,由于 UID 映射,在宿主机上他只是一个普通用户,无法造成真正的破坏.这就是 Rootless Container 的核心原理.
Namespace 操作的三个系统调用
| 系统调用 | 作用 | 场景 |
|---|---|---|
clone() |
创建新进程时同时创建新的 Namespace | 容器运行时(runc)创建容器进程 |
unshare() |
将当前进程移入新的 Namespace | 命令行工具 unshare,调试 |
setns() |
将当前进程加入已存在的 Namespace | docker exec 进入容器,nsenter 命令 |
|
利用 /proc/<pid>/ns/ 下的文件,可以让其他进程通过 setns() 加入已有容器的 Namespace--这就是 docker exec 和 kubectl exec 的底层原理.
小结:Namespace 与容器的关系
|
关键区别:容器 vs 虚拟机
容器只是通过 Namespace 实现了资源视图的隔离,所有容器共享同一个内核.而虚拟机通过 Hypervisor 运行独立的 Guest OS 内核,隔离更彻底但开销更大.容器的优势在于启动快(毫秒级),资源占用小,密度高.
本篇要点回顾
一句话总结
容器 = 正常的进程 + 被限制的资源视图. 容器内运行的进程,占用的资源,在宿主机的进程树和资源树中都只是一个普通节点.但容器内的进程因为 Namespace 的存在,看不见全貌--它"以为"自己独占了一整台机器.
|
要点回顾
| 要点 | 一句话概括 |
|---|---|
| Namespace 本质 | 内核提供的资源视图隔离机制,让进程看到独立的系统资源 |
| 6 种核心类型 | PID(进程)/ Network(网络)/ Mount(文件系统)/ UTS(主机名)/ IPC(通信)/ User(用户) |
| 三个系统调用 | clone(创建时隔离)/ unshare(当前进程进新NS)/ setns(加入已有NS) |
| 容器的本质 | 容器 ≠ 虚拟机,容器只是戴了滤镜的普通进程,宿主机上一览无余 |
| 安全意义 | User Namespace 实现 Rootless Container,容器内 root ≠ 宿主机 root |
动手练习
- 用
unshare --pid --fork --mount-proc bash创建 PID Namespace,运行ps aux观察效果 - 用
ip netns创建两个 Network Namespace,配置 veth pair 并验证 ping 连通 - 用
unshare --uts bash修改主机名,退出后确认宿主机不受影响 - 运行一个 Docker 容器,用
ls -la /proc/<容器PID>/ns/查看其 Namespace inode,与宿主机对比 - 思考题:为什么
docker exec能"进入"一个已运行的容器?底层调用了哪个系统调用?