Kubernetes 网络的四种场景分析.docx
本文介绍了KUbenIeteS网络的各种场景,包括容器之间、POd之I可、POd到Service外部到内部的这4种场景下,不同的通信模式。在设计KUbenleteS容器平台的时候,建议按照这些通信模式,根据具体的场景,逐一比对选择合适的解决方案。其中,特别需要注意的是外部到内部的访问。在实际的业务场景中,业务组件之间的关系十分复杂,特别是微服务概念的提出,应用部署的粒度更加细小和灵活。为了支持业务应用组件的通信联系,Kubernetes网络的设计主要致力于解决以下场景:(1)(2)(3)(4)紧密耦合的容器到容器之间的直接通信;抽象的Pod到Pod之间的通信;Pod到SerViCe之间的通信;集群外部与内部组件之间的通信。1 .容器到容器的通信在同一个POd内的容器(POd内的容器是不会跨宿主机的)共享同一个网络命名空间,共享同一个1.inUX协议栈。所以对于网络的各类操作,就和它们在同一台机器上一样,它们甚至可以用localhost地址访问彼此的端口。这么做的结果是简单、安全和高效,也能减少将已经存在的程序从物理机或者虚拟机移植到容器的难度。如下图中的阴影部分就是Node上运行着的一个Pod实例。容器1和容器2共享了一个网络的命名空间,共享一个命名空间的结果就是它们好像在一台机器上运行似的,它们打开的端口不会有冲突,可以直接用1.inux的本地IPC进行通信。它们之间互相访问只需要使用1。CalhoSt就可以。Nodel同一个POd容器1容器2共享网络空间ODOCkero网桥容器到容器间通信2 .Pod之间的通信每一个Pod都有一个真实的全局IP地址,同一个Node内的不同Pod之间可以直接采用对房Pod的IP地址通信,而不需要使用其他发现机制,例如DNS>Consul或者etcdoPod既有可能在同一个Node上运行,也有可能在不用的Node上运行,所以通信也分为两类:同一个Node内的POd之间的通信和不同NOde上的POd之间的通信。1)同一个NOde内的POd之间的通信如图,可以看出,Podl和Pod2都是通过Veth连接在同一个DockerO网桥上的,它们的IP地址Pl、IP2都是从DOCkero的网段上自动获取的,它们和网桥本身的IP3是同一个网段的。另外,在POd1、Pod2的1.inUX协议栈上,默认路由都是DoCkem的地址,也就是说所有非本地的网络数据,都会被默认发送到DoCkem网桥上,由DOCkem网桥直接中转,它们之间是可以直接通信的。同一个Node内的Pod关系2)不同Node上的Pod之间的通信Pod的地址是与DOCkerO在同一个网段内的,我们知道DockerO网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行,因此要想实现位于不同NOde上的POd容器之间的通信,就必须想办法通过主机的这个IP地址来进行寻址和通信。另外一方面,这些动态分配且藏在Dockert)之后的所谓“私有IP地址也是可以找到的。Kubernetes会记录所有正在运行Pod的IP分配信息,并将这些信息保存在etcd中(作为Service的Endpoint)o这些私有IP信息对于Pod到Pod的通信也是十分重要的,因为我们的网络模型要求Pod到Pod使用私有IP进行通信。之前提到,Kubemetes的网络对Pod的地址是平面的和直达的,所以这些POd的IP规划也很重要,不能有冲突。综上所述,想要支持不同Node上的Pod之间的通信,就要达到两个条件:(1)在整个Kubernetes集群中对Pod分配进行规划,不能有冲突;(2)找到一种办法,将Pod的IP和所在Node的IP关联起来,通过这个关联让POd可以互相访问。根据条件1的要求,我们需要在部署Kubernetes的时候,对DockerO的IP地址进行规划,保证每一个NOde上的DOCkero地址没有冲突。我们可以在规划后手工分配到每个Node上,或者做一个分配规则,由安装的程序自己去分配占用。例如Kubernetes的网络增强开源软件Flannel就能够管理资源池的分配。根据条件2的要求,Pod中的数据在发出时,需要有一个机制能够知道对方Pod的IP地址挂在哪个具体的Node上。也就是说要先找到Node对应宿主机的IP地址,将数据发送到这个宿主机的网卡上,然后在宿主机上将相应的数据转到具体的DockerO上。一旦数据到达宿主机Node,则哪个Node内部的DockerO便知道如何将数据发送到Podo具体情况,如下图所示。PodlI器2DOCkero网桥跨Node的Pod通信在图6中,IPl对应的是Podl,IP2对应的是Pod2oPodl在访问Pod2时,首先要将数据从源Node的eth发送出去,找到并到达Node2的eth。也就是说先要从IP3到IP4,之后才是1P4到IP2的送达。3 .Pod到SerViCe之间的通信为了支持集群的水平扩展、高可用,Kubernetes抽象出Service的概念。SerViCe是对一组POd的抽象,它会根据访问策略(1.B)来访问这组Pod。Kubernetes在创建服务时会为服务分配一个虚拟的IP地址,客户端通过访问这个虚拟的IP地址来访问服务,而服务则负责将请求转发到后端的Pod上。这个类似于反向代理,但是,和普通的反向代理有一些不同:首先它的IP地址是虚拟的,想从外面访问需要一些技巧;其次是它的部署和启停是Kubernetes统一自动管理的。Service在很多情况下只是一个概念,而真正将Service的作用落实的是背后的kube-proxy服务进程。在Kubernetes集群的每个Node上都会运行一个kube-proxy服务进程,这个进程可以看作Service的透明代理兼负载均衡器,其核心功能是将到某个Service的访问请求转发到后端的多个Pod实例上。对每一个TCP类型的KubernetesService,kube-proxy都会在本地Node上建立一个SoCketSerVer来负责接收请求,然后均匀发送到后端某个POd的端口上,这个过程默认采用RoundRobin负载均衡算法。Kube-proxy和后端Pod的通信方式与标准的Pod到Pod的通信方式完全相同。另外,Kubernetes也提供通过修改Service的service.spec.-sessionAffinity参数的值来实现会话保持特性的定向转发,如果设置的值为“ClientIP”,则将来自同一个ClientIP的请求都转发到同一个后端PodJto此外,SerViCe的ClUSterIP与NOdePOrt等概念是kube-proxy通过IPtabIeS和NAT转换实现的,kube-proxy在运行过程中动态创建与Service相关的Iptables规则,这些规则实现了CIUSterlP及NOdePOrt的请求流量重定向到kube-proxy进程上对应服务的代理端口的功能。由于Iptables机制针对的是本地的kube-proxy端口,所以如果Pod需要访问Service,则它所在的那个Node上必须运行kube-proxy,并且在每个Kubernetes的Node上都会运行kube-proxy组件。在Kubernetes集群内部,对ServiceClusterIP和Port的访问可以在任意Node上进行,这个因为每个Node上的kube-proxy针对该Service都设置了相同的转发规则。综上所述,由于kube-proxy的作用,在SerViCe的调用过程中客户端无需关心后端有几个Pod,中间过程的通信、负载均衡及故障恢复都是透明的,如下图所示。Service的负载均衡转发访问Service的请求,不论是用ClusterlP÷TargetPort的方式,还是用节点机IP÷NodePort的方式,都会被节点机的Iptables规则重定向到kube-proxy监听Service服务代理端口。Kube-proxy接收至IiService的访问请求后,会血何选择后端Pod?首先,目前kube-proxy的负载均衡只支持RoundRobin算法。该算法按照成员列表逐个选取成员,如果一轮循环完,便从头开始下一轮,如此循环往复。Kube-proxy的负载均衡器在RoundRobin算法的基础上还支持Session保持。如果Service在定义中指定了Session保持,则kube-proxy接收请求时会从本地内存中查找是否存在来自该请求IP的affinityState对最,如果存在该对象,且Session没有超时,则kube-proxy将请求转向该affinityState所指向的后端Pod。如果本地存在没有来自该请求IP的affinityState对象,记录请求的IP和指向的Endpointo后面的请求就会粘连到这个创建好的affinityState对象上,这就实现了客户端IP会话保持的功能。接下来我们深入分析kube-proxy的实现细节。kube-proxy进程为每个Service都建立了一个“服务代理对象'',服务代理对象是kube-proxy程序内部的一种数据结构,它包括一个用于监听此服务请求的SOCket-SerVer,SOCketSerVer的端口是随机选择的一个本地空闲端口。此外,kube-proxy内部也建立了一个“负载均衡器组件”,用来实现SocketServer上收到的连接到后端多个Pod连接之间的负载均衡和会话保持能力。kube-proxy通过查询和监听APIServer中Service与Endpoint的变化来实现其主要功能,包括为新创建的Service打开一个本地代理对象(代理对象是kube-proxy程序内部的一种数据结构,一个SerViCe端口是一个代理对象,包括一个血于监听的服务请求的SocketServer),接收请求,针对发生变化的Service列表,kube-proxy会逐个处理。下面是具体的处理流程:(D如果该SerViCe没有设置集群IP(ChlSterIP),则不做任何处理,否则,获取该Service的所有端口定义列表(SPee.ports域)(2)逐个读取服务端口定义列表中的端口信息,根据端口名称、SerViCe名称和Namespace判断本地是否已经存在对应的服务代理对象,如果不存在就新建,如果存在且Service端口被修改过,则先删除Iptables中和该Service相关的的规则,关闭服务代理对象,然后走新建流程,即为该SerViCe端口分配服务代理对象并为该Service创建相关的Iptables规则。(3)更新负载均衡器组件中对应Service的转发地址表,对于新建的Service,确定转发时的会话保持策略。(4)对于已经删除的SerViCe则进行清理。MasterKube-proxy与APIServer的交互过程4 .外部到内部的访问Pod作为基本的资源对象,除了会被集群内部的Pod访问,也会被外部使用。服务是对一组功能相同Pod的抽象,以它为单位对外提供服务是最合适的粒度。由于Service对象在ClusterIPRange池中分配到的IP只能在内部访问,所以其他Pod都可以无障碍地访问到它。但如果这个SerViCe作为前端服务,准备为集群外的客户端提供服务,就需要外部能够看到它。Kubernetes支持两种对外服务的Service的Type定义:NodePort和1.oadBalancero(1)NodePort在定义Service时指定spec.type=NodePort,并指定spec.ports.nodePort的值,系统就会在Kubernetes集群中的每个Node上打开一个主机工,的真实端口号。这样,能够访问Node的客户端就能通过这个端口号访问到内部的Service了。(2) 1.oadBaIancer如果云服务商支持外接负载均衡器,则可以通过spec.type=1.oadBalancer定义SerViCe,同时需要指定负载均衡器的IP地址。使用这种类型需要指定SerViCe的NodePort和ClusterIPo对于这个Service的访问请求将会通过1.oadBalancer转发到后端Pod上去,负载分发的实现方式依赖于云服务商提供的1.oadBalancer的实现机制。(3)外部访问内部Service原理我们从集群外部访问集群内部,最终都是落在具体的Pod上。通过NodePort的方式就是将kube-proxy开放出去,利用Iptables为服务的NodePort设置规则,将对Service的访问转到kube-proxy上,这样kube-proxy就可以使用和内部Pod访问服务一样的方式来访问后端的一组Pod了。这种辍式就是利用kube-proxy作为负载均衡器,处理外部到服务进一步到Pod的访问。而更常用的是外部均衡器模式。通常的实现是使用一个外部的负载均衡器,这些均衡器面向集群内的所有节点。当网络流量发送到1.oadBaIanCer地址时,它会识别出这是某个服务的一部分,然后路由到合适的后端Pod。所以从外面访问内部的Pod资源,就有了很多种不同的组合。外面没有负载均衡器,直接访问内部的POd外面没有负载均衡器,直接通过访问内部的负载均衡器来访问Pod外面有负载均衡器,通过外部负载均衡器直接访问内部的Pod外面有负载均衡器,通过访问内部的负载均衡器来访问内部的POd第一种情况的场景十分少见,只是在特殊的时候才需要。我们在实际的生产项目中需要逐一访问启动的Pod,给它们发送一个刷新指令。只有这种情况下才使用这种方式。这需要开发额外的程序,读取SerViCe下的EndPOint列表,逐一和这些Pod进行通信。通常要避免这种通信方式,例如可以采取每个Pod从集中的数据源拉命令的方式,而不是采取推命令给它的方式来避免。因为具体到每个Pod的启停本来就是动态的,如果依赖了具体的Pod们就相当于绕开了KUberneteS的SerViCe机制,虽然能够实现,但是不理想。第二种情况就是NodePort的方式,外部的应用直接访问Service的NodePort,并通过Kube-proxy这个负载均衡器访问内部的Podo第三种情况是1.OadBalanCer模式,因为外部的1.oadBalancer是具备Kubernetes知识的负载均衡器,它会去监听Service的创建,从而知晓后端的Pod启停变化,所以它有能力和后端的Pod进行通信。但是这里有个问题需要注意,那就是这个负载均衡器需要有办法直接和Pod进行通信。也就是说要求这个外部的负载均衡器使用和Pod到Pod一样的通信机制。第四种情况也很少使用,因为需要经历两级的负载均衡设备,而且网络的调用被两次随机负载均衡后,更难跟踪了。在实际生产环境中出了问题排错时,很难跟踪网络数据的流动过程。(4)外部硬件负载均衡器模式在很多实际的生产环境中,由于是在私有云环境中部署KUberneteS集群,所以传统的负载均衡器都对SerViCe无感知。实际上我们只需要解决两个问题,就可以将它变成Service可感知的负载均衡器,这也是实际系统中理想的外部访问Kubernetes集群内部的模式。通过写一个程序来监听Service的变化,将变化按照负载均衡器的通信接口,作为规则写入负载均衡器。给负载均衡器提供直接访问Pod的通信手段。如下图,说明了这个过程。外部访问自定义外部负载均衡器访问SerViCe这里提供了一个ServiceAgent来实现Service变化的感知。该Agent能够直接从etcd中或者通过接口调用APIServer来监控Service及Endpoint的变化,并将变化写入外部的硬件负载均衡器中。同时,每台Node上都运行着有路由发现协议的软件,该软件负责将这个Node上所有的地址通过路由发现协议组播给网络内的其他主机,当然也包含硬件负载均衡器。这样硬件负载均衡器就能知道每个Pod实例的IP地址是在哪台NOde上了。通过上述两个步骤,就建立起一个基于硬件的外部可感知SerViCe的负载均衡器。具体的案例,可以参见第五章的实践部分。5.总结重点介绍了KUberneteS网络的各种场景,包括容器之间、Pod之间、POd到Service>外部到内部的这4种场景下,不同的通信模式。在设计KUbemeteS容器平台的时候,建议按照这些通信模式,根据具体的场景,逐一比对选择合适的解决方案。其中,需要注意的是外部到内部的访问,既可以通过NodePOrt,也可以通过1.oadBalancer的方式亦或是Ingress模式,需要按照具体的场景来分析。NodePort服务是暴露服务的最原始方式,会在所有节点上打开特定的端口,并且发送到此端口的任何流量都将转发到该服务。这种方法有很多缺点:每个端口只能有一个服务;默认只能使用端口3000032767;如果节点IP地址发生更改,则会带来问题。由于这些原因,不建议在生产中使用这种方法。如果服务可用性不是特别关注,或者特别关注成本,则这个方案比较合适。1.OadBaIanCer是服务暴露的标准方式,将会启动一个网络负载均衡器,提供一个将所有流量转发到服务的IP地址。如果直接暴露一个服务,这是默认的方法。指定的端口上所有的流量将被转发到该服务,没有过滤、路由等。这就意味着可以发送几乎任何类型流量,如HTTP、TCP>UDP、WebsocketgRPC或其他。这个方式最大的缺点是,使用1.OadBaIanCer公开的每项服务都将获得自己的IP地址,并且必须为每个服务使用一个1.OadBalanCer,这将会付出比较大的代价。Ingress实际上不是一种服务。相反,它位于多个服务之前,充当集群中的“智能路由器”或入口点。默认的Ingress控制器将会启动一个HTTP(三)负载均衡器。这将可以执行基于路径和基于子域名的路由到后端服务。Ingress可能是暴露服务最强大的方式了,但也可能是最复杂的。如果希望在相同的IP地址下暴露多个服务,并且这些服务都使用相同的1.7协议,则IngreSS是最有用的。附参考:kubernetes架构及应用场景kubernetes,简称K8s,是用8代替中间8个字符“ubernete”而成的缩写,是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(POWerfU1),Kubernetes提供了应用部署,规划,更新,维护的一种机制。k8s在企业中的应用场景首先我们了解一下k8s的三个基本特点:可移植:支持公有云,私有云,混合云,多重云(multi-cloud)可扩展:模块化,插件化,可挂载,可组合自动化:自动部署,自动重启,自动复制,自动伸缩/扩展自动化运维平台对于中小型企业,为了降本增效,使用k8s来构建一套自动化运维平台,提供了应用部署,规划,更新,维护的一种机制。对于大型互联网公司更要使用容器化部署。现在服务器越来越多,不可能都人工部署,需要使用自动化的运维平台来监控服务,来实现自动服务化的部署、运维。充分利用服务器资源假设现在有一个开发量为200个的请求,服务器配置为2cpus4G静态请求:150(访问CDN,Nginx,cache等)动态请求:50(访问数据库,需要把数据读入内存)估算服务器资源(只考虑内存,不考虑程序响应时间RT,不考虑CPU切换时间)假设一个静态请求进程占用2M,一个动态请求进程占用10M,则这200个请求并发占用:150x2M+50xl0M=800M内存可以支持的QPS(批发量,每秒查询率)为:200×4=800(因为800Mx4<4G)因此如果要充分利用服务器资源,需要达到QPS=800,此时占用内存3.2G(剩下0.8G给OS等)实际上:800QPS无法达到,还要考虑RT、CPU切换、内存等因素,那就保守把QPS=300,但这时没能充分利用服务器的资源。更何况当下服务器配置可不止2cpus4G容器化解决方案,在服务器部署多个容器,容器当中运行着我们部署的各种服务服务无缝迁移在开发环境开发,然后拿到测试环境去测试,但往往一上线就会有bug,因为应用的运行、配置、管理、所有生存周期将与当前操作系统绑定,所以生产环境的不一致就可能导致错误。使用容器化解决方案,每个应用可以被打包成一个容器镜像(红色圈起来表示把服务部署在容器中),使用容器可以在开发或测试的阶段,为应用创建容器镜像,这些镜像能够完全脱离环境,每个应用不需要与其余的应用堆栈组合,也不依赖于生产环境基础结构,这使得从研发到测试、生产能提供一致环境。使用kubernetes来管理这些容器,便能够实现服务的无缝迁移。开发环境服务部署模式变迁&服务部署变化问题的思考服务部署模式是如何变迁的物理机:传统的应用部署方式是通过插件或脚本来安装应用。这样做的缺点是应用的运行、配置、管理、所有生存周期将与当前操作系统绑定,这样做并不利于应用的升级更新/回滚等操作。虚拟化(虚拟机):当然上面的问题可以通过创建虚拟机的方式来实现某些功能,但是虚拟机本身就很占用资源,并不利于可移植性。(就是把服务部署在虚拟机中,达到分隔物理资源的作用充分利用服务器资源)容器部署:每个容器之间互相隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。相对于虚拟机,容器能快速部署,由于容器与底层设施、机器文件系统解耦的,所以它能在不同云、不同版本操作系统间进行迁移。而且更轻量级、运行效率更快。服务部署模式变化,带来了哪些问题前提条件:SOA架构,微服务架构模式下,服务拆分越来越多,部署维护的服务越来越多,该如何管理?虚拟机服务部署方式(通过openstack软件提供可视化的方式来管理虚拟机)容器化部署模式(通过k8s软件管理容器,其实容器也可以看成一个虚拟机,只不过更轻量级)容器化部署问题:如何对服务横向扩展?容器宕机怎么办?如何恢复?重新发布版本如何更新且更新后不影响业务?如何监控容器?容器如何调度创建?数据安全性如何保证?使用k8s管理容器,以上问题都能够完美的解决云架构&云原生云和k8s的关系云:使用容器构建的一套服务集群网络,云是由很多的容器构成。k8s:用来管理云中的容器云架构iaas:基础设施即服务用户角度:租用(购买或分配权限)云主机,用户不用考虑网络、DNS.存储和硬件环境等方面的问题。运营商角度:提供网络、DNS、存储等这样的服务就叫做基础设置服务paas:平台即服务在平台上提供了很多服务,如MySQ1.服务、Redis服务、MQ服务、Elasticsecirch服务等等saas:软件即服务钉钉、财务管理等等,一些软件维护工作都是由运行商来做,用户只管体验软件提供的服务就行了。serverless:server服务,less无无服务不需要服务器站在用户角度考虑问题,用户只需要使用云服务器即可。在云服务器上的所有的基础环境、软件环境都不需要考虑和维护,非常方便。未来开发的趋势都是severless,企业都构建了自己的私有云或者公有云环境。使用k8s构建非常方便。云原生为了让应用程序(项目,服务软件)都运行在云上的解决方案,这样方案叫做云原生,有以下特点:容器化:所有的服务都必须部署在容器中。微服务:Web服务架构是微服务架构CI/CD:可持续交互和可持续部署DevOps:开发和运维密不可分kubernetes架构原理k8s的历史k8s是由Google公司用go语言开发的。google在全球有相当多的服务器,当然需要一个管理软件。Google内部本身就有一个叫borg的系统云平台管理工具,已经使用了十几年。后来参照borg系统架构开发了k8s,主要用它来编排、管理容器,为容器化的应用提供部署运行、资源调度、服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性。k8s的架构k8s集群(Cluster)一个master对应一群node节点master节点apiserver:相当于k8s的网关,所有的指令请求都必须经过apiserverscheduler:调度器,使用调度算法,把请求资源调度到某个node节点controller:控制器,维护k8s资源对象(CRUD:添加、删除、更新、修改)etcd:存储资源对象(可以服务注册、发现等等)node节点docker:运行容器的基础环境,容器引擎kubelet:每个node节点都有一份kubelet,在node节点上的资源操作指令由kuberlet来执行,scheduler把请求交给api,然后apisever再把信息指令数据存储在etcd里,于是kuberlet会扫描etcd并获取指令请求,然后去执行kube-proxy:代理服务,负载均衡fluentd:日志收集服务pod:k8s管理的基本单元(最小单元),pod内部是容器。k8s不直接管理容器,而是管理pod回顾架构特点k8s是用来管理容器的,但是不直接操作容器,最小的操作单元是POd(间接管理容器)一个master对应一群node节点。master节点不存储容器,只负责调度,网关,控制器,资源对象存储等容器存储在node节点的pod内部pod内部可以有一个或多个容器kubelet负责本地的pod的维护,CRUDkube-proxy负责负载均衡,在多个pod间负载均衡。