摘要:从 Network Namespace、veth pair 和 Linux Bridge 理解容器网络,梳理 ARP、邻居表、MAC 转发表与路由表的转发职责。
1. Network Namespace
Linux Namespace 是 Linux 内核提供的一种资源隔离机制。
不同类型的 Namespace 可以隔离不同资源,例如:
| Namespace | 隔离的主要资源 |
|---|---|
net | 网络设备、路由表、网络协议栈等 |
pid | 进程 ID 视图 |
mnt | 挂载点视图 |
uts | 主机名和域名信息 |
ipc | 进程间通信资源 |
user | 用户和组 ID 映射 |
本节重点关注 net,也就是 Network Namespace。
1. 没有网络隔离时
假设 Linux 主机上运行两个普通进程:
flowchart TB
subgraph G0["Linux 主机"]
direction TB
N0["进程 A"]
N1["进程 B"]
end
subgraph G1["共享主机网络环境"]
direction TB
N2["网卡"]
N3["IP 地址"]
N4["路由表"]
N5["网络协议栈"]
end
N0 -.->|"共享"| N2
N0 -.->|"共享"| N3
N0 -.->|"共享"| N4
N0 -.->|"共享"| N5
N1 -.->|"共享"| N2
N1 -.->|"共享"| N3
N1 -.->|"共享"| N4
N1 -.->|"共享"| N5
它们通常共享主机的网络命名空间,因此使用同一套网络设备和路由环境。
2. 使用 Network Namespace 后
可以为不同进程创建独立的网络环境:
宿主机 Network Namespace
eth0:192.168.1.10 独立路由表、网络设备与协议栈
Namespace A eth0:10.0.1.2 自己的路由表 自己的网络协议栈
Namespace B eth0:10.0.2.2 自己的路由表 自己的网络协议栈
示意:每个网络命名空间拥有独立的网络视图,命名空间之间需要通过网络连接或其他机制进行通信。
每个 Network Namespace 都可以拥有自己的:
- 网络接口。
- IP 地址。
- 路由表。
- 邻居表。
- 网络协议栈状态。
因此,两个 Namespace 可以分别拥有一个叫作 eth0 的接口,却使用不同的 IP 地址。
关键理解:网络命名空间隔离的是网络环境,而不是自动创建一条网络连接。 要让两个 Namespace 通信,还需要 veth pair、Bridge、路由或其他网络机制。
2. veth pair:连接两个网络命名空间
理解了 Network Namespace,接下来学习 Linux 容器网络中最重要的虚拟网络设备之一:veth pair。
veth pair 是一对互相连接的虚拟以太网接口。可以把它想象成一根虚拟网线:从一端发出的数据帧,会从另一端进入。
例如:
flowchart TB
subgraph G0["veth pair"]
direction TB
N1["Pod 侧:eth0"]
N2["主机侧:vethXXXX"]
end
N0["Pod 网络命名空间<br/>eth0 · 10.10.1.2/24"]
N3["宿主机网络<br/>Linux Bridge / 路由 / 其他数据面"]
N0 <--> N1
N1 <--> N2
N2 <--> N3
注意:veth pair 本身不负责分配 IP 地址,也不会自动完成跨节点通信。它只提供两个网络接口之间的虚拟连接。
在常见的 Bridge 型容器网络中:
- 一端放进 Pod 的 Network Namespace,通常命名为
eth0。 - 另一端留在宿主机,通常命名为
veth...。 - 主机侧接口可以连接 Linux Bridge,也可以配合路由等机制工作。
并非所有 Kubernetes CNI 都采用完全相同的拓扑。例如,某些 CNI 采用路由或 eBPF 数据面,不一定通过传统 Linux Bridge 转发。
3. Linux Bridge 是什么?
Linux Bridge 可以理解为 Linux 内核中的一个虚拟二层交换机。
真实网络中,多台电脑可以连接到一台以太网交换机,通过 MAC 地址进行二层转发。
Linux Bridge 做的事情类似,只不过它运行在 Linux 主机内部,可以连接虚拟网络接口。
flowchart TB
N0["Pod A<br/>eth0 · 10.10.1.2"]
N1["veth pair<br/>连接 Pod A 与宿主机"]
N2["Linux Bridge:br0<br/>虚拟二层交换机"]
N3["veth-b<br/>Pod B 的主机侧接口"]
N4["veth-c<br/>Pod C 的主机侧接口"]
N5["Pod B<br/>10.10.1.3"]
N6["Pod C<br/>10.10.1.4"]
N0 --> N1
N1 --> N2
N2 --> N3
N2 --> N4
N3 --> N5
N4 --> N6
图中展示的是典型的 Bridge 型容器网络模型。实际 Kubernetes CNI 可能采用路由、Overlay 或 eBPF 等其他实现。
理解 Linux Bridge 时,要记住:Bridge 主要依据以太网帧的 MAC 地址进行二层转发,而路由器主要依据 IP 地址进行三层转发。
4. Bridge 如何转发数据?
假设三个 Pod 位于同一个 IP 子网:
| Pod | IP 地址 | MAC 地址(示例) |
|---|---|---|
| Pod A | 10.10.1.2 | 02:00:00:00:00:0a |
| Pod B | 10.10.1.3 | 02:00:00:00:00:0b |
| Pod C | 10.10.1.4 | 02:00:00:00:00:0c |
Pod A 要向 Pod B 发送数据时,需要知道目标 MAC 地址。
如果 Pod A 不知道 10.10.1.3 对应的 MAC 地址,它会通过 ARP 请求进行解析。
拿到目标 MAC 地址后,Pod A 构造以太网帧,并通过自己的 eth0 发出。
数据帧经过 veth pair 进入宿主机,再由 Bridge 根据 MAC 地址转发给 Pod B 对应的接口。
完整路径如下:
flowchart TB
N0["Pod A eth0"]
N1["veth pair"]
N2["Linux Bridge br0"]
N3["Pod B 对应的 veth"]
N4["Pod B eth0"]
N0 --> N1
N1 --> N2
N2 -->|"根据目标 MAC 查找转发表"| N3
N3 --> N4
如果 Bridge 尚未学习目标 MAC 对应的端口,或者目的 MAC 是广播地址,可能需要进行泛洪转发。具体行为取决于 Bridge 的转发表和配置。
Bridge 的 FDB(转发数据库)与 Namespace 的邻居表不是同一张表:
- 邻居表:记录 IP 地址与 MAC 地址的映射。
- Bridge FDB:记录 MAC 地址从哪个 Bridge 端口可以到达。
这是两个经常被混淆的概念。
5. ARP:知道 IP,为什么还需要 MAC?
这是理解局域网通信的关键。
IP 地址用于三层寻址,而以太网帧需要目标 MAC 地址才能在二层网络中传输。
例如,Pod A 要访问 10.10.1.3:
- Pod A 检查自己的路由表。
- 判断目标 IP 位于直连子网。
- 查询邻居表,寻找
10.10.1.3对应的 MAC 地址。 - 如果没有有效映射,通过 ARP 请求询问局域网中的设备。
- Pod B 收到 ARP 请求后,返回自己的 MAC 地址。
- Pod A 缓存这个映射,再发送实际的数据帧。
flowchart TB
N0["Pod A<br/>想访问 10.10.1.3"]
N1["ARP Request:广播询问<br/>谁拥有 10.10.1.3?"]
N2["Pod B:ARP Reply<br/>10.10.1.3 对应我的 MAC 地址"]
N3["Pod A 缓存映射并发送数据<br/>IP 数据包封装进以太网帧"]
N0 --> N1
N1 --> N2
N2 --> N3
这里有一个非常重要的细节:
ARP 只负责解析同一链路上下一跳的 MAC 地址,并不是所有情况下都解析最终目标 IP 的 MAC。
如果目标 IP 不在本地子网,Pod 通常会把数据包发送给默认网关。此时,Pod 需要解析的是网关的 MAC 地址,而不是远端 Pod 的 MAC 地址。
这也解释了为什么跨节点网络中,即使源 Pod 知道目标 Pod 的 IP 地址,也不一定能直接获得目标 Pod 的 MAC 地址。
6. 邻居表:Linux 如何记住 IP 与 MAC 的对应关系?
每次访问一个 IP 都重新发送 ARP 请求会产生不必要的网络开销。
因此,Linux 会缓存解析结果。
在 Linux 中,可以使用以下命令查看邻居表:
ip neigh show如果想查看特定接口的记录:
ip neigh show dev eth0输出可能类似:
10.10.1.3 dev eth0 lladdr 02:00:00:00:00:0b REACHABLE10.10.1.4 dev eth0 lladdr 02:00:00:00:00:0c STALE字段解释:
| 字段 | 含义 |
|---|---|
10.10.1.3 | 目标 IP 地址 |
dev eth0 | 使用的网络接口 |
lladdr | 对应的链路层地址,通常是 MAC 地址 |
REACHABLE | 映射被认为仍然可达 |
STALE | 缓存存在,但尚未确认其当前可达性 |
其他常见状态还有 DELAY、PROBE、INCOMPLETE 和 FAILED。这些状态可以帮助你判断邻居解析是否成功。
1. ARP 失败会出现什么现象?
例如:
10.10.1.3 dev eth0 INCOMPLETE如果长时间无法完成邻居解析,可能意味着:
- 目标设备没有响应。
- 二层网络连接有问题。
- 接口或 VLAN 配置不正确。
- 广播请求没有到达目标。
- 目标 IP 地址配置错误。
不过,看到 INCOMPLETE 并不能直接证明 Bridge 故障,还需要结合路由、接口状态和抓包结果判断。
7. 先理解:交换机和路由器有什么区别?
我们先建立一个简单模型。
flowchart LR
subgraph G0["网段 A:10.10.1.0/24"]
direction LR
N0["主机 A<br/>10.10.1.10"]
N1["主机 B<br/>10.10.1.20"]
N2["二层交换机 / Linux Bridge"]
end
subgraph G1["网段 B:10.10.2.0/24"]
direction LR
N4["主机 C<br/>10.10.2.10"]
end
N3["路由器 / Linux 路由节点<br/>连接两个 IP 网段"]
N0 <--> N2
N1 <--> N2
N2 <--> N3
N3 <--> N4
主机 A 访问主机 B 时,两者在同一个子网中,通常可以直接通过二层网络通信。
但是,当主机 A 访问主机 C 时,目标 IP 10.10.2.10 不属于本地 10.10.1.0/24 网段。此时主机 A 通常需要将数据包交给路由器,由路由器继续转发。
因此:
- 二层交换:主要依据 MAC 地址转发以太网帧。
- 三层路由:根据 IP 地址和路由表决定下一跳。
Linux Bridge 属于二层转发机制,而 Linux 本身也可以充当三层路由器。
8. 路由表:Linux 如何决定数据包往哪里走?
Linux 通过路由表决定 IP 数据包应该从哪个接口发出,以及应该交给哪个下一跳。
查看路由表:
ip route例如:
default via 192.168.1.1 dev eth010.10.1.0/24 dev eth1 proto kernel scope link src 10.10.1.10192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10逐条解释:
| 路由 | 含义 |
|---|---|
default via 192.168.1.1 dev eth0 | 没有更具体路由时,经 eth0 交给网关 |
10.10.1.0/24 dev eth1 | 目标属于这个网段时,从 eth1 直接发送 |
192.168.1.0/24 dev eth0 | 目标属于这个网段时,从 eth0 直接发送 |
其中:
via表示下一跳网关。dev表示发送接口。src表示该路由通常使用的首选源地址。default表示默认路由,等价于0.0.0.0/0。
1. Linux 如何匹配路由?
假设路由表是:
10.0.0.0/8 via 192.168.1.110.10.0.0/16 via 192.168.1.210.10.1.0/24 via 192.168.1.3default via 192.168.1.254现在目标 IP 是 10.10.1.20。
它同时匹配前三条路由,但 Linux 通常选择最长前缀匹配,也就是最具体的路由:
10.10.1.0/24 via 192.168.1.3如果目标是 10.10.2.20,则 /24 路由不再匹配,会选择:
10.10.0.0/16 via 192.168.1.2如果目标是 172.16.1.10,上述路由都不匹配,才会考虑默认路由。
实际路由选择还可能受到策略路由、路由表优先级和其他内核配置影响。
可以使用以下命令查询 Linux 对具体目标的路由判断:
ip route get 10.10.1.20ip route get 10.10.2.20ip route get 172.16.1.10这是排查 Linux 网络问题时非常实用的命令。
9. 默认网关:为什么访问外部网络需要它?
假设一台 Linux 主机的配置如下:
IP:192.168.1.10掩码:255.255.255.0网关:192.168.1.1主机要访问 192.168.1.20 时,目标与本机处于同一子网,通常可以直接通信。
但是,如果访问 8.8.8.8,目标不在本地子网中,主机就需要查找路由表。
假设存在默认路由:
default via 192.168.1.1 dev eth0数据包就会被交给 192.168.1.1,由它继续转发。
flowchart TB
N0["Linux 主机<br/>192.168.1.10"]
N1["默认网关<br/>192.168.1.1"]
N2["其他网络 / Internet<br/>继续逐跳转发"]
N0 --> N1
N1 --> N2
要注意,默认网关并不是互联网本身,也不保证目标一定可达。它只是主机在没有更具体路由时使用的下一跳。
10. 跨网段通信时,ARP 到底解析谁?
这是本节最重要的知识点之一。
假设:
主机 A:IP:10.10.1.10/24MAC:AA-AA-AA-AA-AA-AA
网关:IP:10.10.1.1MAC:BB-BB-BB-BB-BB-BB
主机 C:IP:10.10.2.10/24主机 A 想访问主机 C。
由于 10.10.2.10 不在 A 的本地子网中,A 会根据路由表决定把数据包发送给网关。
因此,A 需要解析的是:
网关 10.10.1.1 的 MAC 地址,而不是远端主机 10.10.2.10 的 MAC 地址。
数据包的变化可以这样理解:
flowchart TB
subgraph G0["第一跳"]
direction TB
N0["主机 A"]
N1["网关"]
end
subgraph G1["第二跳"]
direction TB
N2["网关"]
N3["目标网络"]
end
subgraph G2["最后一跳"]
direction TB
N4["到达主机 C<br/>目标 IP 仍然是 10.10.2.10<br/>以太网目的 MAC 是 C 的 MAC"]
end
N0 -->|"IP 头部:源 10.10.1.10,目的 10.10.2.10<br/>以太网头部:源 MAC 为 A,目的 MAC 为网关"| N1
G0 --> G1
N2 -->|"网关查找路由表,决定下一跳<br/>重新封装新的以太网头部"| N3
G1 --> G2
在没有 NAT 等额外处理的普通路由场景中:
- 每一跳的以太网源 MAC 和目的 MAC 都可能变化。
- IP 源地址和目的地址通常保持不变。
- IP TTL 每经过一个三层转发节点通常减 1。
这就是为什么抓包时,同一个 IP 数据包在不同网络接口上可能具有不同的以太网头部。
11. 容器网络创建过程与 Kubernetes
现在回到 Kubernetes。
当你在集群中创建一个普通 Pod 时,容器运行时与 CNI 插件会共同完成 Pod Sandbox 的网络配置。具体过程取决于运行时和插件实现。
在常见的 veth 型网络中,可以把它理解为:
flowchart TB
N0["Kubernetes 创建 Pod"]
N1["创建 Pod 网络命名空间"]
N2["创建 veth pair"]
N3["一端放入 Pod Namespace"]
N4["另一端连接宿主机网络"]
N5["配置 IP、路由和网络规则"]
N6["Pod 获得网络连接能力"]
N0 --> N1
N1 --> N2
N2 --> N3
N2 --> N4
N3 --> N5
N4 --> N5
N5 --> N6
但请注意,Kubernetes 并不规定所有 CNI 都必须采用 veth pair + Linux Bridge 的实现方式。这是理解容器网络的经典模型,而不是每个集群的固定结构。
12. 观察真实 Pod 的网络命名空间
如果你有 Linux 节点的 root 权限,可以继续观察真实 Kubernetes Pod。
1. 找到 Pod 所在节点
kubectl get pod <pod-name> -o wide记录它所在的 Node。
2. 查看节点网络接口
登录该节点:
ip -br addrip route在传统 veth 网络中,可能会看到一些 veth... 接口;使用其他 CNI 时,也可能看到隧道设备、路由设备或其他网络接口。
3. 通过进程进入网络命名空间
在节点上查找容器进程 PID。具体方法取决于使用的容器运行时。
拿到目标进程 PID 后,可以执行:
sudo nsenter -t <PID> -n ip addrsudo nsenter -t <PID> -n ip route其中:
-t <PID>:指定目标进程。-n:进入目标进程所在的 Network Namespace。
这样就可以从宿主机直接查看该网络命名空间里的接口和路由,而不必依赖 Pod 镜像中是否安装了 ip 命令。
注意,容器进程 PID 必须来自目标 Pod 对应的容器或 Sandbox 网络命名空间,不能随意选择一个进程。
13. Bridge 与路由的排查命令
实际排障时,可以先执行:
# 查看节点接口ip -br addr
# 查看路由ip routeip route get < 目标 IP>
# 查看邻居表ip neigh show
# 查看 Bridgebridge link show
# 查看 Bridge 的 MAC 转发表bridge fdb show这些命令可以帮助你区分:
- 路由是否正确。
- 邻居解析是否成功。
- Bridge 是否学习到 MAC 地址。
- 网络接口是否处于正常状态。
如果使用 Cilium 或其他不依赖传统 Linux Bridge 的 CNI,bridge 命令没有相关输出并不代表网络故障。
ip route get < 目标 IP> 能帮助确认 Linux 当前如何选择下一跳与发送接口。
14. 跨节点网络中的路由、封装与返回路径
假设集群中的两个 Pod 分别位于不同节点:
Pod A:10.244.1.10Node A:192.168.1.10
Pod B:10.244.2.20Node B:192.168.1.20当 Pod A 访问 Pod B 时,CNI 必须提供一条可达路径。
不同网络方案的实现方式不同:
- 路由型网络:节点通过路由表知道目标 Pod 网段应该经哪个节点到达。
- VXLAN Overlay:源节点将内部 Pod 数据包封装在外层节点 IP 数据包中,通过底层网络传送。
- eBPF 数据面:可以通过 eBPF 程序、映射表和相关路由机制处理转发。
因此,Kubernetes 跨节点通信并不是简单地把 Pod IP 放进数据包就结束了。底层还必须存在正确的转发路径和返回路径。
15. 知识检查清单
- Network Namespace:隔离网络接口、路由表和网络协议栈等网络资源。
- veth pair:提供一对互相连接的虚拟网络接口。
- Linux Bridge:Linux 内核提供的二层虚拟交换机。
- ARP:在 IPv4 网络中解析同一链路上的下一跳 MAC 地址,不一定是最终目的主机的 MAC。
- 邻居表:缓存 IP 与链路层地址映射。
- FDB:Bridge 根据 MAC 地址进行二层转发时使用的端口映射表。
- 路由表:决定下一跳和发送接口,通常采用最长前缀匹配;没有更具体路由时使用默认网关。
- 路由转发需要考虑返回路径、防火墙和内核转发设置。