4. 连接两个网络命名空间

上一节我们创建了两个彼此隔离的网络命名空间,但它们各自只有一个处于 DOWN 状态的 loopback 接口,既没有对外通信的链路,也没有可用的 IP 地址。本节要解决的核心问题是:如何用一对 veth pair 把两个 netns 真正“接”起来,并让它们能互相 ping 通。这里的关键不是敲几条命令,而是理解 veth pair 的“成对”本质、接口在命名空间之间的迁移过程,以及地址与路由在两端如何各自独立生效。

一、veth pair 的本质:一根虚拟网线的两个插头

veth(virtual Ethernet)设备总是成对出现,可以把它想象成一根虚拟网线:一端收到的以太网帧会从另一端发出,反之亦然。它工作在数据链路层,两端各自拥有独立的 MAC 地址、MTU 和状态。默认情况下,创建出来的一对 veth 都停留在初始(通常是 root)命名空间里,此时它们只是两个“悬空”的接口,没有任何实际意义。

真正让 veth pair 发挥作用的关键动作是把两端分别移入不同的命名空间。一旦一端被移入某个 netns,它就从原命名空间中消失,只在该 netns 内可见。于是,两个原本完全隔离的命名空间之间,就通过这根“虚拟网线”建立了一条二层通路。这也解释了为什么 veth pair 是容器网络、网桥、隧道等场景的基础构件。

netns ns-a 隔离的网络栈 A netns ns-b 隔离的网络栈 B veth-a 10.10.0.1/24 UP · MAC 自动生成 veth-b 10.10.0.2/24 UP · MAC 自动生成 虚拟以太网链路(成对收发) 一端发出的帧从另一端进入 直连路由 10.10.0.0/24 dev veth-a 直连路由 10.10.0.0/24 dev veth-b ping 10.10.0.2 ⇄ ping 10.10.0.1 同网段 → 直接 ARP 解析 → 二层转发,无需网关

二、把两端分别放入两个命名空间

假设上一节已经创建了 ns-a 和 ns-b。第一步是在当前命名空间创建一对 veth,并给它们起一个便于识别的名字,例如 veth-a 和 veth-b:

ip link add veth-a type veth peer name veth-b
ip link show type veth

此时执行 ip link show type veth,会看到两个接口都还在当前命名空间,状态为 DOWN。接下来把 veth-a 移入 ns-a,把 veth-b 移入 ns-b:

ip link set veth-a netns ns-a
ip link set veth-b netns ns-b

这里有一个容易踩坑的地方:把一端移入 netns 后,它在当前命名空间就不可见了。如果先移走了 veth-a,再想用 ip link set veth-a ... 在当前命名空间操作它就会报 “Cannot find device”。因此后续对每一端的配置,都必须用 ip -n <netns> 进入对应命名空间执行,或者用 ip netns exec <netns> ...。

三、在各自命名空间内配置地址并启用接口

veth 接口刚被移入 netns 时是 DOWN 状态,且没有 IP 地址。需要分别在两个命名空间内完成“配地址 + 启用”两个动作。下面选用 10.10.0.0/24 作为示例网段,两端分别取 .1 和 .2:

# 在 ns-a 内配置
ip -n ns-a addr add 10.10.0.1/24 dev veth-a
ip -n ns-a link set veth-a up
ip -n ns-a link set lo up

# 在 ns-b 内配置
ip -n ns-b addr add 10.10.0.2/24 dev veth-b
ip -n ns-b link set veth-b up
ip -n ns-b link set lo up

注意 lo 接口也要一并启用,否则某些依赖本地回环的程序会异常。配置完成后,可以用下面的命令确认每一端的状态:

ip -n ns-a addr show veth-a
ip -n ns-a route show
ip -n ns-b addr show veth-b
ip -n ns-b route show

预期能看到 veth-a 处于 UP 且带有 10.10.0.1/24,路由表中出现一条直连路由 10.10.0.0/24 dev veth-a proto kernel scope link src 10.10.0.1;ns-b 侧同理,源地址为 10.10.0.2。这条直连路由是自动生成的,它告诉内核:目标地址落在 10.10.0.0/24 的报文直接从 veth-a(或 veth-b)发出,不需要经过任何网关。

四、连通性测试与结果判读

最直接的验证方式是从 ns-a 内 ping ns-b 的地址:

ip netns exec ns-a ping -c 3 10.10.0.2

如果配置正确,会看到类似下面的输出(示例结果,实际时延与系统负载有关):

PING 10.10.0.2 (10.10.0.2) 56(84) bytes of data.
64 bytes from 10.10.0.2: icmp_seq=1 ttl=64 time=0.045 ms
64 bytes from 10.10.0.2: icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from 10.10.0.2: icmp_seq=3 ttl=64 time=0.041 ms

--- 10.10.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss

这里有几个判读要点。第一,ttl=64 说明报文没有经过任何三层路由跳转,是同一网段内的直接二层转发,符合预期。第二,0% packet loss 表示链路双向可达。第三,时延通常在几十微秒量级,因为 veth 是纯软件路径,不经过物理网卡。反向再从 ns-b ping 10.10.0.1 也应成功,这样才能确认双向通路都正常。

如果 ping 不通,可以按下面的顺序排查:

  • 用 ip -n ns-a link show veth-a 确认接口是 UP 而不是 DOWN;
  • 用 ip -n ns-a addr show 确认地址确实配在了 veth-a 上,而不是误配到 lo;
  • 用 ip -n ns-a neigh show 查看 ARP 表项,正常情况下应能解析出对端 MAC;
  • 确认两端地址在同一网段、掩码一致,否则会被当作跨网段而尝试走网关;
  • 确认 veth pair 的两端确实分别位于两个不同的 netns,而不是都留在同一个命名空间里。
veth pair 的两端是“绑定”关系:只要一端被删除,另一端也会随之消失。因此清理时只需删除任意一端,例如 ip -n ns-a link del veth-a,ns-b 中的 veth-b 会自动被移除。

五、本节小结与关键参数

把两个 netns 连通,本质上就是完成三件事:创建成对的 veth、把两端分别移入目标命名空间、在各自命名空间内配地址并启用接口。地址配置会自动生成直连路由,使同网段通信无需网关。下表汇总了本节涉及的关键对象与示例参数,便于对照复现:

对象所在命名空间示例地址状态作用
veth-ans-a10.10.0.1/24UPns-a 侧虚拟网卡
veth-bns-b10.10.0.2/24UPns-b 侧虚拟网卡
lons-a / ns-b127.0.0.1/8UP本地回环,建议一并启用
直连路由ns-a / ns-b10.10.0.0/24—同网段直接二层转发

需要强调的是,本节建立的是点对点连通:一对 veth 只能连接两个命名空间。如果后续要让多个命名空间互相通信,就需要引入 Linux 网桥,把多个 veth 的一端桥接在一起,这正是下一阶段要展开的内容。