kubernetes之flannel
kubernetes网络通信
- 容器间的通信 pod内的容器通信(lo)
- Pod之间的通信 pod IP <-----> pod IP
- Pod与Service之间的通信 podIP <-----> ClusterIP
- Service与集群外部的通信 ClusterIP <-----> 集群外部
CNI插件:
- flannel
- calico
- canel
- kube-route
解决方案:
- 虚拟网桥
- 多路复用 MacVLAN
- 硬件交换 SR-IOV
Flannel
Flannel本身是一个框架,真正提供网络功能是他的后端实现。目前支持三种后端实现:
- VXLAN
- host-gw
- UDP
UDP
UDP模式是Flannel早期支持的一种方式,却也是性能最差的一种方式。所以现在基本都弃用了。但是这种模式是最直接最容易理解的一种方式。
Node1的容器访问Node2的容器,Node1容器访问的目的地址不是在容器的路由规则里,所以会走默认路由到达宿主机的docker0 网桥上,从而出现在宿主机上,这个时候这个IP的下一步取决于这台宿主机的路由规则,这时候这条会使用flannel0的设备上,flannel是一个TUN设备,在Linux中,TUN设备是一种工作在三层的虚拟网络设备,TUN设备的功能是在操作系统内核和应用程序之间传递IP包。
当操作系统将一个IP包发送给flannel0设备后,flannel0 就会把IP包交给创建这个设备的应用程序,也就是flannel进行。这就是从内核态(Linux操作系统)到用户态(Flannel进程)的流动方向。
反之,如果flannel进程向flannel0设备发送一个IP包,那么这个IP包就会出现在宿主机网络栈,然后根据宿主机路由表进行下一步处理,这是用户态向内核态流动方向。
所以,当IP包从容器经过docker0出现在宿主机上,然后又根据宿主机路由表进入flannel0设备后,宿主机上的flannel进程就会收到这个IP包。然后,flannel看到了这个IP包的目的地址,就可以把他发送到Node2宿主机上。
flannel是如何知道目的IP地址对应的容器是运行在Node2上
源于flannel里的一个重要概念子网(Subnet)。事实上,在由Flannel管理的容器网络里,一台宿主机上的所有容器都属于该宿主机被分配的一个“子网”,所以flannel会根据目的IP地址找到对应的子网,从而在etcd里找到对应的宿主机。只要node1 和node2 互通,flannel进程一定会到达node2。
UDP模式的缺点
相比两台宿主机之间的直接通信,基于flannel UDP模式的容器通信网络多个一个额外的步骤,那就是flannel的处理过程,这个过程由于使用到了flannel0这个TUN设备,仅仅是发送IP包的时候,就需要三次用户态和内核态的数据拷贝。如图:
- 用户态容器进程发起IP包进过docker0网桥进入内核态
- IP包根据路由表进入TUN(flannel0)设备,从而回到用户态的flanneld进程
- flanneld 进行UDP封包之后重新进入内核态,将UDP包通过宿主机的eth0发出去
VXLAN
VXLAN全称虚拟可扩展局域网,是Linux内核本身就支持的一种网络虚拟化技术。所以说,VXLAN可以完全在内核态实现上述封装和解封工作,从而通过前面相似的“隧道机制”,构建出覆盖网络。
VXLAN覆盖网络的设计思想:在现有的三层网络之上,“覆盖”一层虚拟的,由内核VXLAN模块负责维护的二层网络,使得连接在这个VXLAN二层网络上的“主机”(可以是虚拟机也可以是容器)之间,可以像一个局域网里那样自由通信,当然,实际上,这些“主机”可能分布在不同的宿主机上,甚至可以分布在不同的物理机房里。
而为了能够在二层网络上打通“隧道”,VXLAN会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备叫VTEP,既:虚拟隧道端点。而这个VTEP设备的作用,其实跟前面的flanneld进程非常相似。只不过,它进行封装和解封装的对象是二层数据帧。而且这个工作的执行流程全部是在内核里完成的(因为VXLAN本身就是内核模块)。如图:
从图里看每个宿主机都有一个flannel1的设备,就是VXLAN所需的VTEP设备,它既有IP地址也有MAC地址。现在我们是container1 访问 container2,相对UDP比较来看,当container1发出请求后,这个目的的地址是10.244.1.3的IP包,会先出现在docker0网桥,然后被路由到本机flanner1设备上处理,也就是说,来到了“隧道”的出口。既目的宿主机的VTEP设备。而这个设备信息正是每台宿主机上的flanneld进程负责维护的。
当所有Node启动后,我们可以在Node1 上可以看到多个flannel1 网卡的路由信息,是因为flanneld启动后创建的。
$ route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface ... 10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1 ....
从上图看到10.244.1.0就是Node2的VTEP设备(就是flannel1 设备)的IP地址,而这些VTEP设备之间就需要想办法组成一个虚拟的二层网络,既:通过二层数据帧进行通信,而Node1上的VTEP设备收到原始IP包后,就要想办法把原始IP包加一个目的MAC地址,封装成二层数据帧,然后发送给目的VTEP设备。这里需要解决一个问题目的VTEP设备的MAC地址是什么?
根据路由表信息我们知道了目的VTEP设备的IP地址,而根据三层IP地址查询二层MAC地址正是ARP表的功能。而这里用ARP表的记录,也就是flanneld进程在Node2节点启动时,自动添加到Node1上的。如下:
$ ip neigh show dev flannel.1 10.244.1.0 lladdr b2:ba:aa:a5:10:1a PERMANENT
有了这个MAC地址linux内核就可以开始二层封包了,上面提到的MAC地址,对宿主机的二层网络没有任何意义,所以上述封装的数据帧不能在宿主机的二层网络里传输,为了方便概述,我们把上述数据帧称为内部数据帧。所以Linux内核还要把内部数据帧进一步封装成宿主机网络的一个普通数据帧,好让他载着内部数据帧,通过eth0网卡进行传输。这次封装我们称为外部数据帧,为了实现这个搭便车的机制,Linux内核在封装内部数据帧前面,加上特殊的VXLAN头,用来表示这个乘客实际上是VXLAN使用的数据帧。而这个VXLAN头里有一个重要的标志VNI,它是识别某个数据帧是不是应该归属自己处理的标志。而flannel中,VNI的值是1,这也是为什么宿主机的VTEP设备都叫做flannel1的原因。这个时候linux内核会把这数据帧封装一个UDP包里发出去。虽然node1的flannel1知道node2的flannel2的MAC地址,但是不知道node2MAC的地址,也就是UDP该发往那台主机,实际上flannel1还要扮演一个网桥的角色,在二层网络进行UDP转发,而在Linux内核里面,网桥设备进行转发的依据来自FDB的转发数据库。这个flannel网桥对应的FDB信息,就是flannel进程维护的,他的内容如下:
$ bridge fdb show flannel.1 | grep b2:ba:aa:a5:10:1a b2:ba:aa:a5:10:1a dev flannel.1 dst 172.16.138.41 self permanent
我们可以看到发往的IP地址是172.16.138.41的主机,显然这台主机就是 Node2,UDP要转发的目的也找到了。接下来就是宿主机网络封包的过程了。
Flannel 在kubernetes上的应用
其实上述过程也是kubernetes的主要处理方法,只不过kubernetes是通过一个叫CNI接口维护一个单独网桥来代替docker0,这个网桥CNI网桥,默认叫做cni0。如图:
过程也是和VXLAN是一样的。需要注意的是,CNI网桥只是接管所有CNI插件负责的,既只是kubernetes创建的容器。
kubernetes之所以要设置这样一个与docker0网桥功能一样的CNI网桥,主要原因有两点:
- kubernetes项目并没有使用Docker的网络模型,所以它不希望,也不具备配置docker0的能力。
- 还与kubernetes如何配置Pod,也就是infra容器Network Namespace相关。
我们在部署kubernetes的时候,有一个步骤是安装kubernetes-cni包,他的目的就是宿主机上安装CNI插件所需要的基础可执行文件。
ls /opt/cni/bin/ bridge dhcp flannel host-local ipvlan loopback macvlan portmap ptp sample tuning vlan
CNI的基础可执行文件,按照功能分三类:
- Main插件 他是用来创建具体网络设备的二进制文件。例如:birdge(网桥设备)、ipvlan、lookback(lo 设备)、ptp(Veth Pair设备)、macvlan、vlan
- IPAM插件 他是负责分配IP地址的二进制文件。例如:dhcp,这个文件会向dhcp服务器发起请求;host-local,则会使用预先配置的IP地址来进行分配。
- CNI社区维护的内置CNI插件。例如 flannel,就是专门为Flannel项目提供的CNI插件。
从二进制文件来看,如果实现一个kubernetes用的容器网络方案,其实需要两部分工作,以Flannel下项目为例。
- 首先实现这个网络本身,其实就是flanneld进程里主要逻辑,例如:创建和配置flannel.1设备,配置宿主机路由、配置ARP和FDB表。
- 实现该网络方案对应的CNI插件,就是配置infra容器里的网络栈,并把他连接在CNI网桥上。
接下来进行第一步,在宿主机上安装flannel。而这个过程中,flanneld启动后会在每台宿主机上生成他对应的CNI配置文件(就是configmap),从而告诉kubernetes,这个集群要使用Flannel作为容器网络方案。
CNI配置文件内容如下:
cat /etc/cni/net.d/10-flannel.conflist { "name": "cbr0", "plugins": [ { "type": "flannel", "delegate": { "hairpinMode": true, "isDefaultGateway": true } }, { "type": "portmap", "capabilities": { "portMappings": true } } ] }
需要注意的是Kubernetes 目前不支持多个CNI混用,如果在/etc/cni/net.d/里放置多个CNI配置文件的话,只会加载字母排序第一个配置文件。但另一方面,CNI允许你在一个CNI配置文件里,通过plugins字段,定义多个插件协作。假如我们定义了flannel和protmap这两个插件,这个时候dockershim会把这个CNI配置文件加载起来,并把列表第一个插件,就是flannel插件设置为默认插件,然后执行过程中flannel和portmap会按照定义顺序被调用,从而一次完成“配置容器网络”和“配置端口映射”。
CNI插件工作原理
当kubelet组件需要创建pod时,他一个创建的一定是infra容器,所以这一步,dockershim 就会先调用Docker API创建并启动infra容器,紧着执行一个叫做SetUpPod的方法,这个方法的作用是:为CNI插件准备构建参数,然后调用CNI插件为infra容器配置网络。这里要调用CNI插件是/opt/cni/bin/flannel;而调用它所需的参数,分两部分:
第一部分,是由dockershim设置的一组CNI环境变量。其中,最重要的环境变量参数叫做:CNI_COMMAND。他的取值只有两种ADD和DEL。这个ADD和DEL操作,就是CNI插件唯一实现的两种方法。
- ADD 把容器添加CNI网络里
- DEL 把容器从CNI网络里移除
在网桥类型的CNI里,这两个操作说明把容器以“Veth Pair”方式插在CNI网桥上,或者从网桥上拔掉。
第二部分,是dockershim从CNI配置文件里加载到的默认插件的配置信息。这个配置信息在CNI中被叫作Network Configuration,dockershim会把Network Configuration 通过JSON数据的方式,通过标准输入的方式传递给Flannel CNI插件。
将Flannel插件的后端改成VXLAN+DriectRouting
以下操作不适合在生产环境操作,kubernetes的网络环境需要根据未来的业务环境选择。所以要集群配置前做好规划。
VXLAN+DriectRouting是首先采用直接路由的方式,如果不可以就使用VXLAN的方式。
Flannel的默认配置是VXLAN,但是我们可以配置多个插件协作,接下来我们就使用VXLAN+DriectRouting这两个插件协作。
下载Flannel配置文件
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/k8s-manifests/kube-flannel-legacy.yml
修改kube-flannel-legacy.yml,在net-conf.json里的增加一个type。
$ vim kube-flannel-legacy.yml ....... net-conf.json: | { "Network": "10.244.0.0/16", "Backend": { "Type": "vxlan", "Type": "DriectRouting" } } .....
删除flannel插件,重新生成配置文件。
$ kubectl delete ds kube-flannel-ds -n kube-system
$ kubectl apply -f kube-flannel-legacy.yml
这样就修改完成了,切忌生成不可以直接操作。