2. 网络命名空间初探

上一节我们从概念上理解了网络命名空间(network namespace)是 Linux 内核提供的一种网络协议栈隔离机制。本节我们不再停留在概念层面,而是直接动手,用 ip netns 这一组命令把命名空间真正创建出来、列出来、进去看看、再删掉,并在这个过程中亲眼观察每个命名空间独立的网络协议栈、网卡、路由表和 iptables 规则。

需要先明确一个前提:ip netns 是 iproute2 工具集的一部分,它管理的是“具名网络命名空间”。所谓具名,是指内核会把这些命名空间挂载到 /var/run/netns/ 目录下,每个命名空间对应一个文件,这样即使没有进程在其中运行,命名空间也能持续存在,并且可以被反复引用。这一点非常关键——它让网络命名空间从“进程的附属品”变成了可以独立操作的网络对象。

创建:ip netns add

创建命名空间最简单的一条命令是:

ip netns add ns1
ip netns add ns2

执行后,内核会创建两个独立的网络命名空间。我们可以立刻验证它们是否真的存在:

ls -l /var/run/netns/
# 输出示例:
# -r--r--r-- 1 root root 0 ... ns1
# -r--r--r-- 1 root root 0 ... ns2

这两个文件就是命名空间的“句柄”。注意,此时 ns1 和 ns2 内部各自拥有一套完整的网络协议栈:独立的网卡列表、独立的路由表、独立的 ARP 表、独立的 iptables/nftables 规则集、独立的 socket 端口空间。它们默认只有一个处于 DOWN 状态的 loopback 接口,除此之外什么都没有。

列出:ip netns list

查看当前系统上有哪些具名命名空间:

ip netns list
# 输出示例:
# ns2
# ns1

输出顺序并不保证稳定,它反映的是目录读取顺序,因此不要依赖 list 的排列来判断创建先后。如果需要确认某个命名空间是否存在,可以用 ip netns identify <pid> 反查进程所属命名空间,或用 ip netns pids ns1 查看哪些进程运行在 ns1 中。

进入:ip netns exec

“进入”命名空间并不是像 ssh 那样切换终端,而是让某条命令在指定命名空间的网络协议栈上下文中执行。语法是:

ip netns exec ns1 <command>

例如,查看 ns1 内部的网卡和路由表:

ip netns exec ns1 ip link show
# 输出示例:
# 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
#     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

ip netns exec ns1 ip route show
# 输出示例:(空,因为还没有配置任何路由)

对比一下宿主机(根命名空间)的 ip link show,你会看到 eth0、docker0、veth 等一大堆接口,而 ns1 里只有 lo。这就是“独立协议栈”最直观的证据:同一个内核,不同的网络视图。

再进一步,我们可以在 ns1 里启用 lo 并查看 iptables:

ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip addr show lo
# 输出示例:
# 1: lo: <LOOPBACK,UP,LOWER_UP> ...
#     inet 127.0.0.1/8 scope host lo

ip netns exec ns1 iptables -L -n
# 输出示例:
# Chain INPUT (policy ACCEPT)
# target     prot opt source               destination
# Chain FORWARD (policy ACCEPT)
# ...
# Chain OUTPUT (policy ACCEPT)
# ...

此时 ns1 的 iptables 是空的默认策略,与宿主机上可能已经存在的复杂规则完全无关。如果你在宿主机上添加一条 DROP 规则,再回到 ns1 查看,会发现 ns1 的规则表纹丝不动——这再次证明规则集是按命名空间隔离的。

删除:ip netns delete

删除命名空间:

ip netns delete ns1

删除后,/var/run/netns/ns1 文件消失,ns1 内部的所有网络配置(网卡、路由、iptables 规则)随之被内核回收。如果命名空间内还有进程在运行,删除操作会失败或需要先终止这些进程。可以用 ip netns pids ns1 先确认。

一个常见误区:ip netns delete 删除的是“具名引用”,如果某个进程仍持有该命名空间的引用(例如通过 /proc/<pid>/ns/net),命名空间本身不会立即销毁,只是不再能通过名字访问。理解这一点对排查“删不掉”的问题很有帮助。

一张图看清四个操作与隔离边界

下面的 SVG 把“创建—列出—进入—删除”四个操作与命名空间内部的独立协议栈对象放在同一张图中,帮助你建立“命令作用于哪个对象、隔离边界在哪里”的整体印象。

ip netns 四操作与网络命名空间隔离边界 宿主机根命名空间(root netns) 网卡:eth0 / docker0 / veth… 路由表:完整 iptables:业务规则 /var/run/netns/ 目录:ns1、ns2 的具名句柄挂载于此 命令执行位置:ip netns add / list / delete 均在此层生效 —— 内核网络协议栈隔离边界 —— ns1(具名网络命名空间) 网卡列表 lo(DOWN→UP)、无 eth0 路由表 独立,初始为空 iptables / nftables 独立规则集,默认 ACCEPT ARP / socket 端口空间 与其它命名空间互不可见 ns2(具名网络命名空间) 网卡列表 lo(独立实例) 路由表 独立,与 ns1 不共享 iptables / nftables 独立规则集 ARP / socket 端口空间 与 ns1 完全隔离 add 创建句柄 → list 列出句柄 → exec 在目标协议栈内执行命令 → delete 移除句柄并回收配置

观察要点与常见判断依据

做完上面这组操作,你应该能回答几个关键问题,它们也是判断“是否真正理解了网络命名空间”的依据:

  • 网卡是否独立?在 ns1 中执行 ip link show 只看到 lo,而宿主机看到 eth0 等,说明网卡列表按命名空间隔离。一个物理网卡同一时刻只能属于一个命名空间,但 veth pair 可以一端在宿主机、一端在 ns1,从而把两个命名空间连起来。
  • 路由表是否独立?ns1 中 ip route show 为空,宿主机有默认路由,说明路由表是每命名空间一份。给 ns1 添加 ip netns exec ns1 ip route add default via ... 不会影响宿主机。
  • iptables 是否独立?在宿主机添加一条规则,再在 ns1 中 iptables -L -n,看不到该规则,说明规则集隔离。
  • 端口空间是否独立?ns1 中监听 80 端口,宿主机同样可以监听 80 端口,二者不冲突,因为 socket 端口空间按命名空间隔离。
注意:ip netns exec 只改变网络命名空间,不改变 mount、PID、UTS 等其它命名空间。因此你在 ns1 中看到的文件系统、进程列表仍然是宿主机的。如果发现“进去之后文件系统没变”,这是正常现象,不是命令失效。

一个可复现的最小实验流程

把上面的命令串成一个完整闭环,便于你直接复制执行并观察结果:

# 1. 创建
ip netns add ns1
ip netns add ns2

# 2. 列出
ip netns list

# 3. 进入并观察独立协议栈
ip netns exec ns1 ip link show
ip netns exec ns1 ip route show
ip netns exec ns1 ip link set lo up
ip netns exec ns1 ip addr show lo
ip netns exec ns1 iptables -L -n

# 4. 对比宿主机
ip link show
ip route show
iptables -L -n

# 5. 删除
ip netns delete ns1
ip netns delete ns2
ip netns list

执行完第 5 步后,ip netns list 应该不再显示 ns1、ns2,/var/run/netns/ 下对应文件也应消失。如果删除时报 “No such file or directory”,通常说明命名空间名拼写错误或已被删除;如果报 “Device or resource busy”,则要检查是否有进程仍在使用它。