当前位置: 首页 > 科技观察

Kubernetes网络模型的来龙去脉

时间:2023-03-12 03:26:24 科技观察

容器网络起源于Docker网络。Docker采用了比较简单的网络模型,即内部网桥加内部保留IP。这样设计的好处是容器的网络与外界解耦,不需要占用宿主机的IP,也不需要占用宿主机的资源。它是完全虚拟的。其设计初衷是:当需要访问外界时,会使用SNAT借用Node的IP来访问外部服务。例如,当一个容器需要对外提供服务时,它使用DNAT技术,即在Node上开放一个端口,然后通过iptable或者其他一些机制将流量导入到容器进程中,从而达到目的。这种模型的问题是外部网络无法区分哪个是容器的网络和流量,哪个是宿主机的网络和流量。比如我们要做高可用,172.16.1.1和172.16.1.2就是两个功能相同的容器。这时候我们就需要将两者绑定成一个Group对外提供服务。这时候我们发现,从外面看来,这两者并没有什么共同点,它们的IP都是从宿主机的端口借来的,所以很难把两者放在一起。在此基础上,Kubernetes提出了这样一种机制:即每个Pod,也就是一小群功能聚合体,应该有自己的“身份证”,或者ID。在TCP协议栈上,这个ID就是IP。这个IP是真正属于Pod的,不管用什么方式,外界都必须给它。访问这个PodIP才是真正访问它的服务,中间拒绝任何修改。比如你用10.1.1.1的IP访问10.1.2.1的Pod,在10.1.2.1上发现其实是借用了宿主机的IP,而不是源IP,这是不允许的。Pod将需要共享此IP,从而解决一些功能内聚的容器如何成为部署原子的问题。剩下的问题是我们的部署方式。Kubernetes实际上对如何实现这个模型没有任何限制。可以使用underlay网络控制外部路由器进行导流;如果要解耦,使用overlay网络,在底层网络之上加一个overlay网络,也是可以的。的。总之,只要达到模型要求的目的即可。Pod具体是如何上网的呢?容器网络的网络包是如何传输的?我们可以从以下两个维度来看:协议层网络拓扑1.协议层与TCP协议栈的概念相同。层层叠叠,层层叠叠。从右向左发送数据包时,即先有应用数据,然后发送到TCP或UDP的四层协议,继续向下传输,加IP头,加MAC头即可送出。接收数据包时,按照相反的顺序,先剥离MAC头,再剥离IP头,最后通过协议号在端口上找到要接收的进程。2.网络拓扑一个容器包要解决的问题分为两步:第一步,如何从容器空间(c1)跳转到宿主空间(下);第二步,如何到达远端。我个人的理解是,容器网络解决方案可以从三个层面来考虑:接入、流控、通道。第一个是access,就是说我们的容器和宿主机之间是用什么机制连接的,比如经典的方式比如Veth+bridge,Veth+pair,其他的方式比如使用高版本内核的新机制(如mac/IPvlan等),向主机空间发送数据包;二是流量控制,即我的方案是否支持NetworkPolicy,如果支持,如何实现。这里需要注意的是,我们的实现必须是在数据路径必须经过的节点。如果数据路径不经过Hook点,则不起作用;三是通道,即如何完成两台主机之间的数据包传输。我们有很多种方式,比如路由,可以分为BGP路由或者直接路由。还有各种隧道技术等等。最终我们达到的目标是,一个容器中的包经过容器,经过接入层到达宿主机,再经过宿主机的流控模块(如果有的话)到达通道发送它给同行。3、最简单的一种路由方案:Flannel-host-gw该方案采用每个Node独享网段,每个Subnet会绑定一个Node,网关也设置在本地,或者直接在内部端口上的cni0桥的。这种方案的优点是易于管理,缺点是Pod不能跨Node迁移。也就是说这个IP和网段属于这个Node后,就不能再迁移到其他的Node上了。这个方案的本质在于路由表的设置,如上图所示。接下来我就为大家一一讲解。第一个很简单,我们在设置网卡的时候就加上这一行。就是指定我的默认路由经过哪个IP,默认设备是什么;第二个是对子网的规则反馈。也就是说我的网段是10.244.0.0,掩码是24位,它的网关地址在网桥上,就是10.244.0.1。意思是这个网段的每一个包都发往这个网桥的IP;三是对同行的反馈。如果你的网段是10.244.1.0(上图右边的子网),我们就用它的Host的网卡上的IP(10.168.0.3)作为网关。也就是说,如果数据包发送到10.244.1.0网段,请使用10.168.0.3作为网关。我们来看看这个数据包是如何运行的。假设容器(10.244.0.2)要向10.244.1.3发送一个数据包,那么它在本地生成一个TCP或者UDP数据包后,依次填写对端IP地址,本地以太网的MAC地址作为源MAC和对等MAC。一般来说,本地会设置一条默认路由,默认路由会使用cni0上的IP作为自己的默认网关,对端MAC就是这个网关的MAC地址。然后可以将数据包发送到网桥。如果网段在网桥上,可以通过MAC层的交换来解决。在这个例子中,我们的IP不属于这个网段,所以网桥会把它上送到宿主机的协议栈去处理。主机堆栈恰好找到了对等方的MAC地址。以10.168.0.3为网关,经过本地ARP探测,得到10.168.0.3的MAC地址。也就是说,通过协议栈的层层组装,我们达到了目的,将右图中主机网卡的MAC地址填充到Dst-MAC中,从而将数据包从主机的eth0到对端的eth0。所以你可以发现这里有一个隐含的限制。上图中的MAC地址填写后,一定能到达对端。但是如果两台主机之间没有二层连接,一些网关,使用一些复杂的路由的话,MAC是不能直达的,这个方案是不能用的。当数据包到达对端的MAC地址时,发现数据包确实是发给自己的,但IP不是自己的,于是启动Forward流程,将数据包送入协议栈,然后走路由再次,它恰好找到了10.244。1.0/24需要发送到10.244.1.1的网关,这样才能到达cni0桥,它会找到10.244.1.3对应的MAC地址,然后通过桥接机制,将数据包到达对端容器。可以看到,整个过程一直是二层三层,发送的时候变成二层,然后路由,就是大环带小环。这是一个相对简单的解决方案。如果中间要用到隧道,可能会有vxlan隧道设备。此时并没有填写直连路由,而是填写了对端的隧道号。Service是如何工作的呢?Service其实就是一种LoadBalance机制。我们认为是客户端的一种负载均衡,也就是说从VIP到RIP的转换已经在客户端完成了,不需要再去中心化到一个组件比如NGINX或者ELB去做决定。它的实现是这样的:首先一组Pod组成一组功能后端,然后在前端定义一个虚拟IP作为访问入口。一般来说,由于IP不好记,我们也会提供一个DNS域名。Client首先访问域名获取虚拟IP,然后转换为真实IP。Kube-proxy是整个机制的核心,隐藏了很多复杂性。它的工作机制是通过apiserver监听Pod/Service的变化(比如是否添加了Service或Pod),反馈给本地规则或者用户态进程。LVS版本的服务让我们实际制作一个LVS版本的服务。LVS是一种专门用于负载均衡的内核机制。它工作在第四层,它的性能会比用iptables实现的更好。假设我们是一个Kube-proxy,拿到一个Service配置,如下图:它有一个ClusterIP,这个IP上的端口是9376,需要反馈给容器的端口是80,以及还有三个可以工作的Pod,它们的IP分别是10.1.2.3、10.1.14.5、10.1.3.8。它要做的是:第一步,绑定VIP到本地(欺骗内核);首先,你需要让内核相信自己有这样一个虚拟IP,这是由LVS的工作机制决定的,因为它工作在第四层,不关心IP转发,只有认为这个IP是自己的自己的,它会分裂到TCP或UDP层。第一步,我们将IP设置到内核中,告诉内核它确实有这样一个IP。有很多方法可以实现它。这里我们采用直接将local添加到iproute的方法,也可以将IP添加到dummydevices。第二步,为这个虚拟IP创建一个IPVS虚拟服务器;告诉它我需要对这个IP进行负载均衡分配,后面的参数是一些分配策略等等。虚拟服务器的IP其实就是我们的ClusterIP。第三步,为此IPVS服务创建对应的真实服务器。我们需要为虚拟服务器配置对应的真实服务器,也就是真正提供服务的后端是什么。比如我们刚才看到有3个Pod,那么我们把这三个的IP分配给虚拟服务器,就可以完全一一对应了。Kube-proxy的工作原理与此类似。只是它还需要监听一些Pod的变化。比如Pod的数量变成5个,那么rules就应该变成5个。如果有一个Pod死亡或者被杀死,那么相应的减去一个。或者如果整个服务被撤销,所有这些规则将被删除。所以它实际上做了一些管理工作。什么?负载均衡也分为内部和外部。最后介绍一下Service的种类,可以分为以下4类。1.ClusterIP集群内部的一个虚拟IP。该IP将绑定到一堆服务的GroupPod。这也是默认的服务方法。它的缺点是这种方式只能在Node内部使用,也就是集群内部。2.NodePort用于集群外的调用。将Service托管在Node的静态端口上,端口号与Service一一对应,集群外的用户可以通过:方法调用Service。3、LoadBalancer对云厂商的扩展接口。阿里云、亚马逊等云厂商都有成熟的LB机制,可以通过大型集群来实现。为了不浪费这个能力,云厂商可以通过这个接口进行扩展。首先,它会自动创建两种机制,NodePort和ClusterIP。云供应商可以选择将LB直接附加到这两种机制,或者两者都不使用。也可以直接把Pod的RIP附加到云厂商的ELB后端。的。4、ExternalName放弃内部机制,依赖外部设施。例如,某用户特别强。他觉得我们提供的东西没用,只好自己去实现。这时候一个Service就会和一个域名一对一,整个负载均衡的工作都是对外实现的。下图就是一个例子。灵活应用ClusterIP、NodePort等多种服务方式,并结合云厂商的ELB,成为一个非常灵活、极具扩展性、真正可用于生产的系统。首先,我们使用ClusterIP作为功能Pod的服务入口。可以看到,如果有三种Pod,则有三个ServiceClusterIP作为它们的服务入口。这些方法都是在客户端,如何在服务器端做一些控制呢?首先是一些IngressPod(Ingress是K8s后来新增的服务,本质上是一堆同构的Pod),然后将这些Pod组织起来,暴露给一个NodePortIP,K8s的工作就结束了。任何在端口23456上访问Pod的用户都将访问Ingress服务。背后有一个Controller,会管理ServiceIP和Ingress的后端。最后会转入ClusterIP,再转入我们的功能Pod。如前所述,我们要连接到云供应商的ELB。我们可以让ELB在所有集群节点上监听23456端口。只要23456端口上有服务,就认为有Ingress实例在运行。整个流量通过外部域名的解析分发到达云厂商的ELB。ELB负载均衡,通过NodePort到达Ingress,Ingress通过ClusterIP调用后台真实的Pod。这个系统看起来更丰富、更健壮。任何一个环节都没有单点问题,任何一个环节都有管理和反馈。本文总结了本课到目前为止的主要内容。这里给大家简单总结一下:大家应该从根本上了解Kubernetes网络模型的演进,理解PerPodPerIP的用意;根据模型,第4层向底层发送数据包的过程就是接收数据包的过程。容器网络也是如此。Ingress等机制处于更高层级(service<->port),方便集群对外服务的部署。通过Arealusabledeploymentexample,希望大家结合Ingress+ClusterIP+PodIP等概念,了解社区对于引入新机制、新资源对象的思考。

猜你喜欢