Linux Namespace 隔离

FreeGuideOnline 最新 2026-07-13

bash ls -l /proc/$$/ns/


输出示例:

lrwxrwxrwx 1 user user 0 Apr 18 10:00 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 ipc -> 'ipc:[4026531839]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 mnt -> 'mnt:[4026531841]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 net -> 'net:[4026531840]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 time -> 'time:[4026531834]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 user -> 'user:[4026531837]' lrwxrwxrwx 1 user user 0 Apr 18 10:00 uts -> 'uts:[4026531838]'


方括号中的数字是 Namespace 的唯一标识符。如果两个进程的某个 Namespace 链接指向同一个 inode 号,说明它们处于同一个 Namespace 下。

可以通过 `readlink` 查看具体指向:

```bash
readlink /proc/$$/ns/uts
# 输出:uts:[4026531838]

实战:用 unshare 创建隔离环境

unshare 命令能让一个新进程脱离父进程的部分 Namespace,并可选地创建新的 Namespace 来运行程序。这是体验 Namespace 隔离最直接的方式。

基本语法:

unshare [options] <program> [arguments]

常用选项:

  • --mount / -m:新的 Mount namespace
  • --uts / -u:新的 UTS namespace
  • --ipc / -i:新的 IPC namespace
  • --net / -n:新的 Network namespace
  • --pid / -p:新的 PID namespace(通常需配合 --fork
  • --user / -U:新的 User namespace
  • --cgroup / -C:新的 Cgroup namespace
  • --fork / -f:执行程序前先 fork 出一个子进程(创建 PID namespace 时必须)

示例 1:隔离主机名 —— UTS Namespace

# 当前主机名
hostname

# 在新的 UTS namespace 中修改主机名
sudo unshare --uts /bin/bash
hostname my-container
hostname
# 输出 my-container

# 退出 bash
exit

# 回到宿主机,主机名未改变
hostname

在新的 UTS namespace 内,主机名被修改为 my-container,但退出后宿主机的名字纹丝不动。这就是 UTS namespace 的隔离效果。

示例 2:隔离进程视图 —— PID Namespace

创建新的 PID namespace 时,必须使用 --fork 才能让新进程变为新 namespace 的 init 进程(PID 1),否则程序会直接在当前 PID namespace 中运行。

sudo unshare --pid --fork --mount-proc /bin/bash

--mount-proc 会重新挂载 /proc 文件系统,这样 ps 等工具才能正确显示新 namespace 中的进程视图。

执行后,在新 bash 中运行:

ps aux

输出只有当前 bash 和 ps 两个进程,且 bash 的 PID 为 1。而宿主机上这个 bash 的真实 PID 可能是 3456,但在新 PID namespace 内部看到的是 1。PID 号完全被隔离。

尝试 kill -9 1,你会发现它不会使系统崩溃,因为在 PID namespace 内,1 号进程就是当前 bash,你只能杀死它,对其他进程无影响。

注意:PID 隔离只影响进程的可见性和 PID 编号,不限制进程间的信号发送(受 User namespace 和权限控制)。

示例 3:隔离网络 —— Network Namespace

当你创建新的 net namespace 时,里面只有一个回环接口(lo),没有任何物理网卡或 IP 配置。

sudo unshare --net bash

# 查看网卡
ip addr
# 只会显示 lo 接口,且状态为 DOWN

# 你可以把 lo 启动
ip link set lo up
ping 127.0.0.1
# 成功

# 尝试 ping 宿主机以外的地址会失败,因为没有外部接口
ping 8.8.8.8
# connect: Network is unreachable

若要为新 namespace 配置网络与外部通信,通常要用 veth pair 把它和宿主机的 namespace 连接起来,或者使用 ip netns 命令进行管理(下面会讲)。

示例 4:组合多个 namespace 模拟“微型容器”

我们来创建一个同时隔离主机名、进程、网络、挂载点的环境:

sudo unshare --uts --pid --net --mount --fork --mount-proc /bin/bash

现在你拥有一个与宿主机完全不同的:

  • 主机名(可改)
  • 进程列表(只看到自身)
  • 网络栈(只有 lo,无外部连接)
  • 挂载点(在 mount namespace 内可独立挂载 tmpfs 等)

这不正是一个手工打造的容器雏形吗?Docker 底层正是组合这些 namespace,并加上 cgroup 资源限制和联合文件系统来实现的。

使用 nsenter 进入已有的 Namespace

如果某个进程已经处于某个 Namespace 中,我们可以用 nsenter 进入该进程所在的特定 Namespace。最典型的场景:你想调试运行中的 Docker 容器内部环境。

基本语法:

nsenter --target <PID> --mount --uts --ipc --net --pid -- <command>

比如,Docker 容器的 PID 是 12345,我们可以这样进入它的所有 Namespace:

sudo nsenter --target 12345 --mount --uts --ipc --net --pid -- /bin/bash

现在你就像“钻入”了容器内部,共享其所有隔离视图。nsenter 常用于排查网络或挂载问题。

常用管理工具与命令

lsns —— 列出系统中的 Namespace

lsns

输出会按类型分组,显示每个 namespace 的 inode 号、所属进程等。

# 只看 net namespace
lsns -t net

# 查看特定进程的 namespace
lsns -p <PID>

ip netns —— 管理 Network Namespace

ip netns 提供了一种便捷的方法来操作命名网络栈。你可以给它起一个有意义的名称。

# 创建一个名为 ns1 的 net namespace
sudo ip netns add ns1

# 在 ns1 里运行命令
sudo ip netns exec ns1 ip addr

# 创建一对 veth,一端放在 ns1,一端放在宿主机
sudo ip link add veth0 type veth peer name veth1
sudo ip link set veth1 netns ns1

# 在宿主机配置 veth0
sudo ip addr add 10.0.0.1/24 dev veth0
sudo ip link set veth0 up

# 在 ns1 内配置 veth1
sudo ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth1
sudo ip netns exec ns1 ip link set veth1 up
sudo ip netns exec ns1 ip link set lo up

# 现在宿主机和 ns1 可以通过 10.0.0.x 互通
ping 10.0.0.2

删除 namespace:

sudo ip netns delete ns1

ip netns 依赖于将 namespace 的引用挂载到 /var/run/netns/ 目录下,所以它管理的 netns 是“有名字的”,方便复用。

Namespace 与 Cgroup 的区别与组合

初学者常把 Namespace 和 Cgroup 混淆,这里明确区分:

  • Namespace 解决的是 “可见性” 问题(能看见什么),实现资源隔离。
  • Cgroup 解决的是 “限制” 问题(能用多少),实现资源控制(CPU、内存、IO 等)。

二者相辅相成:Namespace 让你以为自己在独立的世界里,而 Cgroup 则为这个世界设定了物理限制。Docker 启动容器时,会同时创建对应的 namespace 和 cgroup 组。

举个例子:

# 创建一个 CPU 限制的 cgroup
sudo cgcreate -g cpu:/mycontainer
echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us

# 在隔离的 namespace 中将进程放入该 cgroup
sudo cgexec -g cpu:/mycontainer unshare --uts --pid --fork --mount-proc /bin/bash