网络命名空间与 veth 对实战:手工搭一遍容器网络的骨架
容器网络经常被讲成黑盒:容器里有网卡、能出网、还能被宿主机端口映射访问,中间发生了什么说不清。其实拆开看,它就是 Linux 自带的几件原语——网络命名空间(netns)做隔离、veth 对做连线、网桥做交换、iptables NAT 做出口。本文不用 Docker,纯手工用 ip 命令把这条链路从零搭一遍:先让两个命名空间点对点互通,再把它们接入网桥,最后经 NAT 出网,每一步都抓包验证。做完这一遍,Docker 的 bridge 网络在你眼里就是同一套动作的自动化版本。环境为任意主流发行版(本文 Rocky Linux 9),需要 root 权限和一台可联网的实验机(虚拟机即可)。
前置条件
- root 权限;iproute2 已安装(ip 命令),另备 tcpdump 用于抓包验证。
- 实验机的出网网卡记为 eth0(按你的实际网卡名替换),其网关可达公网。
- 允许短时修改 iptables 转发与 NAT 规则,实验结束按文末清单清理。
- 规划实验网段:veth 直连段 10.200.1.0/24,网桥段 10.200.2.0/24(均为实验内网段,与现网不冲突即可)。
第一步:两个命名空间经 veth 对互通
# 创建两个命名空间
ip netns add ns1
ip netns add ns2
# 创建一对 veth,两端分别放进两个命名空间
ip link add veth1 type veth peer name veth2
ip link set veth1 netns ns1
ip link set veth2 netns ns2
# 配地址并启用(注意:命名空间内的 lo 默认是 down 的,必须先 up)
ip netns exec ns1 ip link set lo up
ip netns exec ns2 ip link set lo up
ip netns exec ns1 ip addr add 10.200.1.1/24 dev veth1
ip netns exec ns2 ip addr add 10.200.1.2/24 dev veth2
ip netns exec ns1 ip link set veth1 up
ip netns exec ns2 ip link set veth2 up
# 连通性验证
ip netns exec ns1 ping -c2 10.200.1.2
veth 对可以理解为一根网线的两端:从一端进去的数据包必然从另一端出来,它是跨命名空间连线的唯一基础件。ip netns exec 是「进到那个命名空间里执行命令」的入口,后续所有命名空间内操作都靠它。这一步通不了先查三件事:两端是否都 up、lo 是否 up、地址是否写在 peer 的对端而不是同一端。
第二步:接入网桥,让命名空间与宿主机互通
点对点只能连两个命名空间,真实场景是多个「容器」挂到同一台交换机上互通并与宿主机通信,这台交换机就是 Linux 网桥(bridge)。新建网桥 br0,给两个命名空间各拉一根新的 veth 进来:
ip link add br0 type bridge
ip addr add 10.200.2.1/24 dev br0
ip link set br0 up
# 每个命名空间一根 veth:一端进命名空间,另一端留在宿主机并挂到 br0
ip link add veth-ns1 type veth peer name br-ns1
ip link add veth-ns2 type veth peer name br-ns2
ip link set veth-ns1 netns ns1
ip link set veth-ns2 netns ns2
ip link set br-ns1 master br0
ip link set br-ns2 master br0
ip link set br-ns1 up
ip link set br-ns2 up
ip netns exec ns1 ip addr add 10.200.2.11/24 dev veth-ns1
ip netns exec ns2 ip addr add 10.200.2.12/24 dev veth-ns2
ip netns exec ns1 ip link set veth-ns1 up
ip netns exec ns2 ip link set veth-ns2 up
ip netns exec ns1 ip route add default via 10.200.2.1
# 验证:ns1 与 ns2 经网桥互通,ns1 能到达宿主机网桥地址
ip netns exec ns1 ping -c2 10.200.2.12
ip netns exec ns1 ping -c2 10.200.2.1
注意命名空间里的接口改名问题:veth-ns1 进了 ns1 后,ns1 内部看到的名字仍是 veth-ns1(真实容器里会被改名为 eth0,那只是容器运行时的约定动作)。这时 ns1 访问 10.200.2.1 是「同网段直达」,不需要路由;而访问外网需要默认路由指向网桥地址——下一步就靠它。
第三步:NAT 出网
# 宿主机开转发
sysctl -w net.ipv4.ip_forward=1
# 网桥段出 eth0 时做源地址伪装(Ubuntu 上用 ufw 的系统注意别被其默认策略覆盖)
iptables -t nat -A POSTROUTING -s 10.200.2.0/24 -o eth0 -j MASQUERADE
iptables -A FORWARD -i br0 -o eth0 -j ACCEPT
iptables -A FORWARD -i eth0 -o br0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 出网验证(在 ns1 内 ping 一个公网地址与解析一个域名)
ip netns exec ns1 ping -c2 192.0.2.1
ip netns exec ns1 getent hosts example.com
MASQUERADE 把命名空间的源地址换成宿主机 eth0 的地址,回程包靠 conntrack 状态表还原,这是所有容器出网的共同底座。验证时按「先 IP 后域名」的顺序:IP 不通查转发与规则,IP 通而域名不通是 DNS 问题(命名空间有自己的 /etc/netns/<名字>/resolv.conf 挂载点,没配就用宿主机的 resolv.conf 兜底确认)。
第四步:抓包验证每一跳
# 三个观察点各开一个终端,ns1 持续 ping 网桥段对端
tcpdump -i veth1 -nn icmp # 点:直连链路(在宿主机看不到已移入 ns 的接口,用 netns exec 抓)
ip netns exec ns1 tcpdump -i veth-ns1 -nn icmp
tcpdump -i br0 -nn host 10.200.2.11
tcpdump -i eth0 -nn icmp
逐跳看包的意义在于建立「包的旅程」直觉:同一个 ICMP 请求,在 veth 口是内网地址原样,在 eth0 口源地址已被 NAT 成宿主机地址。以后排查容器网络故障,按这条旅程逐段抓包,哪一段断了问题就锁在哪一段,比对着报错猜快得多。
与 Docker bridge 网络的对应关系
把实验部件和 Docker 默认 bridge 网络(docker0)对照,概念一次对齐:
| 本文实验部件 | Docker 中的对应物 | 说明 |
|---|---|---|
| ns1 / ns2 | 每个容器的 network namespace | 容器进程就跑在命名空间里,docker exec 进容器即进入该空间 |
| veth 对 | 容器 eth0 与宿主机 vethXXXX 一对 | ip link 在宿主机看到的 vethXXXX 就是另一端 |
| br0 | docker0 网桥(默认 172.17.0.1/16) | 同网桥容器二层互通,网桥地址即容器默认网关 |
| MASQUERADE 规则 | iptables nat 表中 docker 维护的 POSTROUTING 链 | docker inspect 看到的 Gateway 就是网桥地址 |
| (本文未做)端口映射 | -p 8080:80 的 DNAT 规则 | 宿主机端口转发进容器,本质是再加一条 PREROUTING 的 DNAT |
实验清理
iptables -t nat -D POSTROUTING -s 10.200.2.0/24 -o eth0 -j MASQUERADE
ip link del br0
ip netns del ns1
ip netns del ns2
ip netns list
删除命名空间会自动销毁其中的 veth 端,宿主机侧的 peer 也会跟着消失,不需要逐个删接口。
常见问题
命名空间里 ping 自己都不通。先看 lo:新建命名空间的 lo 默认 down,没启用时连 127.0.0.1 都不通,这是第一排查点,不是路由问题。
同网段互通但出不了网。按顺序查三个开关:命名空间里有没有默认路由(ip route)、宿主机 ip_forward 是否为 1、NAT 规则的出接口名是否与实际网卡一致。云主机还要叠加一层云安全组放行。
ARP 不通、ping 显示 Destination Host Unreachable。多是 veth 的宿主机端忘了 up,或挂网桥时 master 没设对。用 bridge link 命令确认每个口都挂在 br0 下、状态为 forwarding,再让对端重新发包触发 ARP 学习。
小结
这一遍手工流程的价值,是把「容器网络」从产品概念还原成内核原语:netns 隔离、veth 连线、bridge 交换、iptables NAT 出口,四件事组合出几乎所有单机容器网络方案。之后无论看 Docker 的网络排障、还是 Kubernetes 里 CNI 插件在 Pod 创建时替你执行的动作,你都知道它本质在重复本文的哪一步,排障也就有了固定的逐跳抓包套路。






