几种通过 iproute2 来打通不同节点间容器网络的方式
几种通过 iproute2 来打通不同节点间容器网络的方式
- host-gw
- ipip
- vxlan
背景
之前由于需要打通不同节点间的容器网络,对 flannel 进行修改增加了网络相关信息的获取逻辑,进而可以完整使用 backend 的功能。
最近想拿掉这个 flannel,所以分析一下 backend 的功能,使用 ip 命令来进行模拟
flannel 的 backend 提供了 7 种类型,这里只对以下 3 种内核提供的功能作模拟
- host-gw
- ipip
- vxlan
环境
容器使用网络命名空间隔离网络相关资源,为了操作简单直接使用网络命名空间作为测试环境。
vm1(192.168.32.245) 和 vm2(192.168.32.246) 是物理机上的两个虚拟机,操作系统为 CentOS 7.2。
目标为将 172.245.0.0/24 和 172.246.0.0/24 网段打通。
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ vm1 │ │ vm2 │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ ns245 │ │ │ │ ns246 │ │
│ │ 172.245.0.2 │ │ │ │ 172.246.0.2 │ │
│ │ eth0 │ │ │ │ eth0 │ │
│ └────────|────────┘ │ │ └────────|────────┘ │
│ │ │ │ │ │
│ veth245 │ │ veth246 │
│ │ │ │ │ │
│ br245 │ │ br246 │
│ 172.245.0.1/24 │ │ 172.246.0.1/24 │
│ │ │ │
│ │ │ │
│ 192.168.32.245/24 │ │ 192.168.32.246/24 │
│ eth0 │ │ eth0 │
└──────────────│──────────────┘ └──────────────│──────────────┘
│ │
└────────────────────────────────────────┘
环境配置
需要打开 ip_forward 选项,让流量能够通过网桥进入命名空间
sysctl -w net.ipv4.ip_forward=1
设置网桥
# 创建网桥
ip link add br245 type bridge
# 启用网桥
ip link set br245 up
创建命名空间、设置虚拟网卡
# 创建网络命名空间
ip netns add ns245
# 创建 veth-peer,并且一端设置在 netns 中
ip link add veth245 type veth peer name eth0 netns ns245
# 启用 netns 中的 veth-peer
ip netns exec ns245 ip link set eth0 up
# 启用 host 中的 veth-peer
ip link set veth245 up
# 将 host 中的 veth-peer 挂载到 br245 网桥上
ip link set veth245 master br245
设置网卡地址和路由
# 设置网桥地址
ip addr add 172.245.0.1/24 dev br245
# 设置命名空间内的 veth-peer 地址
ip netns exec ns245 ip addr add 172.245.0.2/24 dev eth0
# 设置命名空间内的默认路由(新建容器也会存在一条这个默认路由)
ip netns exec ns245 ip route add default via 172.245.0.1 dev eth0
vm2 的设置只需将 245 替换为 246 即可
设置完成后,vm1 的网卡、地址和路由信息如下(略去无关网卡)
$ ip addr
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:ce:51:a5 brd ff:ff:ff:ff:ff:ff
inet 192.168.32.245/24 brd 192.168.32.255 scope global noprefixroute eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fece:51a5/64 scope link
valid_lft forever preferred_lft forever
9: br245: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 02:8d:b1:91:fa:7a brd ff:ff:ff:ff:ff:ff
inet6 fe80::7426:b5ff:fea9:d35/64 scope link
valid_lft forever preferred_lft forever
10: veth245@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br245 state UP group default qlen 1000
link/ether 02:8d:b1:91:fa:7a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::8d:b1ff:fe91:fa7a/64 scope link
valid_lft forever preferred_lft forever
$ ip route
default via 192.168.32.1 dev eth0 proto static metric 100
172.245.0.0/24 dev br245 proto kernel scope link src 172.245.0.1
192.168.32.0/24 dev eth0 proto kernel scope link src 192.168.32.245 metric 100
$ ip netns exec ns245 ip addr
2: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether be:9c:d4:57:58:5a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.245.0.2/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::bc9c:d4ff:fe57:585a/64 scope link
valid_lft forever preferred_lft forever
$ ip netns exec ns245 ip route
default via 172.245.0.1 dev eth0
172.245.0.0/24 dev eth0 proto kernel scope link src 172.245.0.2
host-gw
只需要在 vm 中设置一条指向对方的路由即可
# vm1
ip route add 172.246.0.0/24 via 192.168.32.246 dev eth0 onlink
# vm2
ip route add 172.245.0.0/24 via 192.168.32.245 dev eth0 onlink
在命名空间内测试 172.246.0.2 是否能通
$ ip netns exec ns245 ping -c 1 172.246.0.2
PING 172.246.0.2 (172.246.0.2) 56(84) bytes of data.
64 bytes from 172.246.0.2: icmp_seq=1 ttl=62 time=0.656 ms
--- 172.246.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.656/0.656/0.656/0.000 ms
从 172.245.0.2 访问 172.246.0.2 的时序图如下(回包方向逻辑一致)
172.245.0.2 ns245:eth0 br245 vm1:eth0 vm2:eth0 br246 ns246:eth0 172.246.0.2
| | | | | | | |
| default | | | | | | |
| routing | | | | | | |
| --------> | | | | | | |
| | veth-peer | | | | | |
| | master | | | | | |
| | --------> | | | | | |
| | | 172.246.0.2 | | | | |
| | | routing | | | | |
| | | ----------> | | | | |
| | | | layer 2 | | | |
| | | | --------> | | | |
| | | | | 172.246.0.2 | | |
| | | | | routing | | |
| | | | | ----------> | | |
| | | | | | master | |
| | | | | | veth-peer | |
| | | | | | --------> | |
| | | | | | | 172.246.0.2 |
| | | | | | | ----------> |
| | | | | | | |
172.245.0.2 ns245:eth0 br245 vm1:eth0 vm2:eth0 br246 ns246:eth0 172.246.0.2
Q&A
Q: 为何 host-gw 模式下的容器所在宿主机必须在同一网段(二层互通)
A: 路由不会修改源IP,回包交换源目的 IP 后,网关查询路由表不能确定发包接口,二层互通不存在这个问题,不需要查询路由表,直接根据 MAC 地址选路
ipip
IPIP(IP-in-IP)协议是一种网络层隧道协议,用于在一个IP网络上传输另一个IP数据包。IPIP协议的主要目的是在网络之间提供透明的通信通道,使得内部网络的数据包可以通过外部网络传输,而不需要改变数据包的内容。
协议格式如下
// 原始 TCP 数据包
+--------------------------------+
| .... |
+--------------------------------+
| TCP |
+--------------------------------+
| IP |
+--------------------------------+
| Ethernet |
+--------------------------------+
// ipip 封装后的数据包
+----------------------------------------+
| .... |
+----------------------------------------+
| TCP |
+----------------------------------------+
| IP |
+----------------------------------------+
/ IP (tunnel) /
+----------------------------------------+
| Ethernet |
+----------------------------------------+
在 vm1 中设置 ipip 隧道
# 创建 IPIP 隧道接口
ip tunnel add tun245 mode ipip local 192.168.32.245 remote 192.168.32.246
# 配置 IPIP 隧道接口的 IP 地址
ip addr add 172.245.1.1/30 dev tun245
# 启用 IPIP 隧道接口
ip link set tun245 up
# 添加路由,使 172.246.0.0/24 网段的数据包通过 IPIP 隧道传输
ip route add 172.246.0.0/24 via 172.245.1.2 dev tun245
在 vm2 中访问 172.245.0.2 (增加访问 192.168.32.245 进行对比)
$ ping -c 1 192.168.32.245
$ ip netns exec ns246 ping -c 1 172.245.0.2
在 vm1 中对 eth0 进行抓包
$ tcpdump -s0 -i eth0 host 192.168.32.246 -nn
11:28:01.268176 IP 192.168.32.246 > 192.168.32.245: ICMP echo request, id 1730, seq 1, length 64
11:28:01.268299 IP 192.168.32.245 > 192.168.32.246: ICMP echo reply, id 1730, seq 1, length 64
11:28:08.697558 IP 192.168.32.246 > 192.168.32.245: IP 172.246.0.2 > 172.245.0.2: ICMP echo request, id 1708, seq 26, length 64 (ipip-proto-4)
11:28:08.697760 IP 192.168.32.245 > 192.168.32.246: IP 172.245.0.2 > 172.246.0.2: ICMP echo reply, id 1708, seq 26, length 64 (ipip-proto-4)
在 wireshark 更直观的看到 ipip 多了一层 ip,用来作为隧道通信。
Frame 3: 118 bytes on wire (944 bits), 118 bytes captured (944 bits)
Ethernet II, Src: 52:54:00:b9:5d:53 (52:54:00:b9:5d:53), Dst: 52:54:00:ce:51:a5 (52:54:00:ce:51:a5)
Internet Protocol Version 4, Src: 192.168.32.246, Dst: 192.168.32.245
Internet Protocol Version 4, Src: 172.246.0.2, Dst: 172.245.0.2
Internet Control Message Protocol
从 172.245.0.2 访问 172.246.0.2 的时序图如下(省略命名空间内的部分,回包方向逻辑一致)
br245 tun245 vm1:eth0 vm2:eth0 tun246 br246
| | | | | |
| 172.246.0.2 | | | | |
| routing | | | | |
| ----------> | | | | |
| | ipip pack | | | |
| | --------> | | | |
| | | layer 2 | | |
| | | --------> | | |
| | | | ipip unpack | |
| | | | ----------> | |
| | | | | 172.246.0.2 |
| | | | | routing |
| | | | | ----------> |
| | | | | |
br245 tun245 vm1:eth0 vm2:eth0 tun246 br246
Q&A
Q: 和 host-gw 最大的区别是什么
A: 对容器(网络命名空间)所在节点的网络不再限制二层互通,只要节点网络(二层、三层)互通即可。
vxlan
vxlan 旨在解决大型云数据中心和多租户环境中传统 vlan(虚拟局域网)技术的局限性。通过在 UDP 之上封装第二层以太网帧,实现在第三层(IP)网络上的二层网络扩展,从而允许创建多达 1600 万个隔离的虚拟网络,远超 vlan 的4096 个网络限制。
协议格式如下
// 原始 TCP 数据包
+----------------------------------------+
| .... |
+----------------------------------------+
| TCP |
+----------------------------------------+
| IP |
+----------------------------------------+
| Ethernet |
+----------------------------------------+
// vxlan 封装后的数据包
+----------------------------------------+
| .... |
+----------------------------------------+
| TCP |
+----------------------------------------+
| IP |
+----------------------------------------+
| Ethernet |
+----------------------------------------+
/ VXLAN Header (8 bytes) /
+----------------------------------------+
/ UDP (tunnel) /
+----------------------------------------+
/ IP (tunnel) /
+----------------------------------------+
/ Ethernet (tunnel) /
+----------------------------------------+
在 vm1 中设置
# 创建 VXLAN 隧道接口
ip link add vtep245 type vxlan id 1 local 192.168.32.245 dev eth0 dstport 8472 nolearning
# 配置 IP
ip addr add 172.245.0.0/32 dev vtep245
# 设置 MTU
ip link set vtep245 mtu 1450
# 启动 vtep
ip link set veth245 up
# 配置路由
ip route add 172.246.0.0/24 via 172.246.0.0 dev vtep245 onlink
vm2 中配置和 vm1 中相反,245 和 246 需要互换
查看虚拟网卡,看到设置的 vxlan id、网卡和本地 ip
root@vm1# ip -d link show vtep245
12: vtep245: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 52:3a:8c:c9:08:e8 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 192.168.32.245 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
root@vm2# ip -d link show vtep246
10: vtep246: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 02:38:4a:12:3b:43 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 local 192.168.32.246 dev eth0 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
查看监听 UDP 端口,看到内核监听了 8742
$ netstat -nulp
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:8472 0.0.0.0:* -
当前的网络拓扑图如下
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ vm1 │ │ vm2 │
│ ┌─────────────────┐ │ │ ┌─────────────────┐ │
│ │ ns245 │ │ │ │ ns246 │ │
│ │ 172.245.0.2 │ │ │ │ 172.246.0.2 │ │
│ │ eth0 │ │ │ │ eth0 │ │
│ └────────|────────┘ │ │ └────────|────────┘ │
│ │ │ │ │ │
│ veth245 │ │ veth246 │
│ │ │ │ │ │
│ br245 │ │ br246 │
│ 172.245.0.1/24 │ │ 172.246.0.1/24 │
│ │ │ │
│ vtep245 │ │ vtep246 │
│ vni:1 172.245.0.0 │ │ vni:1 172.246.0.0 │
│ │ │ │
│ 192.168.32.245/24 │ │ 192.168.32.246/24 │
│ eth0 │ │ eth0 │
└──────────────│──────────────┘ └──────────────│──────────────┘
│ │
└────────────────────────────────────────┘
在 vm1 上尝试通过 172.245.0.2 访问 172.246.0.2,无法访问,通过 tcpdump 诊断流量到达虚拟网卡 vtep245,但是不知道网关 172.246.0.0 的 MAC 地址
$ ip netns exec ns245 ping -c 1 172.246.0.2
$ tcpdump -s0 -i vtep245 -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
17:44:26.916277 ARP, Request who-has 172.246.0.0 tell 172.245.0.0, length 28
在上面已经知道了网关的 MAC 地址,直接进行手动设置(flannel 中有 etcd 进行集中管理)
# vm1
ip neigh add 172.246.0.0 dev vtep245 lladdr 02:38:4a:12:3b:43
# vm2
ip neigh add 172.245.0.0 dev vtep246 lladdr 52:3a:8c:c9:08:e8
解决 MAC 地址问题后,还有一个问题:vxlan 封的包如何发送出去,这里使用 fdb 来根据 MAC 指定 vxlan 包发送的地址
fdb 是一个用于存储 MAC 地址及其对应端口的信息表,以便桥接设备能够高效地转发数据帧。
# vm1
bridge fdb add 02:38:4a:12:3b:43 dev vtep245 dst 192.168.32.246
# vm2
bridge fdb add 52:3a:8c:c9:08:e8 dev vtep246 dst 192.168.32.245
从 172.245.0.2 访问 172.246.0.2 的时序图如下(省略命名空间内的部分,回包方向逻辑一致)
br245 vtep245 vm1:eth0 vm2:eth0 vtep246 br246
| | | | | |
| 172.246.0.2 | | | | |
| routing | | | | |
| ----------> | | | | |
| | vxlan pack | | | |
| | ---------> | | | |
| | | udp 8742 | | |
| | | --------> | | |
| | | | vxlan unpack | |
| | | | -----------> | |
| | | | | 172.246.0.2 |
| | | | | routing |
| | | | | ----------> |
| | | | | |
br245 vtep245 vm1:eth0 vm2:eth0 vtep246 br246
Q&A
Q: 为何 vm1 上的路由要使用 172.246.0.0 作为网关地址
A:
- 网关不能使用本机的 IP 或者处于本机网段内的 IP,没有路由的情况下,流量转发不到外部;
- 172.246.0.0 不是一个可用的地址,用来标识整个网络,使用其他的地址会有冲突;
总结
从功能上看:
- host-gw 最简单,只需要一条路由就可以打通不同节点的容器网络,但只能在二层互通的场景下完成
- ipip 在 host-gw 的基础上建立了一条 IP 隧道,这样在三层互通的情况下也可以打通容器网络
- vxlan 在类似 ipip 的基础上,基于三层隧道实现了二层的覆盖网络,提供了极高的网络隔离能力,相对也最复杂
从性能上看:
- host-gw 最快,因为没有任何封包的消耗
- ipip 次之,多了二十字节的 IP 头的封装
- vxlan 相对最慢,多了一整个 以太网、IP 层和 UDP 层的封装
参考
- https://www.rfc-editor.org/rfc/inline-errata/rfc7348.html, Virtual eXtensible Local Area Network (VXLAN): A Framework for Overlaying Virtualized Layer 2 Networks over Layer 3 Networks