[解决方案] 几种通过 iproute2 来打通不同节点间容器网络的方式

几种通过 iproute2 来打通不同节点间容器网络的方式

几种通过 iproute2 来打通不同节点间容器网络的方式

背景

之前由于需要打通不同节点间的容器网络,对 flannel 进行修改增加了网络相关信息的获取逻辑,进而可以完整使用 backend 的功能。

最近想拿掉这个 flannel ,所以分析一下 backend 的功能,使用 ip 命令来进行模拟

flannelbackend 提供了 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 网段打通。

shell 复制代码
┌─────────────────────────────┐          ┌─────────────────────────────┐
│ 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 选项,让流量能够通过网桥进入命名空间

shell 复制代码
sysctl -w net.ipv4.ip_forward=1

设置网桥

shell 复制代码
# 创建网桥
ip link add br245 type bridge

# 启用网桥
ip link set br245 up

创建命名空间、设置虚拟网卡

shell 复制代码
# 创建网络命名空间
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

设置网卡地址和路由

shell 复制代码
# 设置网桥地址
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 的网卡、地址和路由信息如下(略去无关网卡)

shell 复制代码
$ 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 中设置一条指向对方的路由即可

shell 复制代码
# 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 是否能通

shell 复制代码
$ 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 的时序图如下(回包方向逻辑一致)

txt 复制代码
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协议的主要目的是在网络之间提供透明的通信通道,使得内部网络的数据包可以通过外部网络传输,而不需要改变数据包的内容。

协议格式如下

txt 复制代码
// 原始 TCP 数据包                
+--------------------------------+
|              ....              |
+--------------------------------+
|               TCP              |
+--------------------------------+
|               IP               |
+--------------------------------+
|            Ethernet            |
+--------------------------------+

// ipip 封装后的数据包
+----------------------------------------+
|                   ....                 |
+----------------------------------------+
|                   TCP                  |
+----------------------------------------+
|                   IP                   |
+----------------------------------------+
/               IP (tunnel)              /
+----------------------------------------+
|                Ethernet                |
+----------------------------------------+

在 vm1 中设置 ipip 隧道

shell 复制代码
# 创建 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 进行对比)

shell 复制代码
$ ping -c 1 192.168.32.245
$ ip netns exec ns246 ping -c 1 172.245.0.2

在 vm1 中对 eth0 进行抓包

shell 复制代码
$ 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,用来作为隧道通信。

txt 复制代码
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 的时序图如下(省略命名空间内的部分,回包方向逻辑一致)

txt 复制代码
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 个网络限制。

协议格式如下

txt 复制代码
// 原始 TCP 数据包
+----------------------------------------+
|                  ....                  |
+----------------------------------------+
|                   TCP                  |
+----------------------------------------+
|                   IP                   |
+----------------------------------------+
|                Ethernet                |
+----------------------------------------+


// vxlan 封装后的数据包
+----------------------------------------+
|                  ....                  |
+----------------------------------------+
|                   TCP                  |
+----------------------------------------+
|                   IP                   |
+----------------------------------------+
|                Ethernet                |
+----------------------------------------+
/          VXLAN Header (8 bytes)        /
+----------------------------------------+
/               UDP (tunnel)             /
+----------------------------------------+
/               IP (tunnel)              /
+----------------------------------------+
/              Ethernet (tunnel)         /
+----------------------------------------+

在 vm1 中设置

shell 复制代码
# 创建 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

shell 复制代码
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

shell 复制代码
$ 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:*                           -  

当前的网络拓扑图如下

txt 复制代码
┌─────────────────────────────┐          ┌─────────────────────────────┐
│ 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 地址

shell 复制代码
$ 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 进行集中管理)

shell 复制代码
# 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 地址及其对应端口的信息表,以便桥接设备能够高效地转发数据帧。

shell 复制代码
# 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 的时序图如下(省略命名空间内的部分,回包方向逻辑一致)

txt 复制代码
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 不是一个可用的地址,用来标识整个网络,使用其他的地址会有冲突;

总结

从功能上看:

  1. host-gw 最简单,只需要一条路由就可以打通不同节点的容器网络,但只能在二层互通的场景下完成
  2. ipiphost-gw 的基础上建立了一条 IP 隧道,这样在三层互通的情况下也可以打通容器网络
  3. vxlan 在类似 ipip 的基础上,基于三层隧道实现了二层的覆盖网络,提供了极高的网络隔离能力,相对也最复杂

从性能上看:

  1. host-gw 最快,因为没有任何封包的消耗
  2. ipip 次之,多了二十字节的 IP 头的封装
  3. vxlan 相对最慢,多了一整个 以太网、IP 层和 UDP 层的封装

参考

  1. 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
相关推荐
hakesashou1 小时前
Python中常用的函数介绍
java·网络·python
C++忠实粉丝1 小时前
计算机网络socket编程(4)_TCP socket API 详解
网络·数据结构·c++·网络协议·tcp/ip·计算机网络·算法
九州ip动态1 小时前
做网络推广及游戏注册为什么要换IP
网络·tcp/ip·游戏
Estar.Lee1 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
蝶开三月1 小时前
php:使用socket函数创建WebSocket服务
网络·websocket·网络协议·php·socket
G丶AEOM2 小时前
SSL/TLS,SSL,TLS分别是什么
网络·网络协议·网络安全
儒道易行2 小时前
【DVWA】RCE远程命令执行实战
网络·安全·网络安全
Koi慢热3 小时前
路由基础(全)
linux·网络·网络协议·安全
hzyyyyyyyu4 小时前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全