在Kubernetes中网络中,主要包含两种IP,分别是Pod IP和Cluster IP。 Pod IP是实际存在于网卡之上(如VETH的虚拟网卡),而Cluster IP则是一个虚拟的IP地址,该虚拟机IP由kube-proxy进行维护,kube-proxy目前提供了两种实现方式,包括默认的ip tables实现以及在K8S 1.8之后开始支持的ipvs实现。
对于Kubernetes其网络而言,其实现需要确保集群中每个Pod都有一个唯一的IP地址,并且Pod之间可以直接进行跨主机通讯。在符合这一原则的前提下,Kubernetes允许通过插件的方式,集成不同的容器集群网络实现。其中最常用的应该是Flannel。Flannel是由CoreOS团队针对KUbernetes设计的一个Overlay Network实现,通过隧道协议(udp,vxlan)封装容器之间的通讯报文,实现集群间网络通讯。
Flannel默认使用UDP作为集群间通讯实现,如上图所示,Flannel通过ETCD管理整个集群中所有节点与子网的映射关系,如上图所示,Flannel分别为节点A和B划分了两个子网:10.1.15.0/16和10.1.20.0/16。同时通过修改docker启动参数,确保Docker启动的容器能够特定的网段中如10.1.15.1/24。
同一Pod实例容器间通信:对于Pod而言,其可以包含1~n个容器实例,这些容器实例共享Pod的存储以及网络资源,Pod直接可以直接通过127.0.0.1进行通讯。其通过Linux的Network Namespace为这组容器实现了一个隔离网络。
相同主机上Pod间通信:对于Pod而言,每一个Pod实例都有一个独立的Pod IP,该IP是挂载到虚拟网卡(VETH)上,并且bridge到docker0的网卡上。以节点A为例,其节点上运行的Pod均在10.1.15.1/24的网段中,其属于相同网络,因此直接通过docker0进行通信。
对于跨节点间的Pod通信:以节点A和节点B通讯而言,由于不同节点docker0网卡的网段并不相同,因此flannel通过主机路由表的方式,将对节点B POD IP网段地址的访问路由到flannel0的网卡上。 而flannel0网卡的背后运行的则是flannel在每个节点上运行的进程flanneld。由于flannel通过ETCD维护了节点间所有网络的路由关系,原本容器将的数据报文,被flanneld封装成UDP协议,发送到了目标节点的flanneld进程,再对udp报文进行解包,后将数据发送到docker0,从而实现跨主机的Pod通讯。
解析阿里云Flannel实现
上述简单解释了Flannel默认的UDP实现过程,可以看出,由于存在大量的数据报文封装和解析的过程,其必然会导致Pod间网络性能的下降。除了默认的UDP实现以外,Flannel还支持基于vxlan的方式,vxlan是一个在已有3层物理网络上构建的2层逻辑网络的协议。
这里我们以通过阿里云Kubernetes服务创建的集群为例,解释跨主机将Pod是如何通讯的。
这里创建了两个Pod实例,其分别运行在节点cn-beijing.i-2ze52j61t5p9z4n60c9m和cn-beijing.i-2ze52j61t5p9z4n60c9l上:
1 | $ kubectl get pods -o wide --selector app=nginx |
以172.16.2.229访问172.16.2.125为例,我们进入到172.16.2.229所在节点的flannel容器:
1 | $ k -n kube-system get pods -o wide --selector app=flannel |
首先,在上文中说了,Flannel通过ETCD会统一为每个节点分配相应的网段:
1 | $ k -n kube-system exec -it kube-flannel-ds-hjlb4 -c kube-flannel cat /run/flannel/subnet.env |
如上所示,Flannel建立了一个172.16.0.0/16的大网,而节点cn-beijing.i-2ze52j61t5p9z4n60c9m则分配了一个172.16.2.1/25的小网。所以该节点上所有Pod的IP地址一定是在该网段中(172.16.2.1 ~ 172.16.2.126)。
出口方向
从nginx-56f766d96f-2dl9t(172.16.2.229)所在节点的flannel实例kube-flannel-ds-86d5j查看网卡信息:
1 | $ k -n kube-system exec -it kube-flannel-ds-86d5j -c kube-flannel ifconfig |
其中veth00c70308是每个Pod实例虚拟网卡,并且通过网桥的方式链接到cni0网卡:
1 | $ k -n kube-system exec -it kube-flannel-ds-86d5j -c kube-flannel brctl show |
从172.16.2.229向172.16.2.125,从源容器发出后通过网桥全部发送到cni0的网卡上。查看系统路由表,遗憾的是在系统中找不到任何从cni0网卡向后转发的规则:
1 | $ k -n kube-system exec -it kube-flannel-ds-86d5j -c kube-flannel route |
这部分的路由转发在阿里云环境中是通过VPC路由表实现,如下所示:
从172.16.2.225发送到172.16.2.125的请求,匹配的路由记录为172.16.2.0/25。流量会被转发到 主机i-2ze52j61t5p9z4n60c9l,即Pod实例nginx2-6f65c584d-nglvf(172.16.2.125)所在的主机。
入口方向
出口方向,从源容器nginx-56f766d96f-2dl9t(172.16.2.229)发送到nginx2-6f4bb4799-t84rh(172.16.2.125)的流量已经正确的发送到目标节点i-2ze52j61t5p9z4n60c9l。
查看接收流量主机的路由规则:
1 | $ k -n kube-system exec -it kube-flannel-ds-hjlb4 -c kube-flannel -- route -n |
根据主机路由表规则,发送到172.16.2.125的请求会落到路由表:
1 | 172.16.2.0 0.0.0.0 255.255.255.128 U 0 0 0 cni0 |
从而请求进入到cni0网卡,并发送到相应的容器。
相比于UDP的实现方式,在vxlan中,不需要依赖于额外的flannel接口,通过(ali-vpc backend)来代替封装IP规则以获得最佳的性能表现。