Kubernetes的网络通信方案分析
#作者:程宏斌
Kubernetes的网络通信问题
容器间通信: 即同一个Pod内多个容器间通信,通常使用loopback来实现。
Pod间通信: K8s要求,Pod和Pod之间通信必须使用Pod-IP 直接访问另一个Pod-IP
Pod与Service通信: 即PodIP去访问ClusterIP,当然,clusterIP实际上是IPVS 或 iptables规则的虚拟IP,是没有TCP/IP协议栈支持的。但不影响Pod访问它.
Service与集群外部Client的通信,即K8s中Pod提供的服务必须能被互联网上的用户所访问到
目前比较常用的时flannel和calico,flannel的功能比较简单,不具备复杂网络的配置能力,calico是比较出色的网络管理插件,单具备复杂网络配置能力的同时,往往意味着本身的配置比较复杂,所以相对而言,比较小而简单的集群使用flannel,考虑到日后扩容,未来网络可能需要加入更多设备,配置更多策略,则使用calico更好
Flannel 常见采取 UDP Overlay 方案,VxLAN 性能比 TUN 强一点,一个是内核态一个是用户态
Calico 是一个纯三层的方案,不需要 Overlay,基于 Etcd 维护网络准确性,也基于 Iptables 增加了策略配置
1、flannel
flannel的工作方式有3种
1.1 VxLAN
而VxLAN有两种工作方式:
a. VxLAN: 这是原生的VxLAN,即直接封装VxLAN首部,UDP首部,IP,MAC首部这种的。
b. DirectRouting: 这种是混合自适应的方式, 即它会自动判断,若当前是相同二层网络 (即:不垮路由器,二层广播可直达),则直接使用Host-GW方式工作,若发现目标是需要跨网段 (即:跨路由器)则自动转变为使用VxLAN的方式。
1.2 host-GW
-
这种方式是宿主机内Pod通过虚拟网桥互联,然后将宿主机的物理网卡作为网关,当需要访问其它Node上的Pod时,只需要将报文发给宿主机的物理网卡,由宿主机通过查询本地路由表,来做路由转发,实现跨主机的Pod通信,这种模式带来的问题时,
-
当k8s集群非常大时,会导致宿主机上的路由表变得非常巨大,而且这种方式,要求所有Node必须在同一个二层网络中,否则将无法转发路由,这也很容易理解,因为如果Node之间是跨路由的,那中间的路由器就必须知道Pod网络的存在,它才能实现路由转发,但实际上,宿主机是无法将Pod网络通告给中间的路由器,因此它也就无法转发理由。
1.3 UDP
这种方式性能最差的方式,这源于早期flannel刚出现时,Linux内核还不支持VxLAN,即没有VxLAN核心模块,因此flannel采用了这种方式,来实现隧道封装,其效率可想而知,因此也给很多人一种印象,flannel的性能很差,其实说的是这种工作模式,若flannel工作在host-GW模式下,其效率是非常高的,因为几乎没有网络开销。
2、Calico
Calico是一种非常复杂的网络组件,它需要自己的etcd数据库集群来存储自己通过BGP协议获取的路由等各种所需要持久保存的网络数据信息,因此在部署Calico时,早期是需要单独为Calico部署etcd集群的,因为在k8s中,访问etcd集群只有APIServer可以对etcd进行读写,其它所有组件都必须通过APIServer作为入口,将请求发给APIServer,由APIServer来从etcd获取必要信息来返回给请求者,但Caclico需要自己写,因此就有两种部署Calico网络插件的方式,一种是部署两套etcd,另一种就是Calico不直接写,而是通过APIServer做为代理,来存储自己需要存储的数据。通常第二种使用的较多,这样可降低系统复杂度。
当然由于Calico本身很复杂,但由于很多k8s系统可能存在的问题是,早期由于各种原因使用了flannel来作为网络插件,但后期发现需要使用网络策略的需求,怎么办?
3、目前比较成熟的解决方案
flannel + Calico, 即使用flannel来提供简单的网络管理功能,而使用Calico提供的网络策略功能

Egress:是出站的流量,即自己是源,远端为服务端,因此我自己的源IP可确定,但端口不可预知, 目标的端口和IP都是确定的,因此to 和 ports都是指目标的IP和端口。 Ingress:是入站的流量,即自己为目标,而远端是客户端,因此要做控制,就只能对自己的端口 和 客户端的地址 做控制。 我们通过Ingress 和 Egress定义的网络策略是对一个Pod生效 还是 对一组Pod生效? 这个就要通过podSelector来实现了。 而且在定义网络策略时,可以很灵活,如:入站都拒绝,仅允许出站的; 或 仅允许指定入站的,出站都允许等等。 另外,在定义网络策略时,也可定义 在同一名称空间中的Pod都可以自由通信,但跨名称空间就都拒绝。
网络策略的生效顺序: 越具体的规则越靠前,越靠前,越优先匹配
基于隧道
1、隧道方案最具普适性,在任何网络环境下都可以正常工作,这与它的原理密不可分。
2、最常见的隧道方案是flannel vxlan模式*,以及calico的ipip模式*,其核心原理包含了2个部分。
分配网段每台宿主机上都有网络插件的agent进程,它们连接到etcd集中式存储,从虚拟IP池中申请一个IP段占位己有,宿主机上每个容器则从IP段中分配得到1个虚拟IP。
封装/解封
1)、当不同宿主机上的容器互相访问时,数据包的源IP和目标IP都是容器IP。
2)、数据包经过宿主机的agent进程进行封装后,新数据包的源IP和目标IP则变成了两端宿主机的物理IP。
3)、数据包送到目标宿主机后,经过agent解封后得到原始数据包,并将数据包送入容器中处理,这就给两端容器营造了一种互通的感觉。
4)、因为物理IP属于3层网络,可以在互联网中经过中间路由设备互相送达,所以隧道方案对宿主机之间的网络环境没有特殊要求,因此隧道方案具备普适性。
优势/劣势
优势就是对物理网络环境没有特殊要求,只要宿主机IP层可以路由互通即可。
劣势就是性能差,这需要从以下方面看: 封包和解包耗费CPU性能; 额外的封装导致带宽浪费,大约有30%左右的带宽损耗;
flannel vxlan和calico ipip模式都是隧道方案,但是calico的封装协议IPIP的header更小,所以性能比flannel vxlan要好一点点。
基于路由
1、路由方案性能最好,原因是该方案不需要封包和解包,所以没有隧道方案的劣势,网络性能很好。
2、常见的路由方案包括了flannel的host-gw模式*,以及calico的bgp模式。
下面以calico bpg模式为例,分析基于路由的方案原理,其包含了3个部分。
- 分配网段
每台宿主机也有agent,会从etcd中的虚拟IP池分配到一个IP子网段,宿主机上每个容器则从该IP段中分配得到1个虚拟IP。 - 本地路由
1)、假设我们在宿主机A上新建了一个容器,则该容器分配了一个虚拟IP,我们假设它是值是k。
2)、agent会在本机配置一条路由规则,即:如果数据包的目标地址等于k,那么把数据包送到容器的虚拟网卡上。
3)、另外一台宿主机B上的一个容器,其IP是m,向k容器发数据包,则数据包的目标地址是k,原地址是m。
4)、既然路由方案是不使用隧道封包为物理IP在网络中流通的,那么该数据包又该如何送达到虚拟IP k呢? - 广播路由
1)、路由方案会采用如下的手段,搞定m到k的虚拟IP互通问题。
2)、即宿主机A会通过某种方式(比如BGP广播协议)把自己的虚拟IP网段广播给宿主机B。
3)、在宿主机B收到广播后,会给本机配置一条路由规则:如果数据包的目标地址属于宿主机A的虚拟IP网段,则把该数据包发给宿主机A的物理IP。
4)、这条路由规则相当于为宿主机A的虚拟IP网段配置了转发网关,而这个网关就是宿主机A的物理IP。这就要求,宿主机B和宿主机A在2层网络是互通的,也就是它们在一个交换机下面,可以基于MAC地址直接互通。
5)、一旦该数据包被送往宿主机A的物理IP,则宿主机A就可以应用刚才讲过的”本地路由“规则了,即:数据包的目标IP是k,直接送给对应容器的虚拟网卡。
整个过程中从m发往k的数据包采用的都是虚拟容器IP,没有经过任何封装和解封,而仅仅是通过宿主机B收到的广播路由+宿主机A的本地路由,就实现了在2层网络互通环境下的高效通讯。
优势/劣势
优势就是没有封包和解包过程,完全基于两端宿主机的路由表进行转发。
劣势包含2方面: 要求宿主机处于同一个2层网络下,也就是连在一台交换机上,这样才能基于MAC通讯,而不需要在IP上动封包/解包的手脚。
路由表膨胀导致性能降低,因为宿主机上每个容器需要在本机添加一条路由规则,而不同宿主机之间需要广播自己的网段路由规则。
网络通讯
1、同一个Pod内的多个容器之间:localhost
2、各Pod之间的通讯:Overlay Network
3、Pod 与 Service 之间的通讯:各节点的Iptables 规则
Flannel 是 CoreOS 团队针对 Kubernetes 设计的一个网络规划服务,简单来说,它的功能是让集群中的不通节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址。而且它还能在这些IP地址质检建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动的传递到目标容器内
ETCD之 Flannel 提供说明:
> 存储管理Flannel可分配的IP地址段资源
> 监控 ETCD 中每个 Pod 的实际地址,并在内存中建立维护 Pod 节点路由表
不同情况下网络通信方式
同一个 Pod 内部通讯:同一个 Pod 共享同一个网络命名空间,共享同一个 Linux 协议栈 (localhost)
Pod1 至 Pod2
> Pod1 与 Pod2 不在同一台主机,Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问。
> Pod1 与 Pod2 在同一台机器,由 Docker0 网桥直接转发请求至 Pod2,不需要经过Flannel
Pod 至 Service 的网络:目前基于性能考虑,全部为iptables 维护和转发
Pod到外网:Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主机网卡完成路由选择后,iptables 执行 Masquerade,把源IP 更改为宿主机的IP,然后向外网服务器发送请求
外网访问 Pod: Service
K8s集群中有三种网络:
节点网络、Pod网络、Service 网络 //这三种网络中,只有节点网络是物理网卡
更多推荐



所有评论(0)