Kubernetes网络模型和典型实现总结-阿里云开发者社区

开发者社区> 云原生> 正文

Kubernetes网络模型和典型实现总结

简介: 前文[Kubernetes中的ClusterIP、NodePort、LoadBalancer、Ingress服务访问方式比较](https://www.atatech.org/articles/156810)中总结了服务接入访问的主要方式,以及它们之间隐含关系。有了这些概念基础后,K8S应用开发和服务部署就容易很多了,但Under the hood服务访问究竟是如何实现的呢?这篇内容就Kubern

前文Kubernetes中的ClusterIP、NodePort、LoadBalancer、Ingress服务访问方式比较中总结了服务接入访问的主要方式,以及它们之间隐含关系。有了这些概念基础后,K8S应用开发和服务部署就容易很多了,但Under the hood服务访问究竟是如何实现的呢?这篇内容就Kubernetes的网络模型和典型的容器网络实现,特别是阿里云自己的容器网络插件(Terway)的实现做了一个较详细的总结。

Pod之间Container-to-Container networking

Linux networking namespace为进程通讯提供了一个逻辑网络栈,包括network devices、routes、firewall rules。Network namespace(NS)管理实际是为其中的所有进程提供了一个独立的逻辑网络Stack。

缺省情况下,Linux将每个进程挂载在Root NS下,这些进程通过eth0通往外面的世界。
image.png

在Pod世界里所有其中的容器共享一个NS,这些容器都有相同的IP和Port空间,通过localhost访问也是互通的。Shared storage也是可以访问的,通过SharedVolume挂载到容器中。如下一个NS per pod图例:
image.png

同Node中Pod-to-Pod networking

先看同一个Node下Pod之间的networking如何实现?答案是通过Virtual Ethernet Device (or veth pair)的两块Virtual interfaces,每块veth挂载在一个NS上,来实现跨NS的连接。比如,一块挂在Root NS(host)上,另一块挂在Pod NS上,好比一根网线把两个在不同网络空间的traffic连接起来了,如图:
image.png

有了veth pair这条网线,Pods网络可以连通到Root NS了,但在Root NS上如何实现对来自不同Pod的packet通讯呢?答案是通过Linux Ethernet Bridge,一个虚拟的Layer2网络设备来实现不同network segments之间的Ethernet packet switching。不得不提这个old-school协议:ARP,实现了MAC地址到IP地址的发现协议。Bridge广播ethframe到所有连接的设备(除发送者外),收到ARP回复后将packet forward到对应veth设备上。如图:
image.png

跨Node之间Pod-to-Pod networking

进入这部分之前,先提及K8S在其(Pod)networking设计上的3个fundamental requirements,任何networking部分的实现都必须遵循这三个需求。

  • 在不使用NAT下,所有Pods都能和其它任何Pods通讯
  • 在不使用NAT下,所有Nodes都能和所有Pods通讯
  • Pod所看到自己的IP和其它Pods看到它的IP一定是相同的

简要来看,K8S网络模型要求Pod IP在整个网络中都能通达。具体实现方案有三方面:

  • Layer2(Switching)Solution
  • Layer3(Routing)Solution,如,Calico, Terway
  • Overlay Solution,如Flannel

这部分下文介绍,目前且认为Pod IP的网络通达性是确保的。

在Pod获得IP之前,kubelet为每个Node分配一个CIDR地址段(Classless inter-domain routing),每个Pod在其中获取唯一IP,CIDR 地址块的大小对应于每个Node的最大 Pod 数量(默认110个)。在Pod IP和跨Node网络层部署成功后,从源Pod1到目的Pod4的通讯如图:
image.png

Pod-to-Service networking

K8S Service管理服务的Pods状态,在Pod有变化下管理对应IP的变化,并管理对外提供服务的Virtual IP到Pod IPs路由访问,实现外部对服务Virtual IP的访问路由到Pod IP,以此屏蔽外部对服务后端的实现形态。所以在服务创建时,会对应生成一个Virtual IP(也即是Cluster IP),任何对该Virtual IP的访问将打散路由到服务所属的Pods上。

K8S的服务是如何实现对Virtual IP的访问负载均衡呢?答案是netfilter和iptables。netfilters是Linux built-in networking framework,为Linux提供网络包过滤、NAT和Port translation等丰富的自定义handler实现。iptables是运行在Linux user-space的规则管理系统,为netfilter框架提供丰富的包转发规则管理。

在K8S实现中kube-proxy(node deamon)通过watch apiserver来获得服务配置的变化,比如,服务的Virtual IP变化、Pod IP变化(ie, pod up/down)。iptables规则随之变化并将请求路由到服务对应的Pod上,Pod IP选取是随机的,这样看iptables起到了Pod负载均衡作用。在访问请求Return path上,iptables会做一次SNAT以替换IP header的Pod IP为服务Virtual IP,这样使得Client看起来请求仅在服务Virtual IP上进行通讯。

从K8S v1.11中IPVS(IP Virtual Server)被引入成为第二种集群内负载均衡方式。IPVS同样也是构建基于netfilter之上,在创建服务定义时可指定使用iptables或IPVS。IPVS是特定适合于服务负载均衡的解决方案,提供了非常丰富的均衡算法应用场景。

使用DNS

每个服务会设置一个DNS域名,kubelets为每个容器进行配置--cluster-dns=<dns-service-ip>,用以解析服务所对应DNS域名到对应的Cluster IP或Pod IP。1.12后CoreDNS成为缺省DNS方式。服务支持3种类型DNS records(A record、CNAME、SRV records)。其中常用的是A Records,比如,在cluster.local的DNS name下,A record格式如pod-ip-address.my-namespace.pod.cluster.local,其中Pod hostname和subdomain字段可以设置为标准的FQDN格式,比如,custom-host.custom-subdomain.my-namespace.svc.cluster.local

CNI

网络模型的实现上K8S在Pod资源管控(kubelet)和一套标准Container Networking Interface(CNI)定义和实现共同完成的。CNI在其中充当了"胶水"作用:各种容器网络实现能在一致的操作接口下由kubelet统一管控调度。另外,多个容器网络也能共存于一个集群内,为不同Pod的网络需求提供服务,都是在kubelet的统一管控下完成。

Overlay networking: Flannel

Flannel是CoreOS为K8S networking开发的解决方案,也是阿里云ACK产品支持的容器网络解决方案。Flannel的设计原理很简洁,在host网络之上创建另一个扁平网络(所谓的overlay),在其上地址空间中给每个pod容器设置一个IP地址,并用此实现路由和通讯。

主机内容器网络在docker bridge docker0上完成通讯,不再赘述。主机间通讯使用内核路由表和IP-over-UDP封装进行实现。容器IP包流经docker bridge会转发到flannel0网卡(TUN)设备上,进而流入到flanneld进程中。flanneld会对packet目标IP地址所属的网段信息查询其对应的下一跳主机IP,容器子网CIDR和所属主机IP的映射(key-value)保存在etcd中,flanneld查询得到packet目标IP所属的主机IP地址后,会将IP packet封装到一个UDP payload中并设置UDP packet目标地址为所得到的目标主机IP,最后在host网络中发送出UDP packet。到达目标主机后,UDP packet会流经flanneld并在这里解封出IP packet,再发送至flannel0docker0最后到达目标容器IP地址上。下图示意流程:

image.png

值得一提是,容器CIDR和下一跳主机IP的映射条目容量没有特殊限制。在阿里云ACK产品上该条目容量需要在VPC/vSwitch控制面中进行分发,考虑到整体性能因素,在数量上做了一定数量限制(缺省48个)。但在自建主机网络部署中,该数量限制就不会明显了,因为主机下一跳主机网络在一个大二层平面上。

Flannel新版本backend不建议采用UDP封装方式,因为traffic存在3次用户空间与内核空间的数据拷贝,(如下图)性能上存在比较大的损耗。新版本推荐用VxLan和云服务商版本的backends进行优化。

image.png

L3 networking: Calico

Calico是L3 Routing上非常流行容器网络架构方案。主要组件是Felix,BIRD和BGP Route Reflector。Felix和BIRD均是运行在Node上的deamon程序。

Felix完成网卡的管理和配置,包括Routes programming和ACLs。实现路由信息对Linux kernel FIB的操作和ACLs的管理操作。由于Felix功能完整性和运行独立性非常好,其功能作为Off-the-shelf被集成到阿里云Terway网络插件中,实现其网络策略功能。

BIRD(BGP client)完成内核路由FIB条目向集群网络侧分发,使其路由条目对所有网络节点中可见,并实现BGP路由协议功能。每一个BGP client会连接到网络中其它BGP client,这对规模较大的部署会是明显的瓶颈(due to the N^2 increase nature)。鉴于该限制引入了BGP Route Reflector组件,实现BGP clients路由分发的中心化汇聚。在集群网站中Reflector组件可以部署多个,完全能于部署规模大小来决定。Reflector组件仅仅执行路由信令和条目的分发,其中不涉及任何数据面流量。如下简要的Calico架构:

image.png

L3 networking:Terway

Terway是阿里云自研CNI插件,提供了阿里云VPC互通和方便对接阿里云产品的基础设施,没有overlay网络带来的性能损耗,同时提供了简单易用的backend功能。

Terway功能上可分为三部分:1. CNI插件,一个独立的binary运行程序;2. Backend Server(也称为daemon),程序以独立daemonSet方式运行在每个Node上;3. Network Policy,完全集成了Calico Felix实现。

CNI插件binary是通过daemonSet部署中initContainer安装到所有节点上,实现了ADDDELCHECK三个接口供kubelet调用。这里以一个Pod在创建过程中的网络setup步骤来说明:

  • 当一个Pod被调度到节点上时,kubelet监听到Pod创建在自己节点上,通过runtime(docker...)创建sandbox容器来打通所需namespace。
  • kubelet调用插件binary的cmdAdd接口,插件程序对接口参数设置检查后,向backendServer发起AllocIP调用。
  • backendServer程序的networkService根据Pod的网络类型进行相应的Pod IP申请,支持三种网络类型ENIMultiIPVPCENIVPCIP

    • ENIMultiIP是eni网卡带多IP类型,由networkService中的ResourceManager在自己的IP地址池中进行IP地址分配
    • VPCENI是为Pod创建和挂载一个eni,由networkService中的allocateENI向阿里云Openapi发起对所属ecs实例的eni创建、挂载,并获得对应eni IP地址
    • VPCIP是为Pod在VPC网络平面上分配一个IP地址,这是在插件程序中通过调用ipam接口从vpc管控面获取的IP地址
  • 在backendServer返回AllocIP调用(IP地址)结果后,插件调用不同网络类型的driver完成net namespace link setup。

综上图示:
image.png

为什么需要支持上述三种网络类型?根本上是由阿里云vpc网络基础设施所决定,同时覆盖阿里云主流应用对vpc网络资源的使用场景需求。另一方面是对标Amazon AWS的容器网络解决方案,在基于VPC和ENI的网络设施上能支持同等功能。

ENI多IP、VPC ENI和VPC IP的主要区别在于前两者下的Pod网段和VPC网段是相同的,而VPC IP的网段和节点的宿主机网段不同。这样使得在ENI网络环境下的IP路由完全在VPC的L2网络平面上进行,而VPC IP网络需要在VPC路由表中进行配置Pod网段的下一跳主机,和Flannel的路由模式类似。可以看出,ENI网络能带来更灵活的路由选择和更好的路由性能。如下两个截图反映其不同路由特点:

VPC ENI网络:
image.png

VPC IP网络:
image.png

ENI多IP(1个主IP/多个辅助IP)网络下有2种路由模式:veth策略路由ipvlan。两者本质区别在于使用不同的路由模式,前者使用veth pair的策略路由,后者使用ipvlan网络路由。策略路由需要在节点上配置策略路由条目来保证辅助IP的流量经过它所属的弹性网卡。ipvlan实现了一个网卡虚拟出多个子网卡和不同的IP地址,eni将其辅助IP绑定到这些虚拟出来的子网卡上形成一个与vpc平面打通的L3网络。这种模式使ENI多IP的网络结构比较简单,性能相对veth策略路由网络也更好。两种网络模式切换通过配置即可完成(缺省是vethpair):

值得一提的是Terway还实现了ENI多IP地址资源池的管理和分配机制。networkService中的eniIPFactory为每个eni网卡创建一个goroutine,该eni网卡上的eniIP的分配释放都在这个goroutine中完成。在创建一个eniIP时扫描已经存在的eni网卡,如该eni还存在空闲的eniIP,该goroutine会通过ipResultChan返回给eniIPFactory一个分配的IP。如果所有的eni网卡的eniIP都分配完毕,会先创建一个新的eni网卡和对应的goroutine,首次创建eni网卡时无需做IP分配,直接返回eni网卡主IP即可。eniIP释放是逆向的,在eni网卡的最后一个eniIP释放时,整个eni网卡资源会释放掉。

另外,有一个startCheckIdleTickergoroutine会定期扫描地址池的MaxPoolSizeMinPoolSize水位,在低于和高出水位阀值时会对地址池eniIP资源进行进行创建和释放,使得地址池IP资源处于一个可控水位范围中。为了保证资源状态一致性,有一个startGarbageCollectionLoopgoroutine会定期扫描IP地址是否在用或过期状态,如检测到会进行资源GC操作。最后,Pod资源状态数据都持久化在本地的一个boltDB文件中/var/lib/cni/terway/pod.db,即使Pod已经在apiServer中删除,GetPod会从本地boltDB中读取副本数据。在Pod已经删除但副本还存在DB的情况下,GC goroutine检测到会执行清理。截图简述:

image.png

总结下,从这些backend功能可以看到Terway为阿里云vpc网络资源设施连通性做了很好的封装,让基于阿里云Kubernetes的应用开发和部署带来更加简便和高效的优点。

版权声明:如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developerteam@list.alibaba-inc.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
云原生
使用钉钉扫一扫加入圈子
+ 订阅

云原生时代,是开发者最好的时代

其他文章