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 是容器网络、网桥、隧道等场景的基础构件。
二、把两端分别放入两个命名空间
假设上一节已经创建了 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,而不是都留在同一个命名空间里。
ip -n ns-a link del veth-a,ns-b 中的 veth-b 会自动被移除。五、本节小结与关键参数
把两个 netns 连通,本质上就是完成三件事:创建成对的 veth、把两端分别移入目标命名空间、在各自命名空间内配地址并启用接口。地址配置会自动生成直连路由,使同网段通信无需网关。下表汇总了本节涉及的关键对象与示例参数,便于对照复现:
| 对象 | 所在命名空间 | 示例地址 | 状态 | 作用 |
|---|---|---|---|---|
| veth-a | ns-a | 10.10.0.1/24 | UP | ns-a 侧虚拟网卡 |
| veth-b | ns-b | 10.10.0.2/24 | UP | ns-b 侧虚拟网卡 |
| lo | ns-a / ns-b | 127.0.0.1/8 | UP | 本地回环,建议一并启用 |
| 直连路由 | ns-a / ns-b | 10.10.0.0/24 | — | 同网段直接二层转发 |
需要强调的是,本节建立的是点对点连通:一对 veth 只能连接两个命名空间。如果后续要让多个命名空间互相通信,就需要引入 Linux 网桥,把多个 veth 的一端桥接在一起,这正是下一阶段要展开的内容。