深入理解 macvlan:从父子接口到内核实现
深入理解 macvlan:从父子接口到内核实现
在容器网络和 Kubernetes Underlay 场景中,macvlan 是被广泛使用的高性能网络方案。但很多人对它的理解停留在"配置能跑通"的层面,对它最核心的设计——父接口与子接口的关系——缺乏深入认识。
理解这个父子关系,是理解 macvlan 一切行为(包括它的性能优势、通信限制、以及为什么需要 Spiderpool 这类方案来补齐能力)的钥匙。
macvlan 的本质是:在一个真实/下层网络接口上,虚拟出多个拥有独立 MAC 的接口。 所以必须有一个"被依附"的接口来提供物理链路,这个接口就是父接口;虚拟出来的那些接口就是子接口。
1 | 物理网卡 eth0(父接口) |
一、核心概念:父接口与子接口
1.1 父接口的作用:提供二层出口
父接口通常是宿主机上的物理网卡,也可以是 bond、bridge、VLAN 接口等。它负责真正把帧发到物理链路,或者从物理链路收帧。子接口自己并没有物理收发能力,它必须依赖父接口。
- 发送时:子接口要发帧,会把帧交给父接口,由父接口从物理网卡发出去;
- 接收时:父接口收到帧,根据目的 MAC 判断这个帧属于哪个子接口,然后交给对应的子接口;
- 子接口:拥有独立的 MAC 地址和 IP 地址,可以放到不同的网络命名空间里,比如每个 Pod 的 netns。
所以父子关系不是"对等连接",而是承载与依赖关系。父接口是物理通道,子接口是虚拟端口。
1.2 为什么不是 veth 那种对等结构?
veth 是成对的:
1 | Pod netns(veth0) <----> 宿主机 netns(veth1) |
两端是对等的,一端进 Pod,一端留在宿主机,流量必须经过宿主机 IP 栈。
macvlan 不是这样。它是一个父接口下挂多个子接口:
1 | 父接口 eth0(宿主机 netns) |
一个父接口可以对应很多子接口,所以是"父子"或"上下层"关系,而不是"对端"关系。这两种结构带来的本质差异:
| 维度 | veth | macvlan |
|---|---|---|
| 拓扑 | 点对点 | 一对多 |
| 数据路径 | 经过宿主机 IP 栈 | 绕过宿主机 IP 栈,纯二层 |
| 性能 | 有内核协议栈开销 | 接近线速 |
| MAC 地址 | 一对 veth 各有 MAC | 每个子接口独立 MAC,对物理网络可见 |
1.3 内核为什么需要父子关系?
因为内核必须知道三个问题的答案:
这个子接口从哪个物理设备收发帧? 答案就是父接口。没有父接口,子接口就没有物理链路可走。
父接口收到帧后,该交给哪个子接口? macvlan 驱动会在父接口上注册一个接收处理钩子(rx_handler)。父接口收到帧后,根据目的 MAC 去查找对应的子接口,然后转发过去。
多个子接口如何共享同一个物理网卡? 每个子接口有独立 MAC,父接口像交换机背板一样,根据 MAC 把帧分发给正确的子接口。
所以父子关系是 macvlan 实现"多个虚拟接口共享一个物理接口"的关键机制。
1.4 类比理解
可以把父接口想成一块物理交换机上行口,子接口想成插在它上面的多个虚拟端口:
1 | 物理交换机 / 外部网络 |
每个子接口都有自己的 MAC,外部看起来就像多台独立主机接在同一交换机上。对外部网络(交换机、路由器、防火墙)而言,这些 Pod 就是真实的物理主机,可以被直接路由和管控——这正是 Underlay 网络的价值所在。
1.5 父子关系可以跨 netns
典型用法是:
1 | ip link add link eth0 name macvlan0 type macvlan mode bridge |
- eth0 是父接口,留在宿主机 netns;
- macvlan0 是子接口,被移动到 Pod netns;
- 父子关系仍然存在,内核知道 macvlan0 的 lower device 是 eth0。
这样 Pod 里的 macvlan0 就能直接通过宿主机的 eth0 与外部二层网络通信,而不进入宿主机的 IP 栈。这是 macvlan 性能优异的根本原因:数据包从 Pod netns 的子接口直达物理网卡,中间没有 netfilter、没有 iptables、没有 IP 转发。
二、深入内核:父子关系的实现
前面讲的是概念模型。那么,这个父子关系由谁维护?答案是:完全由 Linux 内核维护,具体来说是内核里的两块:
- 网络设备核心:
net/core/dev.c等; - macvlan 驱动:
drivers/net/macvlan.c。
用户空间的 ip link、CNI 插件、kubelet 都只是"发起创建请求",关系建立之后,运行时的父子绑定和数据转发都归内核管。
2.1 内核里的数据结构
每个网络接口在内核里都是一个 struct net_device。macvlan 子接口会多一层私有数据:
1 | struct macvlan_dev { |
父接口上会挂一个 struct macvlan_port,里面有一张哈希表,用来根据 MAC 地址找到对应的子接口:
1 | struct macvlan_port { |
同时,netdev core 还维护一套通用的 upper/lower 设备层次,用 netdev_adjacent 结构记录"谁挂在谁下面"。你在 ip link 里看到的 macvlan0@eth0,以及 sysfs 里的 lower_eth0 符号链接,就是这套关系的外在表现。
2.2 创建时:内核怎么建立父子关系
当你执行:
1 | ip link add link eth0 name macvlan0 type macvlan mode bridge |
流程大致是:
ip通过 netlink 发一条RTM_NEWLINK消息,里面用IFLA_LINK指定父接口 eth0 的索引;- 内核 netdev core 收到后,根据类型名
macvlan找到注册好的macvlan_link_ops; - 调用
macvlan_newlink(),最终进入macvlan_common_newlink()。
在 macvlan_common_newlink() 里:
- 分配子接口的 net_device;
- 设置
vlan->lowerdev = lowerdev,也就是把父接口指针存下来; - 调用
netdev_upper_dev_link(lowerdev, dev),在 netdev core 里正式建立"父—子"层次; - 调用
netdev_rx_handler_register(lowerdev, macvlan_handle_frame, port),在父接口上注册接收处理函数。
这一步完成后,父子关系就由内核记住了。之后即使把子接口移到 Pod 的 netns,这个关系仍然保留在内核全局的设备层次里——netns 只是命名空间的隔离,不会切断内核设备模型中的 lower/upper 绑定。
2.3 运行时:内核怎么用这个父子关系转发
发送方向。 Pod 里的 macvlan0 要发包时,调用的是 macvlan 驱动的 macvlan_start_xmit():
1 | skb->dev = vlan->lowerdev; // 把发送设备改成父接口 eth0 |
接收方向。 物理网卡 eth0 收到帧后,netdev core 会检查它有没有注册 rx_handler。因为创建时注册了 macvlan_handle_frame,所以帧会进入 macvlan 驱动:
1 | macvlan_handle_frame() |
macvlan_handle_frame 会先处理多播包,再处理单播包,查哈希表匹配目的 MAC,命中则重定向到对应子接口继续上送协议栈。此外它还会跳过 loopback 包,这与 bridge 代码中 br_handle_frame() 的检查逻辑类似。
整个过程都在内核里完成,不经过宿主机 IP 栈,也不经过 iptables/IPVS。
2.4 谁不负责维护这个关系?
排除法能帮我们更清晰地定位职责边界:
- 不是 systemd/NetworkManager:它们只管宿主机自己的网络配置;
- 不是 iptables/IPVS:它们工作在 IP 层,而 macvlan 父子关系是二层设备层次;
- 不是 CNI 插件:CNI 插件只是调用 netlink 创建接口、移动 netns,创建完就退场了;
- 不是 kubelet:kubelet 只负责调用 CNI。
真正一直维护父子关系、哈希表、rx_handler、发送重定向的,是内核 netdev core + macvlan 驱动。
三、四种工作模式:父子关系的不同"玩法"
macvlan 支持四种模式,它们决定了同一父接口下的多个子接口之间如何互访,以及与外部网络的交互行为。
3.1 bridge 模式(最常用)
同一父接口下的子接口之间可以直接在内核里交换帧,不需要把帧发到外部交换机再绕回来。这就是为什么 Kubernetes/CNI 场景下默认都用 bridge 模式——它让同宿主机上的 Pod 互通不依赖外部交换机的 hairpin 能力。
1 | Pod A (macvlan0) ←→ macvlan 驱动内部转发 ←→ Pod B (macvlan1) |
3.2 vepa 模式
子接口之间的通信必须经过外部交换机(要求交换机支持 hairpin/反射继播)。帧从父接口发出,经物理交换机再回到父接口,再分发给目标子接口。
3.3 private 模式
同一父接口下的子接口之间完全隔离,即使外部交换机支持 hairpin 也无法通信。适用于强隔离场景。
3.4 passthru 模式
一个父接口只能绑定一个子接口,子接口独占父接口,常用于 SR-IOV 类似的直通场景。
各模式对比:
| 模式 | 子接口互通 | 外部依赖 | 典型场景 |
|---|---|---|---|
| bridge | 内核内直接互通 | 无 | 容器/K8s 默认选择 |
| vepa | 经外部交换机 | 交换机支持 hairpin | 需要在交换机上做流量管控 |
| private | 互相隔离 | 无 | 安全隔离 |
| passthru | 只有一个子接口 | 无 | 直通独占 |
四、一个绕不开的限制:父接口与子接口不能通信
理解了父子关系的实现后,一个著名的限制就变得"理所当然"了:
宿主机自己无法直接访问同网卡上的 macvlan 子接口,反之亦然。
原因在内核转发路径里:
- 父接口 eth0 发出的帧,目的 MAC 是某个子接口的 MAC。但帧走的是 eth0 的发送队列,不会经过 eth0 上注册的 rx_handler(rx_handler 只对"从物理链路收到的帧"生效);
- 因此这个帧会被直接从物理网卡发出去,而不是交给子接口;
- 反过来,子接口发给宿主机(父接口 MAC)的帧,交给父接口发送后同样直接出网卡,宿主机的 IP 栈收不到。
这是 macvlan 的内核级限制,不是配置问题,无法通过调参解决。常见的 workaround 是在宿主机上额外创建一个 macvlan 子接口(比如 macvlan-host),配置宿主机路由让去往 Pod 子网的流量走这个接口——本质上是用"子接口对子接口"绕开"父对子"的限制。
这也正是 Spiderpool 的 coordinator 插件存在的重要理由之一:它自动为 Pod 创建一对 veth 设备并调谐路由,系统性解决"Pod 无法访问宿主机、本地健康检查失败、无法访问 ClusterIP"等连通性问题。
五、如何查看内核维护的这层关系
1 | ip link show |
这些信息都来自内核的 net_device 和 upper/lower 邻接列表。
另外值得注意的实践细节:macvlan 子接口不能再被 bridge 到第二个接口,且理论上单个物理适配器上可挂载的子接口数量存在上限(Docker 场景下常见 4094 个的讨论)。如果 OS 上运行了 NetworkManager,建议通过 /etc/NetworkManager/conf.d/ 配置将相关接口标记为 unmanaged,避免其干扰 CNI 创建的虚拟接口。
六、回到 Kubernetes:macvlan 在 CNI 中的位置
结合前文对 Spiderpool 的讨论,现在可以更完整地描绘 macvlan 在 Kubernetes Underlay 架构中的角色:
1 | Pod netns |
- CNI 插件(如 multus + macvlan binary):在 Pod 启动时执行
ip link add link <parent> ... type macvlan mode bridge,把子接口移入 Pod netns,配置 IP(IP 来自 Spiderpool IPAM 分配),然后退出; - 内核:从此接管一切——维护父子绑定、哈希表、rx_handler 分发、发送重定向;
- Spiderpool coordinator:作为 chain plugin 补齐内核不管的部分——多网卡策略路由、宿主机连通性(veth workaround)、Service 访问等。
三方分工清晰:CNI 负责"建",内核负责"跑",coordinator 负责"补"。
小结
macvlan 的父子关系由 Linux 内核的 netdev 核心和 macvlan 驱动共同维护:
- netdev core 维护通用的 upper/lower 设备层次;
- macvlan 驱动维护 lowerdev 指针、父接口上的哈希表和 rx_handler;
- 发送时子接口把包转给父接口;
- 接收时父接口根据 MAC 把包分给对应子接口;
- 子接口间互通与否取决于模式(bridge 模式内核内直通);
- 父接口与子接口间因 rx_handler 只作用于接收路径,存在无法直接通信的内核级限制。
macvlan 有父子之分,是因为它必须依附于一个真实接口来承载二层流量。父接口提供物理出口和 MAC 分发,子接口提供独立的网络身份。一个父接口可以挂多个子接口,所以是"一对多"的父子关系,而不是 veth 那种"点对点"的对等关系。这个父子关系跨 netns 有效,让 Pod 能以独立主机的姿态直连物理网络——这是 macvlan 高性能与 Underlay 价值的根源,也是它所有通信限制(父对子不通、需交换机感知 MAC 等)的根源。
理解了父子关系,macvlan 就不再是一个"配置能跑就行"的黑盒:你能预判它的行为边界,能解释踩过的坑,也能明白 Spiderpool 这类方案在网络栈的哪个位置、为解决什么问题而存在。
延伸资源
- 内核源码:drivers/net/macvlan.c
- 相关阅读:《Spiderpool 多网卡 IPAM 深度解析:Underlay 网络缺的那块拼图》——coordinator 如何系统性解决 macvlan 的父对子不通问题
- 相关阅读:《Macvlan 在大模型训练中的角色:为 Pod 提供共享 RDMA 通道》——macvlan 在 AI 训练场景的特定用法
- 相关阅读:《IPoIB 与 pkey 子接口:InfiniBand 世界里的"IP 网卡 + VLAN"》——InfiniBand 侧的"对应物":结构对称但机制不同,pkey 靠硬件分发而 macvlan 靠软件分发