资讯动态

veth pair 实战:两个网络命名空间点对点直连的最小方案

发布时间:2026/10/26 5:00:48 来源:尧图企业网站定制
网络命名空间是 Linux 虚拟化能力里最容易被低估的一块。很多人第一次接触容器网络看到两个隔离环境之间能互相 ping 通第一反应是这不就是配了个网桥嘛但真正把两个命名空间直接点对点连起来、不依赖任何桥接设备的最小方案其实是 veth pair。这个标题说的就是这件事用一对虚拟网卡把两个独立的网络命名空间缝在一起让它们像插了根网线一样通信。它解决的问题很具体——当你需要两个隔离网络栈之间有一条专属通道又不想引入 bridge、不想多占一个网段时veth pair 是最轻的解法。适合谁看做过容器网络调试、写过网络相关的自动化脚本、或者单纯想搞明白两个 namespace 之间到底怎么通的运维和开发同学。下面我按自己实际搭环境的顺序把原理、操作、踩坑和验证一条线讲透。1. 先搞清楚 veth pair 到底是个什么东西1.1 它不是网卡是一根两头都是水晶头的网线刚接触的时候我也有个误解以为 veth 是一块虚拟网卡创建出来就挂在本机某个地方。实际上 veth 永远成对出现你可以把它理解成一根两端都做了水晶头的网线一头插进命名空间 A另一头插进命名空间 B中间这段线由内核负责搬运数据。你把其中一头塞进某个 namespace另一头如果留在初始命名空间那这根线就连接了宿主和容器如果两头分别塞进两个不同的 namespace那这根线就只连接这两个隔离环境跟宿主没关系了。这个成对属性是 veth 的核心特征也是它和 tun/tap、bridge 这些设备最本质的区别。bridge 是一个可以挂多个端口的交换机veth 是严格的一对一。所以当你需要连接三个以上命名空间时veth 就不够用了得靠 bridge 做汇聚veth 只负责把每个 namespace 接入到 bridge 上。理解这一点后面选型就不会走弯路。从内核实现角度看veth 驱动在发送方向做的事情很直接从一端收到的 skbsocket buffer直接挂到对端的接收队列上触发对端的软中断处理。它不经过物理网卡、不经过协议栈的完整收发路径所以延迟极低。这也是为什么很多对性能敏感的场景比如某些 service mesh 的数据面宁愿用 veth 直连也不愿绕桥。1.2 为什么两个 namespace 默认互相看不见网络命名空间隔离的是一整套网络栈网卡设备、路由表、ARP 表、iptables 规则、socket 端口空间全都是独立的。你ip netns add ns1之后ns1 里默认只有一个 loopback而且是 down 状态。两个 namespace 各自有各自的127.0.0.1互不干扰也互不可达。这种隔离是彻底的不是防火墙挡着那种逻辑隔离而是内核里根本就是两套数据结构。所以你不能指望在 ns1 里ip addr看到 ns2 的网卡也看不到 ns2 的路由。想让它们通信必须有一条物理上逻辑上存在的链路把两套栈连起来veth pair 就是这条链路。这个认知很关键通信不是配置出来的是先把链路接上再给链路两端配地址和路由。顺序搞反了你会对着一堆Network is unreachable发呆。1.3 veth 与 bridge、macvlan 的适用边界实际工作里经常要在几种虚拟网络设备之间做选择我整理了一个对照方便你判断什么时候该用 veth设备类型连接能力典型场景是否需要额外设备veth pair严格一对一两个 namespace 直连、容器接入 bridge否bridge多端口汇聚多个容器同网段互通、宿主机做网关需要 veth 做接入macvlan一个父接口派生多个容器需要独立 MAC、直接暴露到外部网络依赖物理网卡tun/tap用户态与内核态之间用户态网络程序、隧道实现需要用户态程序配合从表里能看出来veth 的定位是链路不是交换。它最适合的场景就是点对点或者作为 bridge 的接入端口。如果你一上来就想用 veth 连三个 namespace那说明需求本身就该用 bridge别硬凑。我见过有人为了省一个 bridge硬生生建了三对 veth 做全互联结果路由表复杂到没人敢动这就是选型没想清楚。2. 动手之前环境与前置检查2.1 确认内核能力和权限veth 是内核模块veth提供的绝大多数发行版默认就编进去了但保险起见还是查一下。执行lsmod | grep veth如果没有任何输出试着手动加载modprobe veth。如果连 modprobe 都报module not found那说明内核编译时没带这个模块得换内核或者装对应的 modules 包。这种情况在精简过的嵌入式系统里偶尔会遇到普通服务器和开发机上基本不会。权限方面创建和操作网络命名空间需要CAP_SYS_ADMIN或者 root。日常实验我建议直接用 root 或者sudo -i进一个 root shell别在每条命令前加 sudo因为ip netns exec这种命令嵌套 sudo 的时候环境变量容易出问题排查起来很烦。另外确认ip命令来自 iproute2 包版本不要太老ip netns子命令在很老的版本上行为有差异。2.2 命名空间的生命周期管理习惯ip netns add创建的命名空间会在/var/run/netns/下留一个挂载点文件这个文件就是命名空间的句柄。这里有个特别容易踩的坑如果你手动删了/var/run/netns/下的文件命名空间并不会真正销毁里面的进程还在跑但你已经没法用ip netns管理它了。我早期做实验就干过这事最后只能重启机器清理。正确的清理方式是ip netns delete name它会先卸载挂载点再清理。另外建议实验时给命名空间起有意义的名字比如ns-left、ns-right别用ns1、ns2因为一旦你同时开好几个实验回头根本分不清哪个是哪个。我现在的习惯是每次实验前先ip netns list看一眼有没有残留有就清掉保持环境干净。2.3 一个容易忽略的点loopback 默认是 down 的新建的命名空间里lo接口存在但是 down 状态。很多人配完 veth、配完地址发现本机内部的服务都起不来或者ping 127.0.0.1不通就是忘了把 lo 拉起来。这个细节在只做跨 namespace 通信时不影响但一旦你在 namespace 里跑任何依赖本地回环的服务比如某些数据库、某些 agent就会出问题。所以我的习惯是建完 namespace 第一件事就是ip netns exec name ip link set lo up养成肌肉记忆。3. 完整实操从零搭一条跨命名空间链路3.1 创建两个命名空间并拉起回环先把两个隔离环境建出来ip netns add ns-left ip netns add ns-right ip netns exec ns-left ip link set lo up ip netns exec ns-right ip link set lo up执行完用ip netns list确认两个都在。这时候如果你ip netns exec ns-left ip addr会看到只有一个 lo状态是 UNKNOWN回环的正常状态没有其他网卡。这就是干净的起点。3.2 创建 veth pair 并分发到两端关键的一步来了。创建 veth pair 有两种写法我推荐一步到位的写法ip link add veth-left type veth peer name veth-right这条命令在当前命名空间通常是初始命名空间创建了一对网卡名字分别是 veth-left 和 veth-right。注意此刻它们都还在宿主命名空间里还没进任何隔离环境。接下来把两头分别塞进去ip link set veth-left netns ns-left ip link set veth-right netns ns-right执行完这两条宿主命名空间里就看不到这两块网卡了它们分别住进了对应的 namespace。这里有个操作顺序的细节必须先创建再分发不能直接在某命名空间里创建然后指望对端自动过去。因为ip link add ... type veth peer name ...创建时两端都在当前上下文分发是后续动作。我见过有人试图在 ns-left 里执行创建命令结果两端都跑进 ns-left 了还得再挪一头出来多绕一步。3.3 给两端配地址并拉起现在两端各在各的 namespace 里但都是 down 状态、没有地址。分别配置ip netns exec ns-left ip addr add 10.10.0.1/24 dev veth-left ip netns exec ns-left ip link set veth-left up ip netns exec ns-right ip addr add 10.10.0.2/24 dev veth-right ip netns exec ns-right ip link set veth-right up地址规划上我习惯用/24虽然点对点链路用/30或/31更省地址但/24在实验环境里更直观而且不会因为掩码算错导致明明配了地址却不通。生产环境如果地址紧张用/31是标准做法RFC 3021两端各一个地址没有网络号和广播号的浪费。这里用10.10.0.0/24只是示例网段你换成任何私有网段都行只要别和现有网络冲突。3.4 验证链路ping 与状态检查配完直接测ip netns exec ns-left ping -c 3 10.10.0.2正常情况下三发三收。如果不通先别急着怀疑配置按这个顺序查ip netns exec ns-left ip link show veth-left看状态是不是 UPip netns exec ns-left ip addr show veth-left看地址有没有配上ip netns exec ns-right ip link show veth-right看对端是不是也 UP。veth 的一个特性是只要一端 down整条链路就不通因为链路状态是两端联动的。这个和物理网线类似一头没插好另一头也协商不起来。再补一个验证手段看 ARP 表ip netns exec ns-left ip neigh showping 通之后应该能看到10.10.0.2对应的 MAC 地址状态是 REACHABLE 或 STALE。这个 MAC 是对端 veth 的 MAC可以在 ns-right 里用ip link show veth-right核对。如果 ARP 表里一直是 INCOMPLETE说明二层根本没通问题多半在链路状态或者命名空间分发上。4. 那些文档里不写、但一定会遇到的坑4.1 命名空间里没有默认路由出站全挂这是最高频的问题。你配好地址、ping 通了对端然后想在 ns-left 里访问外网或者访问宿主的其他网段发现全部失败。原因很简单你只配了直连网段的路由没有默认路由。ip addr add会自动生成一条到10.10.0.0/24的直连路由但0.0.0.0/0不会自动出现。解决方式取决于你的目标。如果只是两个 namespace 互通那不需要默认路由现状就是对的。如果 ns-left 要访问 ns-right 之外的东西就得手动加ip netns exec ns-left ip route add default via 10.10.0.2但这里有个前提10.10.0.2 那端得具备转发能力并且知道怎么把包送出去。单纯加默认路由只是把包丢给对端对端不转发照样不通。所以跨 namespace 访问外部网络本质上是把其中一个 namespace 当路由器用需要开net.ipv4.ip_forward1并配 NAT这就超出 veth 本身的范畴了。我的建议是先用 veth 把点对点通信跑通再考虑要不要让它当网关别一上来就堆路由和 NAT出问题根本分不清是哪一层。4.2 忘记 up 接口链路静默失败前面提过但值得单独强调。ip addr add只是配地址不会自动把接口拉起来。很多人配完地址直接 ping不通然后开始怀疑地址配错、掩码算错、命名空间建错折腾半天发现就是ip link set ... up没执行。这个坑我踩过不止一次后来养成习惯配地址和 up 接口永远成对写中间不插入其他命令减少遗漏。更隐蔽的一种情况是接口 up 了但对端没 up。因为 veth 链路状态联动一端 up 一端 down 时up 的那端ip link show会显示NO-CARRIER或者状态是 DOWN。看到这个提示直接去查对端别在本端死磕。4.3 地址冲突导致时通时不通实验环境里网段随手一配很容易和宿主机已有的网段撞车。比如你用了10.10.0.0/24而宿主机上某个 docker 网络或者某个物理网段正好也是这个就会出现诡异现象有时候通有时候不通或者 ping 延迟忽高忽低。根因是路由表里出现了两条指向同一网段但出口不同的路由内核按最长前缀匹配和 metric 选路选到哪条看运气。排查方法在 namespace 里ip route get 10.10.0.2看内核实际选了哪个出口。如果出口不是你以为的 veth那就是路由冲突。解决就是换网段实验环境我一般用10.99.x.x这种不太可能被占用的段或者干脆用192.168.199.0/24这种冷门段。别用172.17.0.0/16那是 docker 默认网段的重灾区。4.4 删除顺序不对留下僵尸设备清理的时候如果你先ip netns delete ns-left再想删 ns-right 里的 veth-right会发现删不掉或者报错。因为 veth 是成对的一端所在的命名空间被销毁时内核会把这一对都回收但回收过程有先后有时候会留下半截状态。稳妥的清理顺序是先删 veth pair在任意一端执行ip link del即可会连带删除对端再删命名空间。ip netns exec ns-left ip link del veth-left ip netns delete ns-left ip netns delete ns-right如果已经乱了用ip netns list看残留用ip link show type veth在宿主和各 namespace 里找残留设备逐个清理。实在清不干净重启是最快的兜底但作为从业者还是建议把清理流程固化下来别依赖重启。5. 把 veth 用进真实场景几个延伸方向5.1 容器网络里的 veth你每天都在用但没注意docker 默认的 bridge 网络模式下每启动一个容器宿主上就会多出一对 veth一头叫vethXXXXXX挂在 docker0 桥上另一头在容器命名空间里叫eth0。你在宿主上ip link show type veth能看到一堆这样的设备名字是随机后缀。容器能上网、能被端口映射访问底层就是这对 veth 在搬数据。理解了这个很多容器网络问题就有了排查抓手。比如容器 ping 不通外网你可以顺着 veth 往上看容器内ip route看默认路由指向哪宿主上找到对应的 veth 看它是不是挂在桥上桥上有没有 IP宿主有没有开转发和 NAT。这条链路一旦建立起来排查效率比盲猜高一个数量级。我处理过的容器网络故障里八成都能通过顺着 veth 两头看定位到具体是哪一环断了。5.2 用 veth 做网络实验的隔离沙箱做网络协议实验、路由实验的时候最怕污染宿主机网络。用 network namespace 加 veth 可以搭一个完全隔离的沙箱建几个 namespace用 veth 两两相连或者接到一个 bridge 上在里面随便折腾路由协议、iptables 规则、甚至跑个软路由玩坏了直接ip netns delete一删了事宿主机毫发无损。这种沙箱的价值在于可复现。你可以把整套搭建命令写成一个脚本每次实验前跑一遍环境秒级就绪。我自己的习惯是把常用拓扑两点直连、三点星型、带网关的三角都写成小脚本需要哪个跑哪个。比开虚拟机轻量太多启动时间从分钟级降到秒级而且资源占用几乎可以忽略。5.3 性能敏感场景下 veth 直连的取舍前面提过 veth 直连延迟低但也不是所有场景都该用它。veth 的数据路径虽然短但每经过一次命名空间边界就涉及一次上下文切换和 skb 的重新归属在高吞吐场景下这部分开销会累积。实测在普通服务器上veth 直连的吞吐能跑到物理网卡的线速附近延迟在微秒级对绝大多数应用足够。但如果你的场景是超低延迟比如某些金融交易系统可能要考虑更底层的方案比如共享内存或者 DPDK 这类绕过内核协议栈的路子。我的经验是先用 veth 把功能跑通用真实流量压一压看瓶颈在哪再决定要不要换方案。过早优化网络路径往往优化了个寂寞因为真正的瓶颈可能在应用层或者磁盘 IO。veth 的简单可靠本身就是一种性能——少一层设备少一类故障。6. 排查思路不通的时候按这个顺序查6.1 从链路层往上逐层验证网络排查最忌讳东一榔头西一棒子。我固定用自下而上的顺序先看链路ip link两端是否 UP、是否有 NO-CARRIER再看地址ip addr地址和掩码是否正确再看路由ip route目标网段走哪个出口再看邻居ip neighARP 是否解析成功最后才看应用层端口是否监听、防火墙是否放行。这个顺序的逻辑是下层不通上层怎么配都没用。比如链路没 up你配再完美的路由也是白搭ARP 解析不了路由选对了包也发不出去。按层排查的好处是每一步都有明确的通过/不通过判据不会陷入感觉哪里不对的玄学。6.2 用 tcpdump 在两端同时抓包当分层排查到某一层卡住、又看不出原因时抓包是终极手段。veth 的好处是两端都能抓在 ns-left 的 veth-left 上抓在 ns-right 的 veth-right 上抓对比两边的包。ip netns exec ns-left tcpdump -i veth-left -nn -c 10 ip netns exec ns-right tcpdump -i veth-right -nn -c 10如果左边发出了 ICMP request 但右边没收到问题在链路中间几乎不可能veth 是内核直连如果右边收到了 request 但没回 reply问题在右边的协议栈可能是路由、可能是防火墙如果右边回了 reply 但左边没收到对称地查。两端同时抓包能瞬间把问题范围缩小一半比单端抓包瞎猜高效得多。这个技巧我在排查容器网络问题时用得最多。6.3 常见现象与对应根因速查把高频问题整理成表方便对照现象最可能的原因快速验证ping 报 Network is unreachable没有到目标网段的路由ip route get 目标IPping 无响应但无报错接口 down 或对端 down两端ip link show看状态ARP 一直 INCOMPLETE二层不通链路或分发有问题两端抓 ARP 包时通时不通网段冲突或路由有多条ip route get看选路能 ping 通但端口连不上应用没监听或防火墙拦截ss -lntp加 iptables 检查这张表不是穷举但覆盖了我实际遇到的绝大多数情况。遇到表外的问题回到 6.1 的分层思路从链路层重新走一遍基本都能定位。7. 几个让操作更顺手的个人习惯7.1 把搭建过程脚本化手工敲命令做一次实验没问题做十次就会烦而且容易漏步骤。我的做法是把建命名空间、建 veth、配地址、up 接口、验证写成一个 bash 脚本参数化命名空间名和网段。这样每次实验一条命令搞定还顺便把正确顺序固化下来了。脚本里加上set -e任何一步失败立即停避免在错误状态上继续操作。脚本化还有个隐性好处逼你把步骤想清楚。手工操作时很多步骤是顺手就做了写脚本时必须明确每一步的前置条件和预期结果这个过程本身就能发现理解上的漏洞。我好几回都是在写脚本时意识到哦这里顺序其实有依赖然后回头修正了手工流程。7.2 给实验环境定一套命名和网段规范命名空间名用ns-前缀加用途veth 名用veth-加两端标识网段固定用某个冷门私有段。这套规范看起来是小事但在同时开多个实验时能救命。我曾经同时开着三组实验因为命名混乱把 A 组的 veth 配到了 B 组的命名空间里排查了半小时才发现是接错线了。从那以后我就严格按规范来再没出过这种低级错误。网段规范尤其重要。建议在实验机上维护一个已占用网段的清单新实验前查一下避免和 docker 网络、已有实验、物理网段冲突。这个清单可以就是一个文本文件简单但有效。7.3 验证不止于 pingping 通只说明 ICMP 通了不代表 TCP 通、不代表应用能跑。完整的验证应该包括ping 测三层连通性nc或curl测四层端口可达性实际跑一下目标应用测端到端。我见过 ping 通但 TCP 不通的情况根因是中间有 iptables 规则只放行了 ICMP。所以验证要覆盖到你真正关心的那一层别拿 ping 通当万事大吉。另外建议验证时带上-c限制次数别让 ping 无限跑着占终端。ping -c 3三发足够判断连通性再多就是浪费时间。如果要做稳定性测试那是另一个话题用ping -f或者专门的压测工具别用普通 ping 硬扛。8. 写在最后的一点体会veth pair 这个技术本身不复杂命令就那么几条但它是理解 Linux 网络虚拟化的一把钥匙。把两个命名空间用 veth 连起来这件事跑通之后你再去看容器网络、看 bridge、看各种 overlay 方案会发现底层逻辑是相通的无非是链路怎么接、地址怎么配、路由怎么指。这套心智模型建立起来遇到再复杂的网络拓扑也能拆解成一个个点对点的连接来分析。我个人在实际操作中的体会是这类底层网络实验最大的价值不在于学会一条命令而在于建立排查的直觉。你知道数据包从 A 到 B 要经过哪几步出问题时就能快速判断是哪一步断了。这种直觉没法从文档里读出来只能靠一次次搭环境、一次次踩坑、一次次抓包积累。所以别怕折腾把环境搭起来故意配错几个地方看看报什么错比看十篇教程都管用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑