4.3 Kubernetes网络组件之 FlannelFlannel是CoreOS维护的一个网络组件,Flannel为每个Pod提供全局唯一的IP,Flannel使用ETCD来存储Pod子网与Node IP之间的关系。flanneld守护进程在每台主机上运行,并负责维护ETCD信息和路由数据包。
其实k8s网络组件flannel和calico主要解决的问题是k8s节点之间容器网络的通信,flannel要保证每个pod的IP是唯一的,怎么保证是唯一的,大部分组件的做法是在每个Node上分配一个唯一的子网,node1是一个单独的子网,node2是一个单独的子网,可以理解是不同网段,不同vlan,所以每个节点都是一个子网,所以flannel会预先设置一个大的子网,然后在这个每个node上分配子网,这些信息都会由flannel存储到etcd中,并且每个子网绑定到node上都有关系记录的,然后方便下次进行二次的数据包传输,并且flannel在node上会启动一个守护进程并运行,守护进程主要维护的是本地的路由规则,和维护etcd中的信息。
1、Flannel 部署
https://github.com/coreos/flannel
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
部署好之后会以daemonset的形式在每个node上启动一个pod,来启动一个flannel的守护进程,主要负责本机路由表的设定和etcd中的数据,本地的子网上报到etcd中,所以守护进程是非常重要的
可以在flannel的配置文件去设定大的子网,还有属性模式
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
---
这个配置完之后会放到cni这个目录下,由于flannel是使用网桥的模式,实现的同节点数据包到达宿主机这个的通信,所以子网信息并没写到这个配置文件里,而是放到了这个 cat /var/run/flannel/subnet.env 下,这个通过ip a也能看到设备分配的ip,每个节点都会分配一个子网,网络接口设备为cni0,也就是一个node上可以分配255个小的子网
[root@k8s-node2 ~]# cat /var/run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true
还有一个cni的二进制文件, /opt/cni/bin,这个就是kubelet调用这个二进制接口为创建的每个pod创建网络信息,并且是从我们的配置的子网中去拿IP
配置的话修改的是就是预先设定它的子网,以及工作模式,另外就是这个网络不能与k8s本身的内网冲突,否则导致网络不通的状况
2、 Flannel工作模式及原理
Flannel支持多种数据转发方式:
UDP:最早支持的一种方式,由于性能最差,目前已经弃用。
VXLAN:Overlay Network方案,源数据包封装在另一种网络包里面进行路由转发和通信
这也是网络的虚拟化技术,也就是原来是有一个包数据包,有源IP和目的IP,但由于某些情况这个数据包到达不了目的地址上,这可能就会借助物理上的以太网网络进行封装一个数据包带上,然后通过这种物理网络传输到目的地址上,这是一种叠加式的网络,里面是有两种数据包的,这种也叫做隧道方案
Host-GW:Flannel通过在各个节点上的Agent进程,将容器网络的路由信息刷到主机的路由表上,这样一来所有的主机都有整个容器网络的路由数据了,这样它就知道这个数据包到达这个节点转发到这个机器上,也就是路由表之间转发的,这种也叫路由方案
VXLAN
使用kubeadm部署的话默认是支持的
kubeadm部署指定Pod网段
kubeadm init --pod-network-cidr=10.244.0.0/16
但是使用二进制部署就得去启动cni的支持,默认我ansible部署的k8s集群都是启动的
二进制部署指定
cat /opt/kubernetes/cfg/kube-controller-manager.conf
--allocate-node-cidrs=true \ 允许node自动分配cidr这个网络
--cluster-cidr=10.244.0.0/16 \ 指定pod网络的网段,这个网段要和flannel的网段对应上
另外也都要在每个node节点的kubelet的配置文件上进行对cni的支持
[root@k8s-node1 ~]# cat /opt/kubernetes/cfg/kubelet.conf
--network-plugin=cni \
这样的话就能以cni的标准来为k8s配置网络
kube-flannel.yml
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "vxlan"
}
}
在节点1上有个容器,与节点2上的容器进行通信,这两个是进行跨主机进行的通信,如果本机通信之间使用网桥使用二层的传输了,像原生的docker网就能解决了,最重要的是这两个节点的数据包传输
flannel保证每个node都是唯一的ip,它是在每个node上都分配一个子网
可以看到flannel是基于宿主机创建的,它会为每个node创建独立的子网,并为当前pod分配ip
[root@k8s-master1 ~]# kubectl get pod -n kube-system -o wide
kube-flannel-ds-amd64-4jjmm 1/1 Running 0 14d 10.4.7.11 k8s-master1 <none> <none>
kube-flannel-ds-amd64-9f9vq 1/1 Running 0 14d 10.4.7.21 k8s-node2 <none> <none>
kube-flannel-ds-amd64-gcf9s 1/1 Running 0 14d 10.4.7.12 k8s-node1 <none> <none>
为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。下图flannel.1的设备就是VXLAN所需的VTEP设备。示意图如下:
vxlan是怎么工作的?
vxlan是Linux上支持的一个隧道的技术,隧道也就是点到点,端到端的两个设备的通信,其实vxlan实现是有一个vtep的设备做数据包的封装与解封装,而现在已经封装到flannel.1这个进程里面了,也就是这个虚拟网卡包含了veth来去使用对这个vxlan进行封装和解封装。
现在是Node1节点上的pod 1是1.10,现在要与Node2节点上的pod 2的2.10进行通信,他们是不在一个网络的,当这个数据包发出去的时候,pod1 的容器的网卡eth0,先出这个网卡,然后会连接这个veth这个好比就是一个网线,etch0是一头,veth是一头,也就是veth是这个设备的另一头,
这个veth是在宿主机上,那么这个宿主机就拿到了这个容器的数据包,然后这个数据包到达这个网桥上面,这个网桥也好比一个二层的交换机,所有的容器都会加入到这个网桥里面,可以通过yum -y install bridge-utils看到veth的另一端是不是加入到cni的网桥中,这个网桥就是flannel创建的,并且这个网桥有独立的mac地址和IP都可以看到
[root@k8s-node2 ~]# brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.4a025e87aa87 no veth08925d5a
veth3591a36f
veth776a1e86
veth718beeac
veth81dadcbd
veth8a96f11c
veth8c90fdb6
veth8f350182
veth90818f0b
vetha471152b
这个就是当我们创建好pod的时候由flannel去分配并加入这个网桥中的,这个后面有个interfaces有这个接口,这个相当于交换机的接口,这正是宿主机上的虚拟网卡,如果本地的话,直接走这个网桥就能直接找到了,然后就可以发送一个ARP广播包进行封包传输了,cni0就相当于一个二层交换机,帮你扩散,找目的的mac进行响应,所以说同节点就可以直接走网桥这个,那么这个目的地址不在这个网桥里面,就像2.10,当前的node是不知道2.10上的pod在哪,那么它只能走路由表了,也就是它它不一定目的地址的时候就会走默认网关,所以flannel会在宿主机上生成很多路由表通过ip router可以看到
[root@k8s-node2 ~]# ip route
default via 10.4.7.1 dev eth0 proto static metric 100
10.4.7.0/24 dev eth0 proto kernel scope link src 10.4.7.21 metric /
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1 /
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink /
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink /
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
部署docker生成的路由表,这里的docker0网桥是没有用到,当部署flannel的时候是默认使用的是自己的网桥,这个的原理和flannel的是一样的,只不过flannel用的是自己做的,也是为了方便自己处理数据包
这里的路由表都记录下来了,它会找哪个是目的地址2.10pod2的IP地址,所以它会根据这个路由表,然后发送到flannel的这个设备上,这个flannel是采用vxlan的模式,vxlan需要veth的数据封装与解封装,所以flannel就把这个数据包交给vxlan,而vxlan是一个内核级的驱动程序,有它去封装这个包,因为vxlan本身是工作在二层的,它还需要目的的mac地址
那么就可以通过ip neigh show dev flannel.1去查看mac地址
[root@k8s-node2 ~]# ip neigh show dev flannel.1
10.244.2.0 lladdr ea:ca:d6:62:be:21 PERMANENT
10.244.1.0 lladdr 4e:e3:fa:5f:d2:34 PERMANENT
而flannel.1的vxlan实现是有一个vtep的设备做数据包的封装与解封装,因为它在2层进行封包,就要知道目的的mac地址,那么这个目的mac就由flannel去提供给vetp,flannel去存储对应下一跳的网关,那么这个网关肯定不是在本地,当我们拿到目的的mac地址之后你们就封装成一个完整的帧,那么封装好之后,对于宿主机没有太多的实际意义,因为这个数据包帧发不出去,要是按二层的走肯定到不了另外一个节点,因为在不同的子网里面,如果没有路由的介入肯定是通信不了的,接下来就需要linux内核的数据帧封装一个宿主机普通的数据帧,也就是udp封装一个普通的数据帧,也就是在这之上再加一层udp的包,这样做的目的能让数据包直接传输到目的容器的主机上。
vxlan是使用的udp协议,它会将原始的报文放在内部,而外部由udp封装的源IP与目的地址
[root@k8s-master1 ~]# bridge fdb show dev flannel.1
a6:a4:e5:5d:19:9b dst 10.4.7.21 self permanent
ea:ca:d6:62:be:21 dst 10.4.7.12 self permanent
可以看到,上面用的对方flannel.1的MAC地址对应宿主机IP,也就是UDP要发往的目的地。使用这个目的IP进行封装。
也就是这些flannel都是知道的,为什么说flannel维护这etcd的数据,守护本地的路由规则,其实etcd的数据要和flannel,把它当前的数据写到etcd中,由各个节点都存储一份,所以根据这个地址拿到了mac地址,然后这又是一个完整的包,由vxlan封装的udp的包,这个udp包里面就有两个IP包的存在,udp就直接能发送到node2的节点上,数据包已经传输过去了,因为宿主机之间是同网段的,到达31.63上之后,接收到udp的包之后,会进行拆分,解包会将原始的包拿出来,所以这里就有一个vxlan的标记,本身flannel是由vtep处理的,所以在封包的时候对着干包打了个标记,也就是vxlan header的标记,首先打上vxlan的头部,那么这就意味着这就是一个vxlan的数据包,并且加了一个VNI的编号,VNI是为了区分vxlan的点对点隧道,多个数据包也是分外多个编号,也是为了区分,而这个编号被flannel引用到了,所以这是内部的一个编号,确认这个数据包无误,然后交给flannel.1这个设备,它处理这个数据包,拿到了源IP和目的IP,而去判断,会发现这个是cni网桥的,所以它根据路由表放到了cni网桥,根据这个路由表拆分这个目的地址,正好这个目的地址匹配到了,所以它会将这个转发到cni网桥里,到cni就跟之前一样了,就相当于一个二层交换机,拿到这个数据包,它会进行一个ARP的广播,发现正在这个网桥里面,然后就进行数据包的转发了。
从此看来;vxlan使用重叠网络,进行封包解封包,性能就下降了很多
小结:
/ # ip route
default via 10.244.0.1 dev eth0
10.244.0.0/24 dev eth0 scope link src 10.244.0.45
10.244.0.0/16 via 10.244.0.1 dev eth0
ip route
default via 192.168.31.1 dev ens33 proto static 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.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
ip neigh show dev flannel.1
10.244.1.0 lladdr ca:2a:a4:59:b6:55 PERMANENT
10.244.2.0 lladdr d2:d0:1b:a7:a9:cd PERMANENT
封装到UDP包发出去:现在能直接发UDP包嘛?到目前为止,我们只知道另一端的flannel.1设备的MAC地址,却不知道对应的宿主机地址是什么。
flanneld进程也维护着一个叫做FDB的转发数据库,可以通过bridge fdb命令查看:
bridge fdb show dev flannel.1
d2:d0:1b:a7:a9:cd dst 192.168.31.61 self permanent
ca:2a:a4:59:b6:55 dst 192.168.31.63 self permanent
可以看到,上面用的对方flannel.1的MAC地址对应宿主机IP,也就是UDP要发往的目的地。使用这个目的IP进行封装。
Host-GW
host-gw模式相比vxlan简单了许多, 直接添加路由,将目的主机当做网关,直接路由原始封包。
切换成host-gw的模式,上面的转发还是一样,pod1容器的网卡先连接veth到宿主机上,然后到达cni0的网桥上,这个网桥就相当于一个二层的交换机,这个数据包到cni的网桥之后,也就是到达宿主机上,那么宿主机的网络协议栈会根据路由表决定转发到哪个网关上,因为它的目的IP地址不是同网段的,肯定走路由表,它会根据路由表判断目的地址是2.10,也就是来自这个数据的数据包转发到了它的下一跳,也就是网关,通过接口之间转发到31.63上了,也就是直接安照宿主机的网络,因为这个数据包是宿主机处理的,所以宿主机要想访问31.63,会进行重新封包,目的地址就是31.63,它判断了31.63下一跳的网关是同一子网,而且是二层的传输,二层的传输又需要获取到目的的mac地址,如果它本地不知道31.63的mac地址的话,它会发送一个ARP广播包,知道了对方的mac就进行封包,所以经过二层的传输到达31.63,31.63收到之后数据包之后,它又会去判断路由表了,然后进入cni的网桥,二层又转发到了容器里面。
最重要两条,host-gw是把每个节点都当成一个网关,它会加入其他节点并设成网关,当数据包到达这个节点的时候,就根据路由表之间发送到下一跳了,也就是节点IP,这个都是同网段的IP,直接通过2层之间把这个数据,转发到另一个节点上,另一个节点再根据另一条规则,根据目的的地址转发到cni网桥,cni网桥根据2层又转发到容器里面,一个是数据的流入,就是当数据包到达这个节点之后,然后发给谁,这是流入数据包,一个是数据包的流出,当从节点出来的数据包,应该转发到哪个node上,这些都是由flannel去维护的
这个的局限是每个node在2层都能通,否则下一跳转发不过去,但是它的性能要比vxlan的性能高很多,不需要封包解封包,这种接近原生,性能也是最好的
下面是示意图:
kube-flannel.yml
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
看名字就能看出hots-gw它把目的的主机当作网关,直接路由原始的封包
将vxlan切换成host-gw的模式,重建之后可以看到路由表发生变化,切换的时候也会对网络进行影响,一般是在夜深人静的时候去做
之前的路由表都是通过flannel.1去转发到设备上,也就是使用host-gw,flannel.1这个设备就不用了,所以就不会用vxlan进行去封包了
当你设置flannel使用host-gw模式,flanneld会在宿主机上创建节点的路由表:
ip route
default via 192.168.31.1 dev ens33 proto static metric 100
10.244.0.0/24 dev cni0 proto kernel scope link src 10.244.0.1
10.244.1.0/24 via 192.168.31.63 dev ens33
10.244.2.0/24 via 192.168.31.61 dev ens33
192.168.31.0/24 dev ens33 proto kernel scope link src 192.168.31.62 metric 100
目的 IP 地址属于 10.244.1.0/24 网段的 IP 包,应该经过本机的 eth0 设备发出去(即:dev eth0);并且,它下一跳地址是 192.168.31.63(即:via 192.168.31.63)。
一旦配置了下一跳地址,那么接下来,当 IP 包从网络层进入链路层封装成帧的时候,eth0 设备就会使用下一跳地址对应的 MAC 地址,作为该数据帧的目的 MAC 地址。
而 Node 2 的内核网络栈从二层数据帧里拿到 IP 包后,会“看到”这个 IP 包的目的 IP 地址是 10.244.1.20,即 container-2 的 IP 地址。这时候,根据 Node 2 上的路由表,该目的地址会匹配到第二条路由规则(也就是 10.244.1.0 对应的路由规则),从而进入 cni0 网桥,进而进入到 container-2 当中。
小结:如果想追求性能的话,二层可以通信,那么就可以选择host-gw,那么如果两个节点之间是不能通过二层通信,那么可能需要路由的转发,那么可能在不同的vlan中,那么使用vxlan是最好的,因为可以满足这样的一个需求。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。