本文会在多个节点上实验容器 macvlan 的网络模型。
我们按照下图创建网络拓扑,让 Node-1 和 Node-2 的之间的容器网络互通。

macvlan 是 Linux kernel 提供的网络虚拟化解决方案,准确说这是一种网卡虚拟化的解决方案。因为 macvlan 这种技术能将一块物理网卡虚拟成多块虚拟网卡,相当于物理网卡施展了多重影分身之术,由一个变多个。macvlan 子接口和原来的主接口是完全独立的,可以单独配置 MAC 地址和 IP 地址,并且与主接口共享同一个广播域。
主机网卡使用 macvlan 网络虚拟化后,主机网卡称为主接口,虚拟网卡称为子接口。
主接口会根据收到包的目的 MAC 地址判断这个包需要交给哪个虚拟网卡(macvlan 子接口),虚拟网卡再把包交给上层协议栈处理。下图展示了主接口(物理网卡)和子接口(虚拟网卡)之间的关系:

掌握 macvlan + 容器跨主机网络通信工作原理,提高 K8s + macvlan 部署模式下,网络排障的基本能力。
注意,请在虚拟机内折腾,以免干扰工作环境。
| Node | OS | 用户 | 主机网卡 | 主机 IP |
|---|---|---|---|---|
| Node-1 | Ubuntu 22.04 | root | ens33 | 192.168.245.168 |
| Node-2 | Ubuntu 22.04 | root | ens33 | 192.168.245.172 |
| 物理机 | MAC OS | hjm | bridge102 | 192.168.245.1/24 |
VMware 会在宿主机上创建一个网桥,使其宿主机能与 WMware 虚拟机通信。
进入 Node-1,给 ens33 网卡创建 macvlan 子接口:
ip link add link ens33 dev macvlan1 type macvlan mode bridge
进入 Node-2,给 ens33 网卡创建 macvlan 子接口:
ip link add link ens33 dev macvlan1 type macvlan mode bridge
进入 Node-1,创建 docker1 容器:
ip netns add docker1
进入 Node-2,创建 docker2 容器:
ip netns add docker2
将两个子接口分别挂到 docker1 和 docker2 容器(Network Namespace)中:
# 在 Node-1 上执行
ip link set macvlan1 netns docker1
# 在 Node-2 上执行
ip link set macvlan1 netns docker2
分别进入 docker1 和 docker2 容器,分别配置 IP 地址:
# 在 Node-1 上执行
ip netns exec docker1 ip addr add 192.168.245.180/24 dev macvlan1
ip netns exec docker1 ip link set macvlan1 up
# 在 Node-2 上执行
ip netns exec docker2 ip addr add 192.168.245.181/24 dev macvlan1
ip netns exec docker2 ip link set macvlan1 up
注意,Node-1 ens33 的 IP 是 192.168.245.168/24,配置的子接口 IP 也必须是同一网段的。Node-2 ens33 的 IP 是 192.168.245.172/24,配置的子接口 IP 也必须是同一网段的。
分别查看 docker1 和 docker2 容器中的子接口:
# 在 Node-1 上执行
4: macvlan1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 86:3c:59:4b:a7:ec brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.245.180/24 scope global macvlan1
valid_lft forever preferred_lft forever
inet6 fe80::843c:59ff:fe4b:a7ec/64 scope link
valid_lft forever preferred_lft forever
# 在 Node-2 上执行
4: macvlan1@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ea:0e:ad:2a:f7:14 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.245.181/24 scope global macvlan1
valid_lft forever preferred_lft forever
inet6 fe80::e80e:adff:fe2a:f714/64 scope link
valid_lft forever preferred_lft forever
测试网络连通性:
进入 Node-1 的 docker1 容器,然后 ping Node-2 的 docker2 容器:
root@ubuntu:~# ping 192.168.245.181
PING 192.168.245.181 (192.168.245.181) 56(84) bytes of data.
64 bytes from 192.168.245.181: icmp_seq=1 ttl=64 time=0.683 ms
进入 Node-2 的 docker2 容器,然后 ping Node-1 的 docker1 容器:
root@ubuntu:~# ping 192.168.245.180
PING 192.168.245.180 (192.168.245.180) 56(84) bytes of data.
64 bytes from 192.168.245.180: icmp_seq=1 ttl=64 time=0.966 ms
进入 MAC OS 物理机,分别 ping docker1 和 docker2 容器:
➜ ~ ping 192.168.245.180
PING 192.168.245.180 (192.168.245.180): 56 data bytes
64 bytes from 192.168.245.180: icmp_seq=0 ttl=64 time=1.027 ms
➜ ~ ping 192.168.245.181
PING 192.168.245.181 (192.168.245.181): 56 data bytes
64 bytes from 192.168.245.181: icmp_seq=0 ttl=64 time=0.958 ms
发现,docker1、docker2 和 MAC OS 物理机的网络是互通的。这正是 macvlan 的特性。
然后使用 tcpdump 分别抓 Node-2 ens33、docker2 macvlan1 的网卡:
# tcpdump: Node-2 ens33 网卡
root@ubuntu:~# tcpdump -i ens33 -n | grep 192.168.245.181
08:32:11.939133 ARP, Request who-has 192.168.245.181 tell 192.168.245.168, length 46
08:32:11.939165 ARP, Reply 192.168.245.181 is-at ea:0e:ad:2a:f7:14, length 28
08:32:12.078591 ARP, Request who-has 192.168.245.168 tell 192.168.245.181, length 28
08:32:12.866861 IP 192.168.245.168 > 192.168.245.181: ICMP echo request, id 9, seq 7, length 64
08:32:12.866903 IP 192.168.245.181 > 192.168.245.168: ICMP echo reply, id 9, seq 7, length 64
# tcpdump: docker2 macvlan1 网卡
tcpdump -i macvlan1 -n | grep 192.168.245.181
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on macvlan1, link-type EN10MB (Ethernet), snapshot length 262144 bytes
08:44:47.587940 IP 192.168.245.168 > 192.168.245.181: ICMP echo request, id 16, seq 1, length 64
08:44:47.587967 IP 192.168.245.181 > 192.168.245.168: ICMP echo reply, id 16, seq 1, length 64
MAC OS 物理机(192.168.245.1/24)、docker1(192.168.245.180/24)和 docker2(192.168.245.181/24)看起来就在同一个局域网内,它们之间可以直接通信。这里证实了 Linux macvlan 将一块物理网卡虚拟成多块虚拟网卡,相当于物理网卡施展了多重影分身之术,由一个变多个。这里没有任何问题。
当在 Node-1 的 docker1 内 ping Node-2 的 docker2 时,发现 Node-2 的 ens33 网卡收到了来自 Node-1 ens33 网卡的 ARP 包:
08:32:11.939133 ARP, Request who-has 192.168.245.181 tell 192.168.245.168, length 46
08:32:11.939165 ARP, Reply 192.168.245.181 is-at ea:0e:ad:2a:f7:14, length 28
这个 ARP 包的意思是:Node-1(192.168.245.168)广播了一个 ARP 请求,询问谁拥有 docker2(192.168.245.181)的 MAC 地址;docker2(192.168.245.181)的设备回复了一个单播的 ARP 响应,告知 Node-1(192.168.245.168)其 MAC 地址。这个 MAC 地址 ea:0e:ad:2a:f7:14 就是 docker2 macvlan1 的 MAC 地址。这里证实了 macvlan 子接口与主接口共享同一个广播域。
通过学习了 macvlan 概念后,亲自动手模拟出了 macvlan + 容器跨主机网络通信,并分别测试了容器间的网络互通、从宿主机访问容器内网络等场景的网络互通。并且发现 macvlan 实现跨主机网络通信非常简单,进出容器的 IP 包只需要经过宿主机的网卡设备就可以了,不需要 Bridge、VXLAN、iptables 等设备和规则,完全依赖于 Linux 内核的实现。缺点就是容器会占用宿主机网卡网段的 IP(某些场景下,这可能不是缺点)。
参考资料: