flannel
两种容器跨主机通信的方案,其中UDP模式
是IP in UDP
,即三层报文封装在UDP数据包中通信;而vxlan
模式则是MAC in UDP
,即二层报文封装在UDP数据包中通信
flannel UDP
模式和vxlan
模式都对数据包做了封解包,特别是UDP模式,还涉及到用户态和内核态数据切换,在性能上肯定存在一定的损耗。本文介绍flannel另外一种没有封解包的容器跨主机通信方案:flannel host-gateway模式,相比于UDP和vxlan模式,host-gateway模式没有额外的封解包过程,单纯依靠路由表项配置实现容器跨主机通信网络。其模型如下图:
在开始之前,我们先来简单了解二层互通与三层互通:
如上图所示,其中A、B、C、D是四台主机,E、F是两台二层交换机(根据MAC转发),G是三层路由器(根据IP转发)。
假设从A发出一个数据包,这个包到达E这台二层交换机后,如果E上没有目标MAC与端口的对应关系,就会将这个数据包往入口外的其它端口转发该报文,也就是E会把这个报文发给B和G。如果该数据包的目标MAC是B,那么B能正常处理该数据包,此时我们把A和B叫作“二层互通”;而如果目的MAC是C或者D(例如A上有一条网关是C或者D的路由),B和G收到数包后会因目的MAC不是自己而丢掉该数据包,此时A和C或者D都不通,我们把这种场景叫作“二层不互通”。
假设从A要访问C,G是A的默认网关,即A发出一个目的IP是C但MAC是G的数据包。这个数据包到达G时,G发现MAC是自己,于是根据相关机制找到C并把报文经过F这台二层交换机转发给C,A与C能正常通信,此时A与C叫作“三层互通”(但“二层不互通”)。
说明:
- 两台机器均已打开内核ipv4转发开关
- 容器分配的IP网段为1.1.0.0/16
- node1维护1.1.1.0/24 IP段
- node2维护1.1.2.0/24 IP段
- 两台主机二层互通
// node1
// 创建网络命名空间netns1
[root@VM-12-11-centos ~]# ip netns add netns1
// 创建虚拟网络设备对veth1-veth2
[root@VM-12-11-centos ~]# ip link add veth1 type veth peer name veth2
// 把veth2放入网络命名空间netns1
[root@VM-12-11-centos ~]# ip link set veth2 netns netns1
// 给veth2配置IP并up
[root@VM-12-11-centos ~]# ip netns exec netns1 ifconfig veth2 1.1.1.2/24 up
// 创建cni0网桥设备
[root@VM-12-11-centos ~]# ip link add name cni0 type bridge
// 给cni0配置IP并up
[root@VM-12-11-centos ~]# ifconfig cni0 1.1.1.1/24 up
// veth1连接cni0
[root@VM-12-11-centos ~]# ip link set dev veth1 master cni0
// 设备veth1 up
[root@VM-12-11-centos ~]# ifconfig veth1 up
// 增加默认路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add default via 1.1.1.1 dev veth2
// 查看netns1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1.1.1.1 0.0.0.0 UG 0 0 0 veth2
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth2
// node2
// 创建网络命名空间netns2
[root@VM-12-7-centos ~]# ip netns add netns2
// 创建虚拟网络设备对veth3-veth4
[root@VM-12-7-centos ~]# ip link add veth3 type veth peer name veth4
// 把veth4放入网络命名空间netns2
[root@VM-12-7-centos ~]# ip link set veth4 netns netns2
// 给veth4配置IP并up
[root@VM-12-7-centos ~]# ip netns exec netns2 ifconfig veth4 1.1.2.2/24 up
// 创建cni0网桥设备
[root@VM-12-7-centos ~]# ip link add name cni0 type bridge
// 给cni0配置IP并up
[root@VM-12-7-centos ~]# ifconfig cni0 1.1.2.1/24 up
// veth3连接cni0
[root@VM-12-7-centos ~]# ip link set dev veth3 master cni0
// 设置veth3 up
[root@VM-12-7-centos ~]# ifconfig veth3 up
// 增加默认路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add default 1.1.2.1 dev veth4
// 查看netns2路由
[root@VM-12-7-centos ~]# ip netns exec netns2 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1.1.2.1 0.0.0.0 UG 0 0 0 veth4
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 veth4
// node1
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node1
[root@VM-12-11-centos ~]# ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.038 ms
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.038/0.038/0.038/0.000 ms
[root@VM-12-11-centos ~]# ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
// node2
[root@VM-12-7-centos ~]# ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=64 time=0.019 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.019/0.019/0.019/0.000 ms
[root@VM-12-7-centos ~]# ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
// node1
[root@VM-12-11-centos ~]# ip route add 1.1.2.0/24 via 10.0.12.7 dev eth0
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
1.1.2.0 10.0.12.7 255.255.255.0 UG 0 0 0 eth0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# ip route add 1.1.1.0/24 via 10.0.12.11 dev eth0
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 10.0.12.11 255.255.255.0 UG 0 0 0 eth0
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
此时整体逻辑才算打通
// node1
[root@VM-12-11-centos ~]# ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=64 time=0.040 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.040/0.040/0.040/0.000 ms
[root@VM-12-11-centos ~]# ip netns exec netns1 ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=64 time=0.019 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.019/0.019/0.019/0.000 ms
// node2
[root@VM-12-7-centos ~]# ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.026 ms
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.026/0.026/0.026/0.000 ms
[root@VM-12-7-centos ~]# ip netns exec netns2 ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.024 ms
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.024/0.024/0.024/0.000 ms
flannel host-gateway模式正如host-gateway的含义,通过一条目标主机(host)IP作为网关(gateway)的路由实现容器跨主机通信。相比于UDP模式和vxlan模式,少了tun设备和vxlan设备的封解包过程,在性能上有更大的优势。但是直连路由要求主机间二层互通,这在一定程度上限制了host-gateway的使用场景,特别是公有云环境,不太好满足所有节点二层互通的条件。
上文的路由是手动配置的,在flannel的实现中,是通过flanneld进程完成该操作的:每个节点上起一个flanneld进程,flanneld进程起来后注册本节点管理的网段和宿主机IP等信息到etcd(直连etcd或者通过apiServer接口),并且监听其它节点的注册信息,当发现有新节点加入集群时,便会在本节点增加一条网关是新节点IP的路由。
对比项 | UDP模式 | vxlan模式 | host-gateway模式 |
---|---|---|---|
网络模式 | overlay | overlay | underlay |
封解包设备/程序 | tun+flanneld | vxlan | 无 |
节点间网络要求 | 三层互通 | 三层互通 | 二层互通 |
性能 | 最差 | 中等 | 最快 |
flanneld的作用 | UDP封解包;监听节点变化 | 监听节点变化,维护路由、ARP和FDB表项 | 监听节点变化,维护路由信息 |
Flannel.1
设备组成的虚拟二层网络[root@VM-12-11-centos ~]# ifconfig vxlan0
vxlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 172.1.1.1 netmask 255.255.255.0 broadcast 172.1.1.255
inet6 fe80::2c4d:5eff:fe39:5f92 prefixlen 64 scopeid 0x20<link>
ether 2e:4d:5e:39:5f:92 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 6 overruns 0 carrier 0 collisions 0
本文还需了解两个与vxlan有关的概念:
- VNI:每个vxlan子网通过唯一的VNI来标识区分,该标识是个24bit范围的整数
- VTEP:VXLAN Tunnel Endpoints,即vxlan网络的边缘设备,为了方便理解,本文把VTEP设备也叫作“vxlan设备”。VNI配置在VTEP设备上,VNI相同的VTEP设备属于同一个vxlan子网网络
[root@VM-12-11-centos ~]# ip -d link show vxlan0
33: vxlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 2e:4d:5e:39:5f:92 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 1 dev eth0 srcport 0 0 dstport 1111 ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
说明:
- 两台机器的8472端口开放了UDP的防火墙
- 两台机器均已打开内核ipv4转发开关
- 容器分配的IP网段为1.1.0.0/16
- flannel.1是vxlan设备,且该vxlan设备的VNI为1
// node1
// 创建vxlan设备flannel.1
[root@VM-12-11-centos ~]# ip link add flannel.1 type vxlan id 1 dstport 8472 dev eth0
// 配置IP并up
[root@VM-12-11-centos ~]# ifconfig flannel.1 1.1.0.1/16 up
// 查看vxlan设备信息
[root@VM-12-11-centos ~]# ifconfig flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 1.1.0.1 netmask 255.255.0.0 broadcast 1.1.255.255
inet6 fe80::7c06:12ff:fe29:250a prefixlen 64 scopeid 0x20<link>
ether 7e:06:12:29:25:0a txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 7 overruns 0 carrier 0 collisions 0
// 查看路由信息
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
// 创建vxlan设备flannel.1
[root@VM-12-7-centos ~]# ip link add flannel.1 type vxlan id 1 dstport 8472 dev eth0
// 配置IP并up
[root@VM-12-7-centos ~]# ifconfig flannel.1 1.1.0.2/16 up
// 查看vxlan设备信息
[root@VM-12-7-centos ~]# ifconfig flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 1.1.0.2 netmask 255.255.0.0 broadcast 1.1.255.255
inet6 fe80::24ba:90ff:fe73:3993 prefixlen 64 scopeid 0x20<link>
ether 26:ba:90:73:39:93 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 8 overruns 0 carrier 0 collisions 0
// 查看路由信息
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
[root@VM-12-11-centos ~]# netstat -nulp | grep 8472
udp 0 0 0.0.0.0:8472 0.0.0.0:* -
- 内层报文是二层报文,需要知道对方vxlan设备的MAC地址
- 外层报文需要知道对方vxlan设备宿主机IP,否则无法确定该UDP数据包发往哪台机器
// node1
[root@VM-12-11-centos ~]# arp -i flannel.1 -s 1.1.0.2 26:ba:90:73:39:93
[root@VM-12-11-centos ~]# ip neigh show dev flannel.1
1.1.0.2 lladdr 26:ba:90:73:39:93 PERMANENT
// node2
[root@VM-12-7-centos ~]# arp -i flannel.1 -s 1.1.0.1 7e:06:12:29:25:0a
[root@VM-12-7-centos ~]# ip neigh show dev flannel.1
1.1.0.1 lladdr 7e:06:12:29:25:0a PERMANENT
// node1
[root@VM-12-11-centos ~]# bridge fdb append 26:ba:90:73:39:93 dev flannel.1 dst 10.0.12.7
[root@VM-12-11-centos ~]# bridge fdb show dev flannel.1
26:ba:90:73:39:93 dst 10.0.12.7 self permanent
// node2
[root@VM-12-7-centos ~]# bridge fdb append 7e:06:12:29:25:0a dev flannel.1 dst 10.0.12.11
[root@VM-12-7-centos ~]# bridge fdb show dev flannel.1
7e:06:12:29:25:0a dst 10.0.12.11 self permanent
重新梳理下:vxlan设备1需要访问vxlan设备2时,先通过对方IP从ARP记录里找到对方MAC地址填充到内层报文中,再通过对方MAC地址从FDB表中查找到对方宿主机IP填充到外层报文中,这样就完成了封包过程。回包过程类似过程,不再赘述
验证下node1上flannel.1和node2上flannel.1是否已互通
// node1
[root@VM-12-11-centos ~]# ping -c 1 1.1.0.2
PING 1.1.0.2 (1.1.0.2) 56(84) bytes of data.
64 bytes from 1.1.0.2: icmp_seq=1 ttl=64 time=0.208 ms
--- 1.1.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.208/0.208/0.208/0.000 ms
// node2
[root@VM-12-7-centos ~]# ping -c 1 1.1.0.1
PING 1.1.0.1 (1.1.0.1) 56(84) bytes of data.
64 bytes from 1.1.0.1: icmp_seq=1 ttl=64 time=0.221 ms
--- 1.1.0.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.221/0.221/0.221/0.000 ms
// node1
// 创建网络命名空间netns1
[root@VM-12-11-centos ~]# ip netns add netns1
// 创建虚拟网络设备对veth1-veth2
[root@VM-12-11-centos ~]# ip link add veth1 type veth peer name veth2
// 把veth2放入网络命名空间netns1
[root@VM-12-11-centos ~]# ip link set veth2 netns netns1
// 给veth2配置IP并up
[root@VM-12-11-centos ~]# ip netns exec netns1 ifconfig veth2 1.1.1.2/24 up
// 增加默认路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add default via 1.1.1.1 dev veth2
// 查看netns1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1.1.1.1 0.0.0.0 UG 0 0 0 veth2
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth2
// node2
// 创建网络命名空间netns2
[root@VM-12-7-centos ~]# ip netns add netns2
// 创建虚拟网络设备对veth3-veth4
[root@VM-12-7-centos ~]# ip link add veth3 type veth peer name veth4
// 把veth4放入网络命名空间netns2
[root@VM-12-7-centos ~]# ip link set veth4 netns netns2
// 给veth4配置IP并up
[root@VM-12-7-centos ~]# ip netns exec netns2 ifconfig veth4 1.1.2.2/24 up
// 增加默认路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add default 1.1.2.1 dev veth4
// 查看netns2路由
[root@VM-12-7-centos ~]# ip netns exec netns2 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 1.1.2.1 0.0.0.0 UG 0 0 0 veth4
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 veth4
// node1
[root@VM-12-11-centos ~]# ip route add 1.1.2.0/24 via 1.1.0.2 dev flannel.1
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
1.1.2.0 1.1.0.2 255.255.255.0 UG 0 0 0 flannel.1
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# ip route add 1.1.1.0/24 via 1.1.0.1 dev flannel.1
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
1.1.1.0 1.1.0.1 255.255.255.0 UG 0 0 0 flannel.1
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node1
[root@VM-12-11-centos ~]# ip netns exec netns1 ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=62 time=0.250 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.250/0.250/0.250/0.000 ms
// node2
[root@VM-12-7-centos ~]# ip netns exec netns2 ping -c 1 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=63 time=0.235 ms
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.235/0.235/0.235/0.000 ms
- route表项:新增一条网关是新节点vxlan设备IP的路由
# ip route add $newNodeCidr via $newNodeVtepIP dev flannel.1
- ARP表项:新增一条新节点vxlan设备IP与MAC地址关系的ARP记录
# arp -i flannel.1 -s $newNodeVtepIP $newNodeVtepMac
- FDB表项:新增一条新节点vxlan设备MAC地址与新节点IP关系的记录
# bridge fdb append $newNodeVtepMac dev flannel.1 dst $newNodeIP
flannel UDP模式下会在每个节点上有一个用户态进程flanneld,这个进程主要有两个作用
在flannel vxlan模式下,每个节点上也会有一个用户态进程flanneld,但是此时的flanneld不再负责封解包(vxlan设备负责封解包),只负责监听节点的变化,并用程序完成前文更新本节点路由表项、ARP表项和FDB表项的命令
calico是一个网络方面的开源项目,它在kubernetes和虚拟机等场景下能提供两种能力:网络连通和网络安全策略(ACL)。calico的ACL会在后续文章中再做说明,本文只讨论calico的网络连通问题
针对网络连通问题,当前calico有两种比较成熟的方案:BGP模式和IPIP模式,本文只介绍calico BGP模式,IPIP模式会在下篇文章中再做说明,敬请期待。
flannel通过路由方式实现的一种高性能容器跨主机通信方案: flannel host-gateway模式,这里介绍的calico BGP模式和flannel host-gateway方式非常类似,也是通过一条网关为目标宿主机IP的路由来实现的,因此calico BGP模式也要求节点间二层互通
flannel host-gateway模式:
我们一直在说BGP模式,那么到底什么是BGP呢?BGP在calico中又起到了什么作用呢?
BGP全称是Border Gateway Protocol,即边界网关协议,读者无需为BGP这个不太熟悉的名词担心,因为当前只需理解下述例子就可以理解BGP在calico中的作用:
假设当前有如下两台可以BGP通信的机器:
主机名 主机IP 负责容器IP段 node1 192.168.1.100 1.1.1.0/24 node2 192.168.2.100 1.1.2.0/24 则node1发送给192.168.1.100的BGP信息可以简单看作:
[BGP信息]
我负责的容器网段是1.1.1.0/24
我的主机IP是192.168.1.100
同理,node2发送给192.168.1.100的BGP信息可以简单看作:
[BGP信息]
我负责的容器网段是1.1.2.0/24
我的主机IP是192.168.2.100
注:我当前使用的两台示例机器是centos 7,不同系统相关命令可能存在一定的差异
# yum install bird2 -y
# systemctl enable bird
Created symlink from /etc/systemd/system/multi-user.target.wants/bird.service to /usr/lib/systemd/system/bird.service.
# systemctl start bird
# birdc show route
BIRD 2.0.9 ready.
注:
- 如果bird进程没起来,我们可以用
journalctl -u bird
查看bird的启动日志排查原因,如果bird进程起来了但是没达到我们期望的效果,我们可以用birdc show route all
和birdc show proto all
查找相关错误信息。- calico封装了calicoctl工具来查看BGP相关数据。
主机名 | 主机IP | 负责容器IP段 |
---|---|---|
node1 | 10.0.12.11 | 1.1.1.0/24 |
node2 | 10.0.12.7 | 1.1.2.0/24 |
router id 10.0.12.11;
protocol static {
ipv4;
route 1.1.1.0/24 blackhole;
}
filter calico_kernel_programming {
accept;
}
protocol kernel {
learn; # Learn all alien routes from the kernel
persist; # Don't remove routes on bird shutdown
scan time 2; # Scan kernel routing table every 2 seconds
ipv4 {
import all;
export all;
};
graceful restart;
merge paths on;
}
protocol device {
debug { states };
scan time 2; # Scan interfaces every 2 seconds
}
protocol direct {
debug { states };
interface -"veth*", "*";
}
function calico_aggr ()
{
if ( net = 1.1.1.0/24 ) then { accept; }
if ( net ~ 1.1.1.0/24 ) then { reject; }
}
filter calico_export_to_bgp_peers {
calico_aggr();
if ( net ~ 1.1.0.0/16 ) then {
accept;
}
reject;
}
template bgp bgp_template {
debug { states };
description "Connection to BGP peer";
local 10.0.12.11 as 50011;
ipv4 {
import all;
export filter calico_export_to_bgp_peers;
};
graceful restart;
connect delay time 2;
connect retry time 5;
error wait time 5,30;
}
protocol bgp Mesh_10_0_12_7 from bgp_template {
neighbor 10.0.12.7 port 179 as 50007;
}
上述配置文件简单翻译就是:我负责1.1.1.0/24网段IP,我的主机IP是10.0.12.11,我要把这条信息发给我的邻居10.0.12.7。
该配置文件需要注意几个地方
- route id作为每个BGP client的唯一标识,不能重复,一般以主机IP命名;
- protocol static下有一条黑洞路由,具体作用下文会说明;
- protocol direct下有条-"veth*"的条目,我在本文后续中的操作会把veth pair的名称以veth开头,因此这里是-“veth*”,calico原生的veth pair名称是以cali开头,它配置的是-“cali*”;
- function calico_aggr中配置的信息在本文中是要告知其它机器你负责的容器IP网段是什么;
- filter calico_export_to_bgp_peers中1.1.0.0/16表示容器IP大网段范围;
- template bgp bgp_template下,local $IP as $um条目中,填的是当前宿主机,num是作为信息校验的一个标识,需要在对方的配置中匹配上;
- protocol bgp Mesh_10_0_12_7 from bgp_template下,neighbor $IP $port as 条目中, I P 填的是邻居的宿主机 I P ,填写默认的的端口, n u m 则要与对方 t e m p l a t e b g p b g p t e m p l a t e 的 n u m (也就是上述第 6 点中的 条目中,IP填的是邻居的宿主机IP,填写默认的的端口,num则要与对方template bgp bgp_template的num(也就是上述第6点中的 条目中,IP填的是邻居的宿主机IP,填写默认的的端口,num则要与对方templatebgpbgpt?emplate的num(也就是上述第6点中的num)对应上。
router id 10.0.12.7;
protocol static {
ipv4;
route 1.1.2.0/24 blackhole;
}
filter calico_kernel_programming {
accept;
}
protocol kernel {
learn; # Learn all alien routes from the kernel
persist; # Don't remove routes on bird shutdown
scan time 2; # Scan kernel routing table every 2 seconds
ipv4 {
import all;
export all;
};
graceful restart;
merge paths on;
}
protocol device {
debug { states };
scan time 2; # Scan interfaces every 2 seconds
}
protocol direct {
debug { states };
interface -"veth*", "*"; # Exclude cali* and kube-ipvs* but
# include everything else. In
# IPVS-mode, kube-proxy creates a
# kube-ipvs0 interface. We exclude
# kube-ipvs0 because this interface
# gets an address for every in use
# cluster IP. We use static routes
# for when we legitimately want to
# export cluster IPs.
}
function calico_aggr ()
{
if ( net = 1.1.2.0/24 ) then { accept; }
if ( net ~ 1.1.2.0/24 ) then { reject; }
}
filter calico_export_to_bgp_peers {
calico_aggr();
if ( net ~ 1.1.0.0/16 ) then {
accept;
}
reject;
}
template bgp bgp_template {
debug { states };
description "Connection to BGP peer";
local 10.0.12.7 as 50007;
ipv4 {
import all;
export filter calico_export_to_bgp_peers;
};
graceful restart;
connect delay time 2;
connect retry time 5;
error wait time 5,30;
}
protocol bgp Mesh_10_0_12_11 from bgp_template {
neighbor 10.0.12.11 port 179 as 50011;
}
// node1
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.2.0 10.0.12.7 255.255.255.0 UG 0 0 0 eth0
# ip route
default via 10.0.12.1 dev eth0
10.0.12.0/22 dev eth0 proto kernel scope link src 10.0.12.11
// node2
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
# ip route
default via 10.0.12.1 dev eth0
10.0.12.0/22 dev eth0 proto kernel scope link src 10.0.12.7
// node1
# systemctl restart bird
// node2
# systemctl restart bird
// node1
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 0.0.0.0 255.255.255.0 U 32 0 0 *
1.1.2.0 10.0.12.7 255.255.255.0 UG 0 0 0 eth0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
# ip route
default via 10.0.12.1 dev eth0
blackhole 1.1.1.0/24 proto bird metric 32
1.1.2.0/24 via 10.0.12.7 dev eth0
10.0.12.0/22 dev eth0 proto kernel scope link src 10.0.12.11
// node2
# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 10.0.12.11 255.255.255.0 UG 0 0 0 eth0
1.1.2.0 0.0.0.0 255.255.255.0 U 32 0 0 *
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
# ip route
default via 10.0.12.1 dev eth0
1.1.1.0/24 via 10.0.12.11 dev eth0
blackhole 1.1.2.0/24 proto bird metric 32
10.0.12.0/22 dev eth0 proto kernel scope link src 10.0.12.7
我们再把netns和veth pair补上,注意对比flannel网络模式是没有网桥cni0的:
// node1
// 创建网络命名空间netns1
[root@VM-12-11-centos ~]# ip netns add netns1
// 创建虚拟网络设备对veth1-veth2
[root@VM-12-11-centos ~]# ip link add veth1 type veth peer name veth2
// 把veth2放入网络命名空间netns1
[root@VM-12-11-centos ~]# ip link set veth2 netns netns1
// 给veth2配置IP并up
[root@VM-12-11-centos ~]# ip netns exec netns1 ifconfig veth2 1.1.1.2/24 up
// 设备veth1 up
[root@VM-12-11-centos ~]# ifconfig veth1 up
// 查看netns1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth2
// node2
// 创建网络命名空间netns2
[root@VM-12-7-centos ~]# ip netns add netns2
// 创建虚拟网络设备对veth3-veth4
[root@VM-12-7-centos ~]# ip link add veth3 type veth peer name veth4
// 把veth4放入网络命名空间netns2
[root@VM-12-7-centos ~]# ip link set veth4 netns netns2
// 给veth4配置IP并up
[root@VM-12-7-centos ~]# ip netns exec netns2 ifconfig veth4 1.1.2.2/24 up
// 设置veth3 up
[root@VM-12-7-centos ~]# ifconfig veth3 up
// 查看netns2路由
[root@VM-12-7-centos ~]# ip netns exec netns2 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 veth4
到这里我们完成了如下逻辑:
我们注意到两点:
- node1上的veth1和node2上的veth3,只是up状态但是并没有配置IP;
- node1上netns1网络命名空间只有目的网段是1.1.1.0/24的路由,node2上netns2网络命名空间只有目的网段是1.1.2.0/24的路由。
通过上述环境准备,我们发现创建的网络命名空间内无法访问非本宿主机维护外的容器网段IP,例如node1上netns1无法访问1.1.2.0/24,因为该网络命名空间内没有对应的路由。回忆下flannel host-gateway模式,我们是在该网络命名空间下加了一条网关是网桥cni0的默认路由,但是calico没有cni0网桥,该如何处理呢?这里calico用到了一点点“黑科技”——arp proxy。
arp proxy
calico为了简化网络配置,会在容器网络命名空间内添加一条网关是169.254.1.1(预留的本地网段IP,实际中并不存在某个网络设备IP是它)的默认路由,这样将容器内的默认路由都设置成了一样,不需要动态更新。即容器网络命名空间下路由为:
# ip route default via 169.254.1.1 dev eth0 169.254.1.1 dev eth0
注:为了和之前的文章保持一致,本文手动创建的网络命名空间内的网卡还是延续之前veth开头的命名规则,没有改为eth0。
以node1为例,在netns1网络命名空间内添加了网关是169.254.1.1的默认路由后,容器会查询下一跳(即169.254.1.1)的MAC地址,这个ARP请求从netns1网络命名空间内通过veth2到达宿主机上veth pair的另一端veth1。veth1只有MAC地址没有IP,收到这个ARP请求后会怎么处理呢?答案是veth1直接应答,返回自己的MAC地址,后续netns1网络命名空间报文IP还是目的IP,但是MAC地址都变成了主机上veth1的MAC地址,也就是netns1上出来的报文都会发给主机网络,主机再根据目的IP地址进行转发。
veth1不管ARP请求内容直接返回自己MAC地址做应答的行为称为“ARP proxy”,可通过把 /proc/sys/net/ipv4/conf/veth1/proxy_arp 置一来开启该功能。
通过arp proxy知识的补充,我们在node1和node2上执行如下命令重新配置出方向路由:
// node1
// netns1网络命名空间内先删除配置IP时自动生成的路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route del 1.1.1.0/24
// 宿主机上开启veth1的arp proxy
[root@VM-12-11-centos ~]# echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp
// netns1网络命名空间内添加169.254.1.1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add 169.254.1.1 dev veth2
// netns1网络命名空间内添加默认路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add default via 169.254.1.1 dev veth2
// 查看netns1网络命名空间路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route
default via 169.254.1.1 dev veth2
169.254.1.1 dev veth2 scope link
// node2
// netns2网络命名空间内先删除配置IP时自动生成的路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route del 1.1.2.0/24
// 宿主机上开启veth3的arp proxy
[root@VM-12-7-centos ~]# echo 1 > /proc/sys/net/ipv4/conf/veth3/proxy_arp
// netns2网络命名空间内添加169.254.1.1路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add 169.254.1.1 dev veth4
// netns2网络命名空间内添加默认路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add default via 169.254.1.1 dev veth4
// 查看netns2网络命名空间路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route
default via 169.254.1.1 dev veth4
169.254.1.1 dev veth4 scope link
到这里,我们完成了如下逻辑:
通过上述配置,数据包从自建网络命名空间出来是没问题了,但是从宿主机默认网络命名空间到自建网络命名空间回程数据包呢?calico也是采用配置一条路由来实现的。以node1上的netns1网络命名空间为例,会配置一条如下路由:
[root@VM-12-11-centos ~]# ip route add 1.1.1.2/32 dev veth1
同理node2上也会配置一条:
[root@VM-12-7-centos ~]# ip route add 1.1.2.2/32 dev veth3
所以,到这里我们完成了如下逻辑:
当前我们只是以一个网络命名空间为例,假设节点上有N个网络命名空间,那么calico就会在节点上就会配置N条入方向的路由。
[root@VM-12-11-centos ~]# route -n | grep 1.1.1
1.1.1.0 0.0.0.0 255.255.255.0 U 32 0 0 *
1.1.1.2 0.0.0.0 255.255.255.255 UH 0 0 0 veth1
[root@VM-12-11-centos ~]# ip route | grep 1.1.1
blackhole 1.1.1.0/24 proto bird metric 32
1.1.1.2 dev veth1 scope link
前文提到,calico的bird会配置一条黑洞路由,例如node1上会有一条1.1.1.0/24 blackhole的黑洞路由,结合入方向的路由(1.1.1.2/32 dev veth1)和路由的最长匹配原则,此时如果有大量请求访问1.1.1.0/24下的不存在的IP(非1.1.1.2)时,会路由到黑洞路由,这样能有效减小这些无效请求对系统负载的影响。
我们通过前面的操作从理论上实现了两个二层互通节点间的容器网络通信,我们来验证一下跨节点自建网络命名空间通信:
// node1
[root@VM-12-11-centos ~]# ip netns exec netns1 ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=64 time=0.049 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.049/0.049/0.049/0.000 ms
// node2
[root@VM-12-7-centos ~]# ip netns exec netns2 ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=64 time=0.019 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.019/0.019/0.019/0.000 ms
- 数据包在node1上netns1网络协议栈组装好,通过匹配默认路由default via 169.254.1.1 dev veth2,数据包从veth2发出;
- node1上从veth2出去的数据包到达veth pair的另一端veth1;
- 数据包匹配bird生成的1.1.2.0/24 via 10.0.12.7 dev eth0路由,把数据包从eth0发出,到达下一跳10.0.12.7,也就是node2;
- node2上匹配路由1.1.2.2/32 dev veth3,数据包发往veth3;
- node2上从veth3过来的数据包到达veth pair的另一端,netns2网络命名空间下的veth4;
- node2上netns2网络命名空间下的veth4发现1.1.2.2是自己,构造回程报文,从veth4发出;
- node2上veth4发出来的报文到达veth pair的另一端veth3;
- node2上到达veth3的报文匹配路由1.1.1.0/24 via 10.0.12.11/32 dev eth0,从eth0发出到达node1;
- node1上匹配1.1.1.2/32 dev veth1到达veth pair的一端veth1;
- node1上从veth1过来的包到达veth pair的另一端,即netns1网络命名空间下的veth2;
- node1上netns1网络命名空间下的veth2收到回程报文。
通过前面的步骤,我们调通了两个节点间的跨主机网络通信模型,但是与calico项目本身的结合还不是很密切,例如bird的配置怎么维护?入方向路由谁来配置?,我们再从这一角度分析下calico BGP模式。
安装完calico之后会创建一个叫作calico-node的daemonSet,daemonSet的特性是会在每个节点上起个pod,这个pod主要有两个功能:
我们主要关注第二点的三个进程在本文中的作用
bird在前文中已经提到,主要是通过BGP通信,实现以下三个功能:
- 生成一条黑洞路由;
- 把本机上的负责的容器网段和主机IP“广播”出去;
- 接收其它bird发过来的网段和主机IP信息,在本地生成路由。
前文我们操作bird时,配置文件都是手动维护的,其中配置项包含的关键信息有本机负责的容器网段、本机IP、邻居节点IP等,如果考虑kubernetes的增加/移除节点等场景,手动维护这些配置的成本太高了,作为一个成熟的网络方案,calico肯定也不会通过手动维护的方式来配置bird。
confd就是负责自动维护bird配置的进程,它通过直接或间接监听ectd中相应信息变化,更新bird配置文件,之后通过reload操作使bird新的配置生效。
felix进程在本文中主要是负责容器网络命名空间入方向的的路由维护,例如node1上的1.1.1.2/32 dev veth1。另外,文章开头提到的ACL功能,也是felix通过维护iptables规则来实现的。
calico IPIP模式是calico默认模式
calico BGP模式的原理与实现,并强调了BGP模式要求节点间二层互通, calico BGP模式原理
calico使用的是ipip隧道,因此本文仅介绍ipip隧道,其它类型的隧道读者可查看相关资料进行了解
# lsmod | grep ipip
#
# modprobe ipip
# lsmod | grep ipip
ipip 13465 0
tunnel4 13252 1 ipip
ip_tunnel 25163 1 ipip
When the ipip module is loaded, or an IPIP device is created for the first time, the Linux kernel will create a tunl0 default device in each namespace, with attributes local=any and remote=any. When receiving IPIP protocol packets, the kernel will forward them to tunl0 as a fallback device if it can’t find another device whose local/remote attributes match their source or destination address more closely.
# ip -d link show tunl0
35: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# ip netns exec netns1 ip -d link show tunl0
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
准备两台三层互通的机器,注意开启ip_forward转发,且node1负责的容器IP网段为1.1.1.0/24,node2负责的容器IP网段为1.1.2.0/24:
两台机器均加载ipip模块,给ipip隧道设备tunl0配置ip并up
// node1
// 加载ipip模块
[root@VM-12-11-centos ~]# modprobe ipip
// 验证模块是否加载成功
[root@VM-12-11-centos ~]# lsmod | grep ipip
ipip 13465 0
tunnel4 13252 1 ipip
ip_tunnel 25163 1 ipip
// 查看ipip隧道设备
[root@VM-12-11-centos ~]# ip -d link show tunl0
35: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
// 给ipip隧道设备配置IP并up
[root@VM-12-11-centos ~]# ifconfig tunl0 1.1.0.1/24 up
// 查看ipip隧道设备ip信息
[root@VM-12-11-centos ~]# ifconfig tunl0
tunl0: flags=193<UP,RUNNING,NOARP> mtu 1480
inet 1.1.0.1 netmask 255.255.255.0
tunnel txqueuelen 1000 (IPIP Tunnel)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
// 查看路由
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.255.0 U 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# modprobe ipip
[root@VM-12-7-centos ~]# lsmod | grep ipip
ipip 13465 0
tunnel4 13252 1 ipip
ip_tunnel 25163 1 ipip
[root@VM-12-7-centos ~]# ip -d link show tunl0
9: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0 promiscuity 0
ipip remote any local any ttl inherit nopmtudisc numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
[root@VM-12-7-centos ~]# ifconfig tunl0 1.1.0.2/24 up
[root@VM-12-7-centos ~]# ifconfig tunl0
tunl0: flags=193<UP,RUNNING,NOARP> mtu 1480
inet 1.1.0.2 netmask 255.255.255.0
tunnel txqueuelen 1000 (IPIP Tunnel)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.255.0 U 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
到这里,我们准备的逻辑如下:
前文有提到:When receiving IPIP protocol packets, the kernel will forward them to tunl0 as a fallback device if it can't find another device whose local/remote attributes match their source or destination address more closely
,以node1为例,我们基于tunl0设备的相关特点,可以增加一条路由1.1.2.0/24 via 10.0.12.7 dev tunl0
。
然而如果直接执行ip route add 1.1.2.0/24 via 10.0.12.7 dev tunl0
,你会发现会报如下错误:
[root@VM-12-11-centos ~]# ip route add 1.1.2.0/24 via 10.0.12.7 dev tunl0
RTNETLINK answers: Network is unreachable
参考(需要能访问外网):https://medium.com/@samueldarwin/full-mesh-ipip-tunnels-d16888913e40
# ip route add 1.1.2.0/24 via 10.0.12.7 dev tunl0 onlink
// node1
[root@VM-12-11-centos ~]# ip route add 1.1.2.0/24 via 10.0.12.7 dev tunl0 onlink
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.255.0 U 0 0 0 tunl0
1.1.2.0 10.0.12.7 255.255.255.0 UG 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# ip route add 1.1.1.0/24 via 10.0.12.11 dev tunl0 onlink
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.0.0 0.0.0.0 255.255.255.0 U 0 0 0 tunl0
1.1.1.0 10.0.12.11 255.255.255.0 UG 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node1
[root@VM-12-11-centos ~]# ip route delete 1.1.0.0/24
[root@VM-12-11-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.2.0 10.0.12.7 255.255.255.0 UG 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
// node2
[root@VM-12-7-centos ~]# ip route delete 1.1.0.0/24
[root@VM-12-7-centos ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.12.1 0.0.0.0 UG 0 0 0 eth0
1.1.1.0 10.0.12.11 255.255.255.0 UG 0 0 0 tunl0
10.0.12.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
在calico项目中,这条与对应节点“建立ipip隧道的路由”是由bird生成的,本文侧重calico IPIP模式原理,bird生成路由以及bird配置文件相关语法资料。
calico IPIP模式和calico BGP模式一样,也是没有网桥去汇总宿主机默认网络命名空间的veth pair
// node1
// 创建网络命名空间netns1
[root@VM-12-11-centos ~]# ip netns add netns1
// 创建虚拟网络设备对veth1-veth2
[root@VM-12-11-centos ~]# ip link add veth1 type veth peer name veth2
// 把veth2放入网络命名空间netns1
[root@VM-12-11-centos ~]# ip link set veth2 netns netns1
// 给veth2配置IP并up
[root@VM-12-11-centos ~]# ip netns exec netns1 ifconfig veth2 1.1.1.2/24 up
// 设备veth1 up
[root@VM-12-11-centos ~]# ifconfig veth1 up
// 查看netns1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 veth2
// node2
// 创建网络命名空间netns2
[root@VM-12-7-centos ~]# ip netns add netns2
// 创建虚拟网络设备对veth3-veth4
[root@VM-12-7-centos ~]# ip link add veth3 type veth peer name veth4
// 把veth4放入网络命名空间netns2
[root@VM-12-7-centos ~]# ip link set veth4 netns netns2
// 给veth4配置IP并up
[root@VM-12-7-centos ~]# ip netns exec netns2 ifconfig veth4 1.1.2.2/24 up
// 设置veth3 up
[root@VM-12-7-centos ~]# ifconfig veth3 up
// 查看netns2路由
[root@VM-12-7-centos ~]# ip netns exec netns2 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
1.1.2.0 0.0.0.0 255.255.255.0 U 0 0 0 veth4
// netns1网络命名空间内先删除配置IP时自动生成的路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route del 1.1.1.0/24
// 宿主机上开启veth1的arp proxy
[root@VM-12-11-centos ~]# echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp
// netns1网络命名空间内添加169.254.1.1路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add 169.254.1.1 dev veth2
// netns1网络命名空间内添加默认路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route add default via 169.254.1.1 dev veth2
// 查看netns1网络命名空间路由
[root@VM-12-11-centos ~]# ip netns exec netns1 ip route
default via 169.254.1.1 dev veth2
169.254.1.1 dev veth2 scope link
// node2
// netns2网络命名空间内先删除配置IP时自动生成的路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route del 1.1.2.0/24
// 宿主机上开启veth3的arp proxy
[root@VM-12-7-centos ~]# echo 1 > /proc/sys/net/ipv4/conf/veth3/proxy_arp
// netns2网络命名空间内添加169.254.1.1路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add 169.254.1.1 dev veth4
// netns2网络命名空间内添加默认路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route add default via 169.254.1.1 dev veth4
// 查看netns2网络命名空间路由
[root@VM-12-7-centos ~]# ip netns exec netns2 ip route
default via 169.254.1.1 dev veth4
169.254.1.1 dev veth4 scope link
// node1
[root@VM-12-11-centos ~]# ip route add 1.1.1.2/32 dev veth1
// node2
[root@VM-12-7-centos ~]# ip route add 1.1.2.2/32 dev veth3
// node1
[root@VM-12-11-centos ~]# ip netns exec netns1 ping -c 1 1.1.2.2
PING 1.1.2.2 (1.1.2.2) 56(84) bytes of data.
64 bytes from 1.1.2.2: icmp_seq=1 ttl=62 time=0.274 ms
--- 1.1.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.274/0.274/0.274/0.000 ms
// node2
[root@VM-12-7-centos ~]# ip netns exec netns2 ping -c 1 1.1.1.2
PING 1.1.1.2 (1.1.1.2) 56(84) bytes of data.
64 bytes from 1.1.1.2: icmp_seq=1 ttl=62 time=0.279 ms
--- 1.1.1.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.279/0.279/0.279/0.000 ms
// node1
[root@VM-12-11-centos ~]# tcpdump -i eth0 -n host 10.0.12.7
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:59:47.220363 IP 10.0.12.11 > 10.0.12.7: IP 1.1.1.2 > 1.1.2.2: ICMP echo request, id 17681, seq 1, length 64 (ipip-proto-4)
20:59:47.220546 IP 10.0.12.7 > 10.0.12.11: IP 1.1.2.2 > 1.1.1.2: ICMP echo reply, id 17681, seq 1, length 64 (ipip-proto-4)
// node2
[root@VM-12-7-centos ~]# tcpdump -i eth0 -n host 10.0.12.11
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
20:59:47.219832 IP 10.0.12.11 > 10.0.12.7: IP 1.1.1.2 > 1.1.2.2: ICMP echo request, id 17681, seq 1, length 64 (ipip-proto-4)
20:59:47.219879 IP 10.0.12.7 > 10.0.12.11: IP 1.1.2.2 > 1.1.1.2: ICMP echo reply, id 17681, seq 1, length 64 (ipip-proto-4)
Note:
如果是云服务器,到这里可能不通。以腾讯云为例,ipip隧道协议不在默认的防火墙范围内,需要放通这两台机器间的防火墙才能正常访问。
在ipip模式下,calico支持一种叫做cross-subnet的配置,所谓的cross-subnet,其实就是calico通过某种方法判断出两个节点是否是二层互通,如果是二层互通,则bird发布的规则是BGP模式的路由(类似$cidr via $dstHost dev eth0
);如果非二层互通,则配置成ipip模式路由(类似$cidr via $dstHost dev tunl0 onlink
)
其实flannel也支持“cross-subnet”功能,只不过在flannel中不叫cross-subnet而是叫作direct-routing,这两者原理都差不多:flannel vxlan模式下如果配置了direct-routing,二层互通的机器间路由类似host-gateway模式,三层互通的机器则是正常的vxlan模式路由。flannel判断是否是direct-routing的源代码如下:
// flannel/pkg/ip/iface.go
func DirectRouting(ip net.IP) (bool, error) {
routes, err := netlink.RouteGet(ip)
if err != nil {
return false, fmt.Errorf("couldn't lookup route to %v: %v", ip, err)
}
if len(routes) == 1 && routes[0].Gw == nil {
// There is only a single route and there's no gateway (i.e. it's directly connected)
return true, nil
}
return false, nil
}
模式 | flannel | calico |
---|---|---|
UDP模式 | 支持(但已弃用) | 不支持 |
vxlan模式 | 支持 | 支持 |
host-gateway模式 | 支持 | 支持(即BGP模式) |
IPIP模式 | 支持 | 支持 |