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

ServiceMesh这么火,背后的技术细节你了解多少?

时间:2023-03-13 18:30:22 科技观察

ServiceMesh在Kubernetes成为容器编排标准之后开始流行起来,但是很多文章讲概念多于技术细节,所以特地写了一篇文章来分析ServiceMesh背后的技术细节。ServiceMesh是Kubernetes支持微服务能力的最后一块。Kubernetes是个奇妙的东西,组件复杂,概念复杂。在实现微服务之前,你可能会想为什么Kubernetes设计起来这么复杂,但是一旦你实现了微服务,你就会发现Kubernetes中的所有概念都是有用的。在我们微服务设计的十点中,我们会发现Kubernetes可以有相应的组件和概念来提供相应的支持。最后一块拼图是服务发现、熔断、限流和降级。众所周知,Kubernetes的服务发现是通过Service来实现的,服务之间的转发是通过kube-proxy下发的iptables规则来实现的。这样只能实现最基本的服务发现和转发能力,无法满足高并发应用下的高级服务特性。SpringCloud和Dubbo存在一定的差距,于是诞生了ServiceMesh。它期望从应用层到基础设施层实现熔断、限流和降级等功能,让Kubernetes和容器完全接管微服务。以Istio为例描述ServiceMesh中的关键技术点ServiceMesh和SDN一样,将服务请求的转发分为控制平面和数据平面,所以分析也是先从数据平面分析转发能力,然后分析控制平面。如何发出命令。今天的文章主要关注两个组件,Envoy和Pilot。一切都从Envoy开始。我们先看看Envoy本身不集成ServiceMesh能做什么?Envoy是一个用C++编写的高性能Proxy转发器,那么Envoy是如何转发请求的呢?需要设置一些规则,然后根据这些规则进行转发。规则可以是静态的,放在配置文件中,并在启动时加载。如果要重新加载,一般需要重启,但是Envoy支持热加载和热重启,一定程度上缓解了这个问题。当然,最好的办法就是把规则设置成动态的,统一维护在一个地方。这个统一的地方在Envoy的眼里叫做DiscoveryService。过一会再去这里获取配置,修改转发策略。不管是静态的还是动态的,配置中经常会配置上图中的四个东西:Listener,也就是Envoy既然是代理,就是专门做转发的,它要监听一个端口,访问请求,以及那么就可以基于Policy转发,监听的端口称为Listener。Route,有时多个Cluster功能相似,只是版本号不同,可以通过Route规则选择将请求路由到某个版本号,即某个Cluster。Cluster,一个Cluster是多个行为完全相同的Endpoint,即如果有3个容器在运行,就会有3个IP和端口。但是,部署了相同的三个服务。它们形成一个集群。从Cluster到Endpoint的过程称为负载均衡,可以轮询。Endpoint是目标的IP地址和端口,也就是Proxy最终转发请求的地方。这四种静态配置示例如下:如图所示,Listener配置为监听本地127.0.0.1的10000接口,Route配置为转发某个URL前缀的Cluster,Cluster中配置了负载均衡策略,配置的hosts都是Endpoints。如果你想简单的使用Envoy,不带任何ServiceMesh,那么一个有这个配置文件的进程就够了,可以转发请求。动态配置还需要配置发现中心,即DiscoveryService。以上四种配置,每一种都对应一个对应的DS,所以有LDS、RDS、CDS、EDS。动态配置的一个例子如下:控制面Pilot和数据面Envoy的工作模式可以通过添加静态配置文件来运行,而动态信息需要从DiscoveryService获取。DiscoveryService部署在控制面,在Istio中是Pilot。如上图所示,是Pilot的架构,最底层是Envoy的API,也就是提供DiscoveryService的API。这个API的规则是由Envoy决定的,但是不是Pilot调用Envoy,而是Envoy主动调用Pilot的这个API。Pilot的最上层称为PlatformAdapter。这一层是做什么用的?这一层不是Kubernetes,Mesos调用Pilot,Pilot通过调用Kubernetes来发现服务之间的关系。这是理解Istio的一个点。也就是说,Pilot使用Kubernetes的Service,只是使用了它的服务发现功能,没有使用它的转发功能。Pilot通过在Kubernetes中注册一个Controller来监听事件,从而获取Service与KubernetesEndpoint和Pod之间的关系。但是在转发层面,将不再使用kube-proxy根据服务下发的iptables规则进行转发,而是将这些映射关系转化为Pilot自己的转发模型,转发给Envoy进行转发,以及Envoy不会使用kube——代理的那些iptables规则。这样,控制平面和数据平面就完全分离了。服务之间的关系是管理平面的事情。应该不绑定真正的转发,而是绕到Pilot后面。Pilot的另一个对外接口是RulesAPI,它是一个面向管理员的接口。管理员通过这个界面设置一些规则。这些规则通常应用于路由、集群和端点。以及PlatformAdapter通过服务发现获得了哪些Clusters和Endpoints。这些自动发现的Clusters和Endpoints,加上管理员设置的规则,就构成了Pilot的数据模型,其实就是自己定义的一系列数据结构,然后通过EnvoyAPI暴露出来,等待Envoy拉取这些规则。一个常见的手动规则是路由。通过服务发现,Pilot可以从Kubernetes中得知ServiceB有两个版本,通常是两个Deployment,属于同一个Service。管理员通过调用Pilot的RulesAPI设置两个版本之间的Route规则,一个占99%的流量,一个占1%的流量。这两方面的信息构成了Pilot的数据结构模型,然后通过EnvoyAPI发送出去,Envoy会根据这个规则设置转发策略。另一个常用的场景是负载均衡。Pilot通过KubernetesService发现ServiceB包含一个Deployment,但是有3个副本。所以通过EnvoyAPI下发规则,让Envoy在三个副本之间进行负载均衡,而不是通过Kubernetes自身Service的负载均衡机制。以Istio为例分析ServiceMesh的技术细节在了解了ServiceMesh的大致原理后,我们将通过一个例子来分析一下技术细节。任何尝试过Istio的人都应该尝试过以下BookInfo示例。不是很复杂,但麻雀虽小,却五脏俱全。在本例中,我们重点关注ProductPage服务,即对Reviews服务的调用,涉及到路由策略和负载均衡。Productpage是一个Python程序Productpage是一个用Python编写的提供RestfulAPI的简单程序。其中定义了很多路由,用于接收API请求并进行相应的操作。当您需要请求其他服务时,例如评论和评分,您需要向后台发起一个restful调用。从代码可以看出,Productpage是通过域名调用后端的。对于productpage这个程序,他觉得很简单,通过这个域名就可以调用。不需要通过服务发现系统获取域名,不需要关心转发。我什至没有意识到我部署在Kubernetes上,是否使用ServiceMesh,所以服务之间的通信完全交给了基础设施层。通过Kubernetes整理Productpage有了Productpage程序,接下来就是部署到Kubernetes上了。这里没什么特别的,使用Kubernetes默认的编排文件。首先定义一个Deployment,使用bookinfo容器镜像,然后定义一个Service用于这个Deployment的服务发现。通过Kubernetes安排评论有点复杂。定义了三个Deployment,但是版本号分别是V1、V2、V3,但是Label都是app:reviews。最后定义一个Service,对应的Label为app:reviews,作为这三个Deployment的服务发现。Istioctl对Productpage的定制之一:将proxy_init作为InitContainer嵌入。至此,一切正常,下一步就是见证奇迹,那就是Istio有一个工具Istioctl,可以自定义Yaml文件。自定义的第一项是添加一个InitContainer。这类Container在做一些初始化工作后就可以成功退出,Kubernetes不会让它长时间保持运行状态。在这个InitContainer中做什么?我们登录进去,发现在这个InitContainer中运行了一个Shell脚本。就是这个shell脚本在容器里面写入了大量的iptables规则。定义的第一个规则是ISTIO_REDIRECT转发链,不管链的性质如何,它都会将网络数据包转发到Envoy的15000端口。但是一开始这条链并没有和iptables的默认链挂钩,所以没有起作用。接下来就是在PREROUTING规则中使用这条转发链,让所有进入容器的流量都先转发到Envoy的15000端口。Envoy作为代理,已配置为将请求转发到Productpage程序。当Productpage程序收到请求时,它会切换到调用外部评论或评级。从上面的分析我们知道,Productpage只是进行普通的域名调用。Productpage调用后端时,遇到输出链,输出链会使用转发链将容器的所有请求转发到Envoy的15000端口。这样入口流量和出口流量都用Envoy做成了汉堡包。根据服务发现的配置,Envoy知道如何访问评论或评级,因此它进行最终的外部调用。这时候iptables规则会对Envoy出去的流量进行特殊处理,允许其发送出去,不再使用上面的输出规则。Istioctl自定义Productpage2:EmbedProxycontainerasSidecar。Istioctl做的第二个定制是将Proxy容器嵌入为Sidecar,如下图所示:这个看起来比较复杂,但是我们进入容器后可以看到启动了两个进程。一种是我们熟悉的Envoy,它有一个配置文件/etc/istio/proxy/envoy-rev0.json。前面讲Envoy的时候,我们说有了配置文件,Envoy就可以转发了。让我们看一下配置文件中的内容。这里配置了Envoy管道端口,后面我们会通过这个端口来查看Pilot向Envoy下发了哪些转发策略。然后是动态资源,也就是从各种DiscoveryServices中获取转发策略。还有静态资源,即静态配置的资源,需要重启才能加载。这就是pilot-agent的角色,也就是Envoy的简单管理者。因为有些静态资源,如果是TLS证书,Envoy不支持动态分发,所以需要重新静态配置,然后pilot-agent负责给Envoy做热重启加载。幸运的是,Envoy有很好的热重启机制。重启时,会先启动一个standby进程,通过SharedMemory在两个进程之间共享转发的统计信息。深入解析Pilot的工作机制Pilot的工作机制展开如上图所示。istioconfig是管理员通过管理界面下发的转发规则。对于Kubernetes,ServiceDiscovery模块会创建一个Controller来监听Service创建和删除事件。当Service发生变化时,Pilot会收到通知,Pilot会根据变化更新发送给Envoy的规则。Pilot将管理员输入的转发策略配置和服务发现的当前状态转换成Pilot自己的数据结构模型,然后对外暴露为Envoy的API。由于是Envoy调用,所以需要实现一个服务器,这里有LDS和RDS、CDS、EDS。接下来我们看看,在Pilot上配置Route后会发生什么?如上所示,我们将所有流量都发送到版本1。我们查看Envoy的管理端口,可以看到只配置了Reviews的V1。当我们修改路由时,V1和V3之间的比例是五十比五十。可以看到Envoy的管理端口,路由有两个版本的配置,也对应了后端的两个IP地址。