本页目录
命名空间
Namespace 是 Linux 容器的基础原语——每种 namespace 为进程提供一类资源的隔离幻觉:PID namespace 让容器里的进程以为自己是 PID 1,user namespace 把容器内的 root 映射为宿主机上的普通用户,network namespace 让每个容器看到独立的网卡和路由表。nsproxy 把多个 namespace 绑定为一个进程的"隔离视图"。 内核版本: 2.6 ~ 6.x
概述
Namespace 是 Linux 容器的基础原语——每个 namespace 为其中的进程提供资源隔离的幻觉。一个进程在 PID namespace 中看到自己是 PID 1,在 mount namespace 中看到独立的文件系统树,在 network namespace 中看到独立的网卡和路由表。
Namespace 与 cgroup 的互补关系:namespace 隔离可见性(看到什么),cgroup 限制资源量(能用多少)。Docker/LXC/Podman 就是在 namespace + cgroup + seccomp 堆叠之上构建的。
8 种 Namespace
| Type | Clone Flag | 隔离内容 | 引入 |
|---|---|---|---|
| Mount | CLONE_NEWNS | 文件系统挂载点,各自可以 mount/umount | 2.4.19 |
| UTS | CLONE_NEWUTS | hostname, domainname | 2.6.19 |
| IPC | CLONE_NEWIPC | SysV IPC (shm/sem/msg), POSIX mq | 2.6.19 |
| PID | CLONE_NEWPID | 进程 ID,ns 内 PID 1 看到自己为 init | 2.6.24 |
| Network | CLONE_NEWNET | 网卡, 路由, netfilter, socket | 2.6.24 (完成 2.6.29) |
| User | CLONE_NEWUSER | UID/GID 映射 (ns 内 root = host 普通用户) | 3.8 |
| Cgroup | CLONE_NEWCGROUP | cgroup 根目录 (容器内看不到 host cgroup 树) | 4.6 |
| Time | CLONE_NEWTIME | CLOCK_MONOTONIC/BOOTTIME 偏移 (每个 ns 独立时间) | 5.6 |
内核实现: nsproxy
// include/linux/nsproxy.h
;
每个 task_struct → nsproxy 指向该进程所属的各 namespace。fork() 时子进程共享父进程的 nsproxy(count++)。unshare() 时克隆 nsproxy 并将指定类型的 namespace 替换为新创建的。setns(fd) 将当前进程加入已有的 namespace(fd 来自 /proc/<pid>/ns/<type>)。
PID Namespace: 嵌套与 init 进程
PID namespace 形成嵌套树——父 ns 可以看到子 ns 的进程(在父 ns 中有普通 PID),子 ns 看不到父 ns 的进程。
每个 PID namespace 有自己的 init 进程(PID 1)。PID 1 的特殊责任:
- 孤儿收养: 父进程在子进程之前退出 → 子进程被 re-parent 到 PID 1
- wait() 循环: PID 1 必须主动
wait()回收僵尸,否则僵尸在 ns 内积累直到 PID 耗尽 - 不能被 kill: SIGKILL 对 PID 1 无效(除非从祖先 ns 发)。但 PID 1 可以被 SIGSTOP 暂停
- PID 1 退出 → 整个 ns 被销毁: 内核给 ns 中所有进程发 SIGKILL
容器中的 PID 1 通常是应用本身(如 CMD ["nginx", "-g", "daemon off;"])——它必须正确处理信号(SIGTERM)和孤儿回收。如果不做,僵尸进程会积累。
User Namespace: root 在 ns 内 = nobody 在 host
User namespace 是容器安全的基础——它允许非特权用户在 ns 内拥有 root 身份:
/proc/<pid>/uid_map:
ns_uid 0 → host_uid 1000 # ns 内 root = host 普通用户
ns_uid 1 → host_uid 100000 # ns 内普通用户 = host 从 100000 开始的 subuid
# 非特权用户创建 user ns (kernel.unprivileged_userns_clone=1):
$ unshare --user --map-root-user
# → ns 内你看到自己是 root, 但在 host 上你还是普通用户
关键安全特性:user ns 内的"root"只能操作该 user ns 拥有的资源——不能访问 host 文件、不能加载内核模块、不能 kill host 进程。这种"seeming root, actually jailed"使得 rootless 容器成为可能(Podman rootless mode)。
Mount Namespace: 独立的挂载树
每个 mount namespace 有独立的文件系统挂载点列表。一个 ns 中的 mount 不影响其他 ns。CLONE_NEWNS 创建时,子 ns 接收父 ns 的挂载点副本(不是共享)——之后各自独立修改。
共享子树 (shared subtrees, 2.6.15+): mount --make-shared /path 使得挂载事件在多个 ns 之间传播。Systemd 用这个机制在 host 和容器间共享 /run。
Network Namespace: 独立的网络栈
netns 是最常用的 namespace——每个容器需要一个。每 netns 有独立的:网卡列表、路由表、netfilter 规则、socket 绑定。ip netns add myns 创建新 netns(实际是 bind mount /var/run/netns/myns)。ip link set eth0 netns myns 把网卡移入 netns。
用户态 API
# 创建新 PID + mount + net namespace:
# --fork: 必须 — 否则新 PID ns 的 init 还是原来的 shell
# --mount-proc: 在新 ns 中 mount procfs → ps 只显示 ns 内进程
# 进入已有 namespace:
# 列出 namespace:
参考
- 源码:
kernel/nsproxy.c,kernel/pid_namespace.c,kernel/user_namespace.c,net/core/net_namespace.c,fs/namespace.c - 内核文档:
Documentation/admin-guide/namespaces/ - LWN: "Namespaces in Linux" 系列
Keywords: namespace, nsproxy, PID namespace, user namespace, UID mapping, mount namespace, network namespace, CLONE_NEWNS, container, setns, unshare