Linux 基础体系 · 第 68/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。
Linux Network Namespace 与 veth:容器网络、路由和 NAT 实验
Network Namespace(网络命名空间)是 Linux 内核提供的网络资源隔离机制。一个进程进入某个 Network Namespace 后,只能看到并使用该命名空间中的网络设备、地址、路由表、邻居表、端口空间、iptables/nftables 规则以及部分网络相关状态。
veth 是一种成对出现的虚拟以太网设备。向一端发送的数据包,会从另一端出现;两端可以分别放入不同的 Network Namespace。因此,Network Namespace + veth 是容器网络最基础的连接方式之一。
本文通过一个可运行的实验构造如下拓扑:
flowchart LR
C1["netns c1\neth0 10.200.1.2/24\n默认路由 via 10.200.1.1"]
C2["netns c2\neth0 10.200.1.3/24\n默认路由 via 10.200.1.1"]
V1["veth-c1\n宿主机侧"]
V2["veth-c2\n宿主机侧"]
BR["Linux Bridge br-lab\n10.200.1.1/24"]
H["宿主机协议栈"]
WAN["宿主机外部网络\n默认路由接口"]
C1 --- V1
C2 --- V2
V1 --- BR
V2 --- BR
BR --> H
H --> WAN
实验会分别验证:
- Network Namespace 隔离了什么;
- veth 如何连接两个命名空间;
- Linux Bridge 如何完成二层转发;
- 命名空间中的路由如何选择下一跳;
- 宿主机如何执行 IPv4 转发;
- NAT 如何让私有地址访问外部网络;
- 数据包失败时如何定位是接口、二层、路由、转发还是 NAT 问题。
一、实验前提与安全边界
示例面向使用 iproute2 的现代主流 Linux 发行版,例如 Debian、Ubuntu、Fedora、Rocky Linux 等。命令需要 root 权限,或者调用者至少需要相应的网络管理能力,通常包括:
CAP_NET_ADMIN:创建命名空间、接口、地址、路由、Bridge 和防火墙规则;CAP_NET_RAW:使用ping等原始套接字;- 修改
net.ipv4.ip_forward需要宿主机管理权限; - 创建 Network Namespace 还可能受到容器运行时、systemd、SELinux 或 AppArmor 策略影响。
先确认工具存在:
ip -V
nft --version
ip 来自 iproute2。本文使用的主要命令包括:
ip netns:管理持久化 Network Namespace;ip link:管理接口、veth、Bridge 和接口状态;ip addr:管理 IP 地址;ip route:管理路由;ip neigh:查看 ARP 或 IPv6 邻居;ip netns exec:在指定命名空间中执行命令;nft:配置现代 Linux 中常用的 nftables 防火墙和 NAT。
实验会修改宿主机的以下状态:
- 创建和删除命名空间;
- 创建和删除虚拟网络设备;
- 临时启用 IPv4 转发;
- 添加一个单独的 nftables 表。
不要在生产宿主机上直接执行,尤其不要把实验用的防火墙规则与现有规则混在一起。本文使用独立的 wr_lab nftables 表,清理时删除该表,不会主动清空宿主机其他规则;但在复杂防火墙环境中,现有规则仍可能影响实验流量。
二、Network Namespace 隔离的对象
2.1 Network Namespace 不是“虚拟机网络栈”
Network Namespace 隔离的是网络相关内核对象,而不是完整操作系统。两个命名空间可以拥有相同的接口名、相同的端口号和相同的地址,只要这些对象处于不同的命名空间中。
典型隔离对象包括:
- 网络接口,例如
lo、eth0; - IPv4 和 IPv6 地址;
- 路由表和路由规则;
- ARP、IPv6 邻居缓存;
- socket 端口空间;
- netfilter 规则;
- 网络设备统计信息;
- 部分网络相关的 sysctl 状态。
Network Namespace 不自动隔离:
- 文件系统;
- PID;
- 用户和组;
- IPC;
- hostname 和其他 UTS 状态,除非另行创建 UTS Namespace;
- cgroup 资源。
因此,容器通常不是只创建一个 Network Namespace,而是将它与 Mount、PID、User、UTS、IPC 和 cgroup 等机制组合使用。
2.2 默认命名空间与命名空间进程
宿主机启动时处于初始 Network Namespace,通常称为 root network namespace。直接执行:
ip link
ip addr
ip route
看到的就是当前进程所在命名空间的网络状态。
创建一个持久化命名空间:
ip netns add c1
ip netns list
ip netns add c1 的常见实现方式是:
- 创建一个新的 Network Namespace;
- 启动一个长期存在的进程作为该命名空间的持有者;
- 在
/var/run/netns/c1或发行版对应目录中建立命名空间引用; - 后续通过
ip netns exec c1 ...进入该命名空间执行命令。
查看新命名空间:
ip netns exec c1 ip link
ip netns exec c1 ip addr
ip netns exec c1 ip route
新命名空间通常只会有回环接口 lo,并且 lo 初始可能处于 DOWN 状态:
1: lo: <LOOPBACK> ...
inet 127.0.0.1/8 scope host lo
命名空间刚创建时没有可用于访问外部网络的物理接口,也没有默认路由。此时即使配置了地址,数据包也没有出口。
Network Namespace 的生命周期与引用有关。只要仍有进程处于该命名空间,或者仍有 /var/run/netns/<name> 这样的持久引用,它就不会被销毁。删除命名空间:
ip netns del c1
如果其中还有进程,删除操作可能延迟到最后一个引用消失后才完成。可以检查:
ip netns pids c1
一个常见误解是:删除命名空间后,所有连接到它的 veth 都会保留。实际上,veth 是成对设备;一端所在的命名空间销毁时,该端会消失,peer 通常也会随之被内核删除。
三、veth:跨命名空间连接的虚拟链路
3.1 veth 的结构
创建 veth pair:
ip link add veth-host type veth peer name veth-c1
这条命令产生两个相互连接的以太网设备:
veth-host <==== 虚拟以太网链路 ====> veth-c1
它们没有自动获得 IP 地址,也通常处于 DOWN 状态。查看关系:
ip -d link show veth-host
输出中通常可以看到 peer 的 ifindex 等信息。
将一端移动到 c1:
ip link set veth-c1 netns c1
此时:
veth-host仍在当前宿主机命名空间;veth-c1只在c1命名空间中可见;- 两端仍然是同一个 veth pair;
- 不能在宿主机中直接通过
ip link show veth-c1找到已移动的接口。
分别查看:
ip link show veth-host
ip netns exec c1 ip link show
设备移动本身不会配置 IP,也不会自动启用接口、设置 MAC、添加路由或启动 DHCP。网络连接只是“设备存在”,要形成可用链路,还需要配置二层和三层状态。
3.2 veth 传递的是二层帧,不是路由
veth 的基本行为是:
- 一端发送以太网帧;
- 内核把帧交给另一端;
- 另一端像普通以太网接口一样接收该帧;
- 接收端所在命名空间的协议栈继续处理它。
veth 不会自动:
- 修改 IP 地址;
- 选择路由;
- 执行 NAT;
- 充当 DHCP 服务器;
- 把一个命名空间的接口“直接变成”另一个命名空间的接口。
如果两端分别配置:
宿主机 veth-host:10.200.1.1/24
c1 veth-c1: 10.200.1.2/24
则两端处于同一个 IPv4 子网,可以通过 veth 直接交换 ARP 和 IP 数据包。但如果还希望连接多个命名空间,就需要 Bridge,或者为每个命名空间配置独立的三层路由路径。
四、Linux Bridge 与 veth 的组合
Linux Bridge 是内核中的二层交换设备。它学习源 MAC 地址,并根据目标 MAC 地址选择端口转发以太网帧。
创建 Bridge:
ip link add br-lab type bridge
ip link set br-lab up
创建两个 veth pair:
ip link add veth-c1-host type veth peer name eth0
ip link add veth-c2-host type veth peer name eth0
ip link set eth0 netns c1
ip link set eth0 netns c2
这里两个命名空间中都使用 eth0,因为接口名位于各自的 Network Namespace 中,并不冲突。
把宿主机侧 veth 加入 Bridge:
ip link set veth-c1-host master br-lab
ip link set veth-c2-host master br-lab
ip link set veth-c1-host up
ip link set veth-c2-host up
加入 Bridge 后,宿主机侧 veth 更像交换机端口。常见做法是不给 veth-c1-host 和 veth-c2-host 配置 IP,而把网关地址配置在 Bridge 本身:
ip addr add 10.200.1.1/24 dev br-lab
原因是:Bridge 代表这个二层广播域对应的宿主机三层接口。给每个端口分别配置地址,容易造成地址归属混乱,并不符合通常的 Bridge 使用模型。
配置命名空间接口:
ip netns exec c1 ip link set lo up
ip netns exec c1 ip link set eth0 up
ip netns exec c1 ip addr add 10.200.1.2/24 dev eth0
ip netns exec c1 ip route add default via 10.200.1.1
ip netns exec c2 ip link set lo up
ip netns exec c2 ip link set eth0 up
ip netns exec c2 ip addr add 10.200.1.3/24 dev eth0
ip netns exec c2 ip route add default via 10.200.1.1
此时的路径是:
c1 eth0
→ veth-c1-host
→ br-lab
→ veth-c2-host
→ c2 eth0
如果 c1 向 10.200.1.3 发送数据包,目标地址和源地址属于同一个 /24 网段,因此 c1 会认为目标是直连网络,不使用默认路由。它先通过 ARP 查询 10.200.1.3 对应的 MAC 地址,然后将数据帧交给 eth0。Bridge 根据 MAC 学习表转发该帧到 veth-c2-host。
五、地址、路由与下一跳的选择
5.1 地址前缀决定“直连还是网关”
假设 c1 有:
10.200.1.2/24
/24 等价于掩码:
255.255.255.0
网络地址为:
10.200.1.2 AND 255.255.255.0 = 10.200.1.0
所以 c1 认为:
10.200.1.0/24 是直连网络
当目标为 10.200.1.3 时,它直接 ARP 查询目标;当目标为 8.8.8.8 时,目标不在 10.200.1.0/24,它选择默认路由的下一跳 10.200.1.1。
查看路由:
ip netns exec c1 ip route
预期类似:
10.200.1.0/24 dev eth0 proto kernel scope link src 10.200.1.2
default via 10.200.1.1 dev eth0
第一条路由通常是在添加地址时由内核自动生成的。它说明:
- 目标是
10.200.1.0/24; - 通过
eth0发送; - 这是直连链路;
- 选用
10.200.1.2作为源地址。
第二条路由说明:
- 其他没有更具体匹配的 IPv4 目的地址;
- 发送给下一跳
10.200.1.1; - 从
eth0发出。
Linux 路由选择的核心原则是最长前缀匹配:匹配的前缀越长,路由越具体,优先级通常越高。例如:
10.200.1.0/24 dev eth0
10.200.0.0/16 via 10.200.1.254
default via 10.200.1.1
目标 10.200.1.3 同时匹配三条路由,但 /24 比 /16 和 /0 更长,所以选择第一条。
可以直接询问内核对某个目标的判断:
ip netns exec c1 ip route get 10.200.1.3
ip netns exec c1 ip route get 8.8.8.8
可能看到:
10.200.1.3 dev eth0 src 10.200.1.2
8.8.8.8 via 10.200.1.1 dev eth0 src 10.200.1.2
ip route get 比只看路由表更有价值,因为它展示了实际选择的接口、下一跳和源地址。
5.2 “有默认路由”不等于“能够访问外部网络”
c1 有默认路由,只能说明它知道把未知目标发给 10.200.1.1。数据包能否继续向外,还需要满足:
10.200.1.1对应的宿主机接口可达;- 宿主机开启 IPv4 转发;
- 宿主机自身有到外部网络的路由;
- 外部网络能处理源地址;
- 必要时宿主机执行 SNAT 或 MASQUERADE;
- 防火墙允许正向流量和返回流量。
因此,路由是逐跳决策,不是“配置一次默认网关后整个网络自动打通”。
六、从 c1 到外部地址的数据流
假设宿主机外部接口为 eth0,其地址为 192.0.2.10,上游网关为 192.0.2.1。这里的 192.0.2.0/24 仅用于文档示例,实际环境通常是 DHCP 或真实地址。
c1 访问 8.8.8.8:443 时,数据流大致如下:
sequenceDiagram
participant C as c1
participant B as br-lab/veth
participant H as 宿主机 IP 转发
participant N as NAT/conntrack
participant U as 上游网络
C->>B: 目标 8.8.8.8,不匹配直连路由,发给 10.200.1.1
B->>H: 宿主机接收目的为 10.200.1.1 的帧
H->>H: IPv4 转发查找外部路由
H->>N: 进入 postrouting,匹配 MASQUERADE
N->>U: 源地址改为宿主机外部地址
U-->>N: 返回包到宿主机外部地址
N-->>H: 根据 conntrack 恢复目标为 10.200.1.2
H-->>B: 转发到 br-lab
B-->>C: 经 veth 交给 c1 eth0
具体地说,初始数据包可能是:
源 IP:10.200.1.2
源端口:40000
目的 IP:8.8.8.8
目的端口:443
上游网络通常不会为 10.200.1.0/24 这样的实验私有网段提供返回路由,因此宿主机在外发方向执行源地址转换:
源 IP:192.0.2.10
源端口:随机或映射后的端口
目的 IP:8.8.8.8
目的端口:443
返回包到达宿主机后,conntrack 根据连接状态把目标还原为:
目标 IP:10.200.1.2
目标端口:40000
再由宿主机路由和 Bridge 将其送回命名空间。
七、完整实验:两个命名空间、Bridge、路由和 NAT
7.1 选择外部接口
先在宿主机查看默认路由:
ip route show default
可能输出:
default via 192.0.2.1 dev eth0 proto dhcp src 192.0.2.10 metric 100
其中 eth0 是外部接口。也可以使用:
ip route get 8.8.8.8
输出中的 dev 就是访问该目标时实际使用的接口。
将它保存到变量:
WAN_IF=$(ip route show default | awk 'NR==1 {print $5}')
printf '外部接口: %s\n' "$WAN_IF"
如果变量为空,说明宿主机没有默认路由,后续 NAT 实验没有外部出口。
7.2 创建命名空间和二层拓扑
以下命令需要在宿主机 root shell 中执行:
set -e
ip netns add c1
ip netns add c2
ip link add br-lab type bridge
ip link set br-lab up
ip link add veth-c1-host type veth peer name eth0
ip link add veth-c2-host type veth peer name eth0
ip link set eth0 netns c1
ip link set eth0 netns c2
ip link set veth-c1-host master br-lab
ip link set veth-c2-host master br-lab
ip link set veth-c1-host up
ip link set veth-c2-host up
ip addr add 10.200.1.1/24 dev br-lab
ip netns exec c1 ip link set lo up
ip netns exec c1 ip link set eth0 up
ip netns exec c1 ip addr add 10.200.1.2/24 dev eth0
ip netns exec c1 ip route add default via 10.200.1.1
ip netns exec c2 ip link set lo up
ip netns exec c2 ip link set eth0 up
ip netns exec c2 ip addr add 10.200.1.3/24 dev eth0
ip netns exec c2 ip route add default via 10.200.1.1
检查宿主机侧:
ip link show br-lab
ip addr show br-lab
bridge link
bridge fdb show br br-lab
检查 c1:
ip netns exec c1 ip addr
ip netns exec c1 ip route
预期路由至少包含:
10.200.1.0/24 dev eth0 proto kernel scope link src 10.200.1.2
default via 10.200.1.1 dev eth0
7.3 验证二层和三层连接
先从命名空间访问宿主机 Bridge 地址:
ip netns exec c1 ping -c 2 10.200.1.1
预期成功。这个测试同时验证了:
c1的eth0已启用;- veth 两端存在;
- veth 宿主机侧已加入 Bridge;
- Bridge 处于启用状态;
c1能通过 ARP 找到10.200.1.1;- 宿主机能处理发往本地 Bridge 地址的 ICMP。
再测试两个命名空间之间的访问:
ip netns exec c1 ping -c 2 10.200.1.3
该路径不需要 NAT,也不需要宿主机 IP 转发。原因是:
10.200.1.2/24与10.200.1.3/24在同一子网;c1直接通过 ARP 查询10.200.1.3;- Bridge 只负责二层转发;
- 目标 IP 属于
c2,不是宿主机本地地址。
可以分别查看邻居缓存:
ip netns exec c1 ip neigh
ip netns exec c2 ip neigh
正常时可能出现:
10.200.1.1 dev eth0 lladdr aa:bb:cc:dd:ee:01 REACHABLE
10.200.1.3 dev eth0 lladdr aa:bb:cc:dd:ee:03 REACHABLE
MAC 地址是示例,实际值由内核随机或自动生成。
7.4 开启宿主机 IPv4 转发
查看当前值:
sysctl net.ipv4.ip_forward
如果输出为:
net.ipv4.ip_forward = 0
宿主机不会在不同接口之间转发 IPv4 数据包。即使 c1 能 ping 通宿主机的 10.200.1.1,访问外部地址也会失败。
临时启用:
sysctl -w net.ipv4.ip_forward=1
这是运行时内核状态,重启后是否保留取决于发行版的 sysctl 配置。生产环境不应只依赖一次性的 sysctl -w。
注意:对本实验而言,c1 和 c2 的同网段通信不依赖该开关,因为那是 Bridge 的二层转发;访问外部网络才需要宿主机进行三层转发。
7.5 添加隔离的 nftables NAT 规则
添加一个独立的 IPv4 nftables 表:
nft add table ip wr_lab
nft 'add chain ip wr_lab postrouting { type nat hook postrouting priority 100; policy accept; }'
nft add rule ip wr_lab postrouting oifname "$WAN_IF" ip saddr 10.200.1.0/24 masquerade
检查规则:
nft list table ip wr_lab
预期类似:
table ip wr_lab {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
oifname "eth0" ip saddr 10.200.1.0/24 masquerade
}
}
这条规则的条件是:
- 地址族为 IPv4;
- 数据包进入
postrouting阶段; - 出接口是宿主机外部接口;
- 源地址属于
10.200.1.0/24。
满足条件时,masquerade 使用外部接口当前地址作为源地址执行动态源地址转换。它适合外部地址可能通过 DHCP 变化的情况。若外部地址固定,也可以使用 snat to <固定地址>,但地址必须与实际外部接口配置一致。
7.6 测试外部连通性
先测试宿主机自身:
ping -c 2 8.8.8.8
如果宿主机自己都不能访问该地址,命名空间 NAT 也不可能修复宿主机的上游连通性。
然后从 c1 测试:
ip netns exec c1 ping -c 2 8.8.8.8
也可以使用 TCP 或 HTTP 测试,因为某些网络会过滤 ICMP:
ip netns exec c1 curl --connect-timeout 5 -I https://example.com
命名空间中的 DNS 可能仍然不可用。Network Namespace 隔离了网络状态,但不会自动生成 /etc/resolv.conf。如果 curl https://example.com 因 DNS 失败,可以先测试 IP,或者在实验命名空间中配置可用的 DNS:
ip netns exec c1 cat /etc/resolv.conf
由于 ip netns exec 默认使用宿主机文件系统,c1 和宿主机可能看到同一个 /etc/resolv.conf。这不是 DNS 网络连通性保证;DNS 服务器地址、路由、防火墙和服务状态仍需分别验证。
八、NAT 到底解决了什么问题
8.1 没有 NAT 时的返回路径
假设没有 NAT,外部服务器看到:
源地址:10.200.1.2
外部服务器回复时,需要把包发回 10.200.1.2。但 10.200.1.0/24 是实验私有网段,上游路由器通常没有到该网段的路由,或者直接丢弃来自私有地址的包。
此时出方向可能成功,返回方向失败:
c1 → 宿主机 → 上游 → 外部服务器
c1 ← 宿主机 ← 上游 ← 外部服务器
返回路由不存在
NAT 通过把源地址改成宿主机外部地址,使返回包先回到宿主机。conntrack 保存转换前后的连接关系,宿主机再把返回包还原并转发给 c1。
8.2 NAT 不是路由
路由回答:
这个数据包下一跳应该从哪个接口发出?
NAT回答:
这个数据包经过边界时,源地址、目标地址或端口是否需要转换?
例如,下面的默认路由仍然是必要的:
default via 10.200.1.1 dev eth0
NAT 不会替命名空间生成默认路由。反过来,即使路由完全正确,缺少 NAT 也可能导致返回路径失败。
8.3 NAT 不是防火墙
masquerade 只描述地址转换,不等同于“允许流量”。nftables 中还可以有过滤链,发行版或云平台也可能通过 nftables、iptables、firewalld 或安全代理配置过滤规则。
在复杂系统中,需要分别检查:
nft list ruleset
iptables -S
iptables -t nat -S
iptables 在现代发行版上可能是基于 nftables 的兼容前端,也可能是 legacy 实现。不要根据命令名称假定底层规则一定相同。
九、用抓包把数据流拆开
当 c1 访问失败时,最有效的方式不是反复修改路由,而是确定数据包停在哪一跳。
宿主机上可以分别抓:
tcpdump -ni br-lab icmp
tcpdump -ni veth-c1-host icmp
tcpdump -ni "$WAN_IF" icmp
然后另开终端执行:
ip netns exec c1 ping -c 1 8.8.8.8
根据观察结果判断:
| 观察结果 | 更可能的问题 |
|---|---|
c1 的 eth0 上没有包 |
接口未启用、应用未发包、路由选择失败 |
veth-c1-host 有包,br-lab 没有 |
veth 或 Bridge 状态异常 |
br-lab 有包,但外部接口没有 |
未开启转发、宿主机路由或过滤规则阻止 |
外部接口有源地址 10.200.1.2 的包 |
NAT 规则未匹配,或规则链未执行 |
| 外部接口有宿主机地址的包,但无返回 | 上游、远端服务或外部防火墙问题 |
有返回包到宿主机,但 c1 看不到 |
conntrack/NAT 回程、转发过滤或 Bridge 路径问题 |
观察 Bridge 的 MAC 学习表:
bridge fdb show br br-lab
如果 c1 和 c2 之间已经有流量,Bridge 通常会学习到对应 MAC 位于哪个端口。若没有学习记录,可能是接口没有真正发送帧,或者端口未正确加入 Bridge。
检查接口状态:
ip link show br-lab
ip link show veth-c1-host
ip netns exec c1 ip link show eth0
需要关注:
- 是否有
UP; - 是否有
LOWER_UP; - 接口是否位于预期命名空间;
- 地址是否配置在正确的接口上。
UP 表示管理状态启用;LOWER_UP 表示内核认为链路层可用。对于 veth,链路状态通常受 peer 状态影响,因此只看一个接口不够。
十、常见失败路径与反例
10.1 只配置地址,不启用接口
错误示例:
ip netns exec c1 ip addr add 10.200.1.2/24 dev eth0
ip netns exec c1 ping -c 1 10.200.1.1
如果 eth0 仍为 DOWN,通常无法正常发送数据。修复:
ip netns exec c1 ip link set eth0 up
回环接口也应显式启用:
ip netns exec c1 ip link set lo up
否则一些依赖 127.0.0.1 的进程行为会异常。
10.2 默认路由指向错误的网关
如果 c1 配置为:
10.200.1.2/24
default via 10.200.2.1
而 10.200.2.1 不在 eth0 的直连网段内,内核通常无法通过当前链路解析该下一跳,添加路由可能直接失败,或者路由存在但无法有效发送。
可以检查:
ip netns exec c1 ip route get 8.8.8.8
ip netns exec c1 ip neigh
网关 10.200.1.1 必须能够通过命名空间中的某个接口到达。网关地址通常需要属于该接口的直连前缀,除非使用特殊的 onlink 配置;onlink 只是绕过内核的网关直连检查,不会凭空创建二层可达性。
10.3 把宿主机 veth 的 IP 配在错误位置
可以让宿主机侧 veth 配 10.200.1.1/24,也可能工作;但在使用 Bridge 的拓扑中,更清晰的模型是:
veth-c1-host:无三层地址
veth-c2-host:无三层地址
br-lab: 10.200.1.1/24
Bridge 设备承担宿主机在该广播域中的三层身份。将地址随意配置在 Bridge 端口上,可能导致 ARP 行为、地址归属和路由观察结果不符合预期。
10.4 开启转发但仍然失败
检查:
sysctl net.ipv4.ip_forward
nft list ruleset
ip route
转发失败可能来自:
- IPv4 forwarding 为
0; - nftables 或 iptables 的 forward 链拒绝;
- firewalld、ufw 或云平台规则拒绝;
- 反向路径过滤
rp_filter丢弃非对称路径数据包; - 外部接口名称写错,导致 NAT 规则不匹配;
- NAT 只配置了 IPv4,但测试使用 IPv6;
- 上游网络阻止 ICMP,而不是命名空间网络本身故障。
rp_filter 是 Linux 的反向路径过滤机制,用于检查入包源地址是否应当从同一接口到达。多路径或非对称路由环境中,它可能造成看似“包已经到达接口,但协议栈没有继续处理”的现象。不要在不了解全局网络设计时直接关闭它,应结合 ip route get、抓包和系统安全策略判断。
10.5 用 127.0.0.1 误测宿主机服务
在 c1 中执行:
ip netns exec c1 curl http://127.0.0.1:8080
访问的是 c1 自己的 loopback,不是宿主机的 127.0.0.1:8080。每个 Network Namespace 有独立的 loopback 和端口空间。
如果要访问宿主机服务,应让服务监听宿主机可达地址,例如 10.200.1.1,然后:
ip netns exec c1 curl http://10.200.1.1:8080
服务若只监听宿主机 127.0.0.1,从其他 Network Namespace 无法直接访问,除非额外设置代理或端口转发。
10.6 删除 veth 的一端
ip link delete veth-c1-host
删除 veth pair 的一端会导致另一端也消失。执行后:
ip netns exec c1 ip link show
可能看不到 eth0。容器运行时如果误删宿主机侧接口,容器内部接口也会中断;这不是“拔掉宿主机一侧但保留容器接口”的可恢复状态。
十一、手工配置与容器运行时的差异
手工实验直接执行了容器运行时通常会自动完成的工作:
创建 namespace
→ 创建 veth pair
→ 将一端移入 namespace
→ 重命名为 eth0
→ 配置 MAC、地址和路由
→ 连接到 Bridge 或其他网络设备
→ 配置转发、过滤、NAT
→ 注入 DNS 和生命周期管理
Docker、containerd、CRI-O、CNI 插件等实现可能选择不同拓扑:
- veth 接入 Linux Bridge;
- veth 接入 Open vSwitch;
- 使用 macvlan 或 ipvlan;
- 使用 overlay 网络;
- 使用 eBPF 数据路径;
- 使用独立网络插件管理地址、路由和策略。
因此,“容器网络就是一对 veth”并不准确。更准确的说法是:veth 是最常见的容器网络接入手段之一,但完整容器网络还需要二层连接、三层地址、路由、转发、过滤、NAT 或可达性设计。
容器编排系统还需要处理:
- IP 地址分配与回收;
- namespace 删除;
- veth 命名冲突;
- 多宿主机地址规划;
- 服务发现;
- 网络策略;
- MTU;
- 并发创建和清理;
- 宿主机重启后的恢复。
手工执行 ip 命令适合理解内核机制和诊断问题,不适合替代生产环境中的声明式网络控制器。
十二、命名空间持久化与进程生命周期
使用 ip netns add 创建的命名空间具有名称引用。也可以让一个已有进程作为命名空间持有者:
unshare --net --fork sleep 1000 &
PID=$!
nsenter -t "$PID" -n ip link
此时 nsenter -t "$PID" -n 根据进程的 Network Namespace 文件描述符进入命名空间。进程退出后,如果没有其他引用,该命名空间可能被销毁。
查看某个进程所在的命名空间:
readlink /proc/$$/ns/net
readlink /proc/$(pgrep -n sleep)/ns/net
如果两个路径中的 inode 相同,说明进程处于同一个 Network Namespace;不同则说明网络栈隔离。
ip netns exec 不是把命令“复制”到另一个环境,而是让命令进程调用 setns() 进入指定命名空间后再执行。命令结束时,进程退出,命名空间本身是否消失取决于是否仍有其他引用。
十三、实验清理与状态恢复
先删除实验 nftables 表:
nft delete table ip wr_lab
删除命名空间:
ip netns del c1
ip netns del c2
删除 Bridge:
ip link set br-lab down
ip link delete br-lab type bridge
如果 veth 因命名空间删除已经消失,不能重复删除;可以检查:
ip link show veth-c1-host
ip link show veth-c2-host
恢复 IPv4 转发时,不应盲目写入 0,因为宿主机原本可能就是路由器。执行实验前可以保存旧值:
OLD_IP_FORWARD=$(sysctl -n net.ipv4.ip_forward)
清理时恢复:
sysctl -w net.ipv4.ip_forward="$OLD_IP_FORWARD"
在生产机器上,如果实验前没有记录状态,就不要凭猜测关闭转发。应先确认该宿主机是否承担路由、虚拟化、VPN、容器或 Kubernetes 节点功能。
十四、IPv6、MTU 和其他边界
本文只配置 IPv4。IPv6 的行为不能简单看作“把 IPv4 地址换成 IPv6 地址”:
- IPv6 邻居发现使用 ICMPv6,不使用 ARP;
- IPv6 默认路由通常通过 Router Advertisement 或手工配置;
- NAT66 并不是 IPv4 NAT 的默认等价物;
- 宿主机需要启用 IPv6 转发;
- 防火墙必须允许必要的 ICMPv6 控制报文。
veth 和 Bridge 还会引入 MTU 问题。若后续在外层再加入 VXLAN、Geneve、WireGuard 或其他封装,实际可用 MTU 会减少。路径上的某一层 MTU 不匹配时,可能表现为:
- 小包 ping 成功,大包失败;
- TCP 建连成功但传输卡住;
- HTTPS 页面部分加载;
- 分片或 PMTUD 行为异常。
可使用:
ip link show
tracepath 8.8.8.8
检查接口 MTU 和路径 MTU。不要只根据“接口都显示 UP”判断网络完整可用。
十五、用一组判断建立故障模型
对于本实验,连通性可以拆成一组明确条件。假设 c1 访问外部目标 D,则至少需要:
命名空间接口存在且 UP
∧ c1 有有效源地址
∧ c1 的路由能选择下一跳 10.200.1.1
∧ ARP 能解析 10.200.1.1
∧ veth 两端连接到正确的 Bridge
∧ 宿主机有到 D 的路由
∧ net.ipv4.ip_forward = 1
∧ 正向防火墙允许
∧ NAT 在正确的 postrouting 条件下匹配
∧ 上游接受并返回转换后的连接
∧ 回程过滤和路由允许数据包回到 c1
这组条件解释了为什么不同测试结果具有不同含义:
c1 → 10.200.1.1 成功
只证明本地命名空间到宿主机网关的链路基本成立,不能证明 NAT 和外部路由正确。
c1 → c2 成功
主要证明命名空间、veth、Bridge、ARP 和同网段通信正常,也不能证明宿主机 IPv4 转发正常。
c1 → 8.8.8.8 失败
还不能直接推出“veth 有问题”。必须结合 ip route get、ip neigh、tcpdump、sysctl 和 nftables 计数器定位具体环节。
最终,Network Namespace 提供的是网络状态隔离,veth 提供的是跨命名空间的虚拟二层连接,Linux Bridge 提供的是二层转发,路由决定三层下一跳,宿主机转发允许数据包跨接口移动,而 NAT 解决的是地址转换和返回路径问题。它们各自负责不同层次;只有把这些职责按数据流串起来,才能准确理解容器网络为什么能工作,以及为什么某一层出错时会出现特定的失败表现。
系列导航与关联阅读
- 系列入口:Linux 完整学习路线:从内核与文件系统到网络、性能和生产运维
- 上一篇:Linux Bridge、VLAN、Bond 与 Team:二层网络、冗余和诊断
- 下一篇:Linux 网络内核调优:Socket Buffer、Backlog、Port、Conntrack 和验证
- 延伸:Linux namespaces 深入:PID、Mount、Network、User 和隔离组合
官方资料
本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。

评论
0 条讨论