Docker 使用 macvlan 网络容器与宿主机的通信过程
一. 摘要
因为 macvlan 的限制,使用 manvlan 虚拟网口的容器不能与宿主机直接互通(准确的说是不能与父网卡互通)。
为了让容器与宿主机互通,可以通过在宿主机上添加一个额外的 macvlan 虚拟网口来实现。
这里涉及到的一些知识:
- 同子网下多网卡 arp 应答机制(可以参考我的这篇文章来了解 ARP Flux)
- 同主机网卡之间的数据报转发(控制参数
ip_forward
) - 网络协议是分层的。源地址 ip 和源 mac 可以不在同一张网卡上;同理目的地址 ip 和目的 mac 也可以不在同一张网卡上
- 路由控制(tips: 没有配置 ip 的网卡依然可以转发数据包)
二. 快速搭建测试环境
基础信息:
网口 ip MAC ens33(宿主机) 192.168.136.130 00:0c:29:80:61:8b eth0(容器) 192.168.136.240 02:42:c0:a8:88:f0 macvlan0-host(宿主机) 无 9e:63:b4:5c:c8:93 macvlan0-host 网口是为了打通宿主机与容器网络额外添加的
开启网卡混杂模式
1
ip link set ens33 promisc on
使用
docker network create
创建 docker macvlan 网络1
docker network create -d macvlan --subnet=192.168.136.0/24 --gateway=192.168.136.2 --ip-range=192.168.136.240/28 -o parent=ens33 macvlan0
1
2
3
4
5
6
7[root@centos7-01 ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
93e753322579 awxcompose_default bridge local
1597fe9b77cc bridge bridge local
18efa0daec31 host host local
da129fb7ee57 macvlan0 macvlan local
679be2fe09c2 none null local启动使用 macvlan 网络的容器,并指定容器 ip 为
192.168.136.240
1
docker run -d -it --network=macvlan0 --ip=192.168.136.240 centos:8 /bin/bash
三. 第一阶段测试
- 在宿主机 ping 容器 ip
192.168.136.240
;发现 ping 不通1
2
3[root@centos7-01 ~]# ping 192.168.136.240
PING 192.168.136.240 (192.168.136.240) 56(84) bytes of data.
From 192.168.136.130 icmp_seq=1 Destination Host Unreachable - 在容器里 ping 宿主机 ip
192.168.136.130
;同样也 ping 不通1
2
3
4
5[root@da15dc23e3d2 /]# ping 192.168.136.130
PING 192.168.136.130 (192.168.136.130) 56(84) bytes of data.
^C
--- 192.168.136.130 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 3ms - 在容器里 arping 宿主机 ip
192.168.136.130
;也没有响应1
2
3
4[root@da15dc23e3d2 /]# arping 192.168.136.130
ARPING 192.168.136.130 from 192.168.136.240 eth0
^CSent 3 probes (3 broadcast(s))
Received 0 response(s)
四. 打通容器与宿主机之间的网络
- 通过 macvlan 添加一块虚拟网口
macvlan0-host
来实现容器与宿主机互通1
2ip link add link ens33 macvlan0-host type macvlan mode bridge
ip link set dev macvlan0-host up - 检查
macvlan0-host
网口,确保是UP
状态1
2
3[root@centos7-01 ~]# ip link show | grep macvlan0-host
7: macvlan0-host@ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 9e:63:b4:5c:c8:93 brd ff:ff:ff:ff:ff:ff - 添加宿主机到容器的专有路由(在宿主机上执行)
1
ip route add 192.168.136.140 dev macvlan0-host
tips: 容器与宿主机在同一网段的情况下,macvlan0-host 不需要配置 ip
五. 再次测试验证
从宿主机 ping 容器
192.168.136.240
并抓包观察
①. 先分析宿主机发出去的包,可以看到源 ip 是192.168.136.130
(ens33 网口上的),源 mac 是9e:63:b4:5c:c8:93
(macvlan0-host 网口上的)。这是因为我们上面加的那条路由指定了发给192.168.136.140
的数据包要从macvlan0-host
出;同时网络协议是分层的,源地址 ip 和源 mac 可以不在同一张网卡上。
②. 再分析宿主机收到的包,可以看到目的 mac 地址是macvlan0-host
网口上的,目的 ip 是192.168.136.130
。然后数据包再由macvlan0-host
转交给ens33
。1
2
3[root@centos7-01 ~]# ping 192.168.136.240
PING 192.168.136.240 (192.168.136.240) 56(84) bytes of data.
64 bytes from 192.168.136.240: icmp_seq=1 ttl=64 time=0.160 ms1
2
3
4
5
6[root@centos7-01 ~]# tcpdump -nn -e -i ens33 icmp -vvv
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
21:03:47.362614 9e:63:b4:5c:c8:93 > 02:42:c0:a8:88:f0, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 7281, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.136.130 > 192.168.136.240: ICMP echo request, id 2135, seq 1, length 64
21:03:47.362637 02:42:c0:a8:88:f0 > 9e:63:b4:5c:c8:93, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 17007, offset 0, flags [none], proto ICMP (1), length 84)
192.168.136.240 > 192.168.136.130: ICMP echo reply, id 2135, seq 1, length 641
2
3
4
5
6[root@centos7-01 ~]# tcpdump -nn -e -i macvlan0-host icmp -vvv
tcpdump: listening on macvlan0-host, link-type EN10MB (Ethernet), capture size 262144 bytes
21:03:47.362590 9e:63:b4:5c:c8:93 > 02:42:c0:a8:88:f0, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 7281, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.136.130 > 192.168.136.240: ICMP echo request, id 2135, seq 1, length 64
21:03:47.362637 02:42:c0:a8:88:f0 > 9e:63:b4:5c:c8:93, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 17007, offset 0, flags [none], proto ICMP (1), length 84)
192.168.136.240 > 192.168.136.130: ICMP echo reply, id 2135, seq 1, length 64从容器抓包也可以观察到回包是先发给了
macvlan0-host
网口1
2
3
4
5
6
7[root@fd6aa698a558 /]# tcpdump -nn -e -i eth0 icmp -vvv
dropped privs to tcpdump
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
13:03:47.362614 9e:63:b4:5c:c8:93 > 02:42:c0:a8:88:f0, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 7281, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.136.130 > 192.168.136.240: ICMP echo request, id 2135, seq 1, length 64
13:03:47.362636 02:42:c0:a8:88:f0 > 9e:63:b4:5c:c8:93, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 17007, offset 0, flags [none], proto ICMP (1), length 84)
192.168.136.240 > 192.168.136.130: ICMP echo reply, id 2135, seq 1, length 64从容器 arping 宿主机 ens33 网口 ip
192.168.136.130
可以观察到应答的 mac 地址是 macvlan0-host 网口上的, 原因在这里( ARP Flux)1
2
3[root@fd6aa698a558 /]# arping 192.168.136.130
ARPING 192.168.136.130 from 192.168.136.240 eth0
Unicast reply from 192.168.136.130 [9E:63:B4:5C:C8:93] 0.600ms
六. 总结
mavlan 虚拟出来的网口不能和父网口直通,但是可以和同一子网的其他网口互通(包括其他虚拟出来的网口)。
通过在宿主机上用 macvlan 添加一个虚拟网口 macvlan0-host
,并在宿主机上配置路由『到容器的数据包由 macvlan0-host
发出』,就架起了宿主机与容器的通信桥梁。
宿主机发往容器的数据包,由 macvlan0-host
发给容器;容器发往宿主机的数据包先发给 macvlan0-host
,再由 macvlan0-host
转交给 ens33
。
tips: 如果服务器上有额外的和容器同网段的网口,可以直接添加路由来实现容器与宿主机互通。
参考文档:
[Bridge] [PATCH 0/3] macvlan: add vepa and bridge mode
Linux 虚拟网卡技术:Macvlan
Docker 网络模型之 macvlan 详解,图解,实验完整