配置集群使用:
修改configmap
kubectl edit -n kube-system configmaps kube-flannel-cfg
修改内容:
...
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
...
"Type": "vxlan"
改为"Type": "host-gw"
重启服务
kubectl rollout restart -n kube-system daemonset kube-flannel-ds
检查是否启动成功
kubectl logs -n kube-system kube-flannel-ds-467p2|grep "host-gw"
检查节点路由表:
[root@master ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.88.8.254 0.0.0.0 UG 100 0 0 ens192
10.88.8.0 0.0.0.0 255.255.252.0 U 100 0 0 ens192
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 10.88.10.182 255.255.255.0 UG 0 0 0 ens192
10.244.2.0 10.88.10.183 255.255.255.0 UG 0 0 0 ens192
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
host-gw模式中,路由表中子网网关对应的都是该子网的宿主机IP.而在VXLAN中,路由表中flannel子网对应的设备都是flannel.1
.
host-gw是一种三层跨主机网络解决方案,不同于VXLAN模式的虚拟二层网络,他是根据IP地址来进行判断的.
同一主机内不同pod通信直接略过,开始不同主机的pod通信.
测试过程中同样使用上次的pod,一个数据包从master节点的pod(IP10.244.0.5
)发往worker1节点的pod IP10.244.1.17
.
[root@master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
network-tools-66b6674fd9-pjpz6 1/1 Running 0 27h 10.244.0.5 master <none> <none>
network-tools-66b6674fd9-tmgcs 1/1 Running 0 27h 10.244.2.13 worker2 <none> <none>
network-tools-66b6674fd9-zk58f 1/1 Running 0 27h 10.244.1.17 worker1 <none> <none>-
首先进入master节点的pod查看下路由表:
root@network-tools-66b6674fd9-pjpz6:/# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.0.1 0.0.0.0 UG 0 0 0 eth0
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
10.244.0.0 10.244.0.1 255.255.0.0 UG 0 0 0 eth0
跟之前没啥区别,0.0.0.0
默认规则,10.244.0.0
网关0.0.0.0
的是本机pod子网网段直连规则同样略过,接下来直接根据第三条路由,去宿主机查询网卡看看那个包含这个IP.
[root@master ~]# ifconfig |grep "10.244.0.1" -B 5 -A 3
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.1 netmask 255.255.255.0 broadcast 10.244.0.255
inet6 fe80::f03d:adff:fea2:563e prefixlen 64 scopeid 0x20<link>
ether f2:3d:ad:a2:56:3e txqueuelen 1000 (Ethernet)
RX packets 1988416 bytes 269346410 (256.8 MiB)
依旧是cni0.
cni0是Kubernetes自动替换掉docker0网桥所创建的一个设备.这样不管什么网络,flannel也好calico也罢,只需要面向cni0这个网桥,也就是符合Kubernetes的规则即可,不需要关心底层到底使用了什么容器匹配什么网络.
按照之前的套路相同,流量从cni0流出后来到宿主机网络,进一步匹配宿主机路由,查看下宿主机路由:
[root@master ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.88.8.254 0.0.0.0 UG 100 0 0 ens192
10.88.8.0 0.0.0.0 255.255.252.0 U 100 0 0 ens192
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 10.88.10.182 255.255.255.0 UG 0 0 0 ens192
10.244.2.0 10.88.10.183 255.255.255.0 UG 0 0 0 ens192
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
从此处开始不同了.对应的目标子网的Use Iface
不再是flannel.1
的虚拟设备,而是主机的ens192
网卡了,同时网关也变成了10.88.10.182
,这是一个宿主机网段的节点机器的IP.
通过ip route
命令可以得到更加详细的结果:
[root@master ~]# ip route
default via 10.88.8.254 dev ens192 proto static metric 100
10.88.8.0/22 dev ens192 proto kernel scope link src 10.88.10.181 metric 100
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 10.88.10.182 dev ens192
10.244.2.0/24 via 10.88.10.183 dev ens192
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
其中可以看到每个flannel的子网还对应了一个via 10.88.10.182
,via表示的就是"下一跳"的地址.并且从ens192网卡发出去.
这是一个宿主机能直接读懂的规则,所以数据包从cni0网络出来后,会直接被宿主机开始封装打包,ens192设备就会用下一跳的mac地址来封装二层数据帧(之所以没有IP层是因为数据包从主机发出的就是一个IP包,所以无需封装),然后数据包会通过物理网络来到对端宿主机的网卡.
对端主机接收到数据包后,拆开二层包,直接根据IP地址匹配自己的路由:
[root@worker1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.88.8.254 0.0.0.0 UG 100 0 0 ens192
10.88.8.0 0.0.0.0 255.255.252.0 U 100 0 0 ens192
10.244.0.0 10.88.10.181 255.255.255.0 UG 0 0 0 ens192
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.2.0 10.88.10.183 255.255.255.0 UG 0 0 0 ens192
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
目的地址的10.244.1.17
更加符合第四条规则,网关地址0.0.0.0
,意味这是一条直连规则,对应设备是cni0
.
数据包自然就通过cni0发送给了对应的pod.
因为要用宿主机节点的IP来作为数据包的"下一跳"地址,所以host-gw模式要求宿主机二层网络必须是互通的
三层IP层包的源IP和目的IP实际都是容器IP.所以这个包在四层的时候就要写明worker2节点的mac地址,才能转发出去.而为了能让worker1节点接收到这个包,就用到了下一跳的这个地址.
换算到Kubernetes中还得加上iptables的规则,所以此时的转发为:
明显可见少了一层flannel.1
的转发,所以host-gw性能相比VXLAN来说有所提高,据传言host-gw方式比起宿主机直接传输性能损耗约10%,而VXLAN则在20%~30%之间.
calico的网络切换
calico的网络就更屌了.他连网桥都不用了,直接就用Veth Pair设备,把容器对接到了宿主机(创建的虚拟设备cali开头).
还是假设master节点pod(IP:10.244.235.130
)访问worker1的pod(IP:10.244.235.129
).
[root@master ~]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
network-tools-66b6674fd9-kf77w 1/1 Running 0 7m37s 10.244.235.130 worker1 <none> <none>
network-tools-66b6674fd9-rqf8x 1/1 Running 0 7m37s 10.244.189.67 worker2 <none> <none>
network-tools-66b6674fd9-wppm4 1/1 Running 0 7m37s 10.244.235.129 worker1 <none> <none>
数据包被传输到主机节点后,直接根据主机节点开始路由
[root@master ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.88.8.254 0.0.0.0 UG 100 0 0 ens192
10.88.8.0 0.0.0.0 255.255.252.0 U 100 0 0 ens192
10.244.0.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.1.0 10.88.10.182 255.255.255.255 UGH 0 0 0 tunl0
10.244.1.0 10.88.10.182 255.255.255.0 UG 0 0 0 ens192
10.244.2.0 10.88.10.183 255.255.255.255 UGH 0 0 0 tunl0
10.244.2.0 10.88.10.183 255.255.255.0 UG 0 0 0 ens192
10.244.189.64 10.88.10.183 255.255.255.192 UG 0 0 0 tunl0
10.244.219.64 0.0.0.0 255.255.255.192 U 0 0 0 *
10.244.219.65 0.0.0.0 255.255.255.255 UH 0 0 0 cali88526110d1d
10.244.235.128 10.88.10.182 255.255.255.192 UG 0 0 0 tunl0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
他明显更符合倒数第二条:
10.244.235.128 10.88.10.182 255.255.255.192 UG 0 0 0 tunl0
他的网关是10.88.10.182
,也就是worker1节点宿主机的IP,这里很类似与flannel的host-gw方式,也是配置一个下一跳的地址来指定.
但是这个包要通过一个tunl0的设备发出去.包来到这个tunl0之后会被再次进行一层封装,这个时候会把容器发出来的IP包作为一个数据包,重新在上面封装一个IP层(是把容器发出来的带有源IP和目标IP这一层连带本身的数据视作一个数据包,在他的上面重新封装三层)不过这次封装的IP层,直接掩盖住了原有的IP层,"伪装"成一个从master到worker1主机的通信包,从ens192网卡发出通过主机网络传输.
而worker1接收到以后,同样先由主机进行拆包,接下来交给tunl0设备,他会还原最初的容器发出的三层数据包,也就是到目标IP10.244.235.129
这一层.
接下来数据包要继续转发到哪,就要取决于worker1节点主机的路由了.
calico模式下,每创建一个pod后calico都会给他创建对应的虚拟网络对接到主机,同时在主机增加一条路由,记录下容器的IP和对应设备的关系,这种就类似于一个"边界网关"
[root@worker1 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.88.8.254 0.0.0.0 UG 100 0 0 ens192
10.88.8.0 0.0.0.0 255.255.252.0 U 100 0 0 ens192
10.244.0.0 10.88.10.181 255.255.255.255 UGH 0 0 0 tunl0
10.244.0.0 10.88.10.181 255.255.255.0 UG 0 0 0 ens192
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.2.0 10.88.10.183 255.255.255.255 UGH 0 0 0 tunl0
10.244.2.0 10.88.10.183 255.255.255.0 UG 0 0 0 ens192
10.244.189.64 10.88.10.183 255.255.255.192 UG 0 0 0 tunl0
10.244.219.64 10.88.10.181 255.255.255.192 UG 0 0 0 tunl0
10.244.235.128 0.0.0.0 255.255.255.192 U 0 0 0 *
10.244.235.129 0.0.0.0 255.255.255.255 UH 0 0 0 calia5b07904eb8
10.244.235.130 0.0.0.0 255.255.255.255 UH 0 0 0 calif1f8fa46c64
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
接下来命中倒数第三条路由
10.244.235.129 0.0.0.0 255.255.255.255 UH 0 0 0 calia5b07904eb8
数据包将会直接通过calia5b07904eb8
这个设备转发,这个设备一头在主机另一头连接到容器(Veth Pair设备),所以,包会被直接转发进容器.
这种需要使用tunl0进行封包解包的模式叫做IPIP模式(很形象,一个IP层后又套了一个IP层),这种网络模式的性能和flannel的VXLAN是差不多的.不过他的优点在于可以在一个需要路由转发的环境中通信(集群中的节点分布在两个局域网).
flannel VXLAN的设计思想是在现有的三层网络上覆盖一个二层网络,所以他需要根据mac地址来转发数据包,而host-gw模式又需要用到路由表中的对端主机IP来配置为自己数据包的"下一跳"地址,同样要求二层网络必须通畅.而calico的IPIP模式封装后数据包就伪装成了一个节点对另一个节点发起的通信传输,所以他的数据包可以经过路由器来转发.虽然性能有些下降,但是可以支持更多地节点(需要保证两个处于不同网段的节点可以通过路由器通信).calico还有另一种模式不需要IPIP的进一步封包,但是同样用到了对端主机IP地址配置为"下一跳"的IP地址的方式.
calico使用了一个**BGP(Border Gateway Protocol,边界网关协议)**来维护各个节点的路由信息.
BGP会在每个节点运行一个小程序,他们将各自的路由表信息传输给其他节点的程序.其他节点的程序接收到以后对其进行分析,然后添加到自己的节点的路由表中.
每个节点的BGP程序和其他的组成集群互相同步各自的路由表信息
这种模式下,容器的数据包通过Veth Pair设备直接来到宿主机,不需要进一步的封装直接匹配主机的路由表,主机路由表中会直接被BGP添加一条对端宿主机的容器网段,网关则是对端主机的IP地址,发送设备就是主机物理网卡的一条路由,类似如下:
10.244.1.0 10.88.10.182 255.255.255.0 UG 0 0 0 ens192
这样,数据包直接匹配此条路由,将对方主机的网卡当成一个"路由器"(下一跳地址),到达对方主机后继续匹配对端主机的路由表,calico会将当前主机的每个容器IP及对应的Veth Pair设备名称添加到路由表,类似如下:
10.244.235.130 0.0.0.0 255.255.255.255 UH 0 0 0 calif1f8fa46c64
这样,数据包直接匹配此条路由转发进pod中.
如图,如果要从 10.10.0.2主机访问172.17.0.2主机是可以访问过去的.因为他发出这个包的时候通过掩码运算发现对端主机和自己不是同一个网段,所以要将这个包发给网关.
而数据包来到网关后,路由器拆包拿到三层IP包,网关的路由表里172.17.0.2 Route2
表示这个网段的包要发往另一个路由器Route2
,所以这个包就被route1转发到了route2,同时route2的路由表中记录了这个IP所对应的端口.直接通过此端口转发给对应主机.
但是反过来,172.17.0.2
要去访问10.10.0.2
,就完全行不通了.因为他的网关中没有另一个局域网任何信息.所以as1这个局域网访问as2局域网内主机是没问题的,而反过来则完全不通.而这种将两个网段连接在一起的路由器,或者一个路由器的列表中有另一个路由器的子网,就称它为"边界网关".
在上面calico的网络中,就是把每个节点都当做了一个"边界网关".他们一起组成了一个大的网络,每个边界网关通过BGP协议来互相同步路由.但是节点数量越多,需要同步的路由信息也就越多,所以calico还有一个
Route Reflector
模式,这种模式下calico会单独抽出几个节点负责跟所有边界网关建立联系并且同步路由信息.其他的节点只需要和这几个同步信息就够了.
参考文章:
《深入剖析Kubernetes》-张磊