
前言 作为业内首个全托管Istio兼容的阿里云服务网格产品ASM,一开始从架构上就保持了与社区、业界趋势的一致性,控制平面的组件托管在阿里云侧,与数据面侧的用户集群独立。ASM产品是基于社区Istio定制实现的,在托管的控制面侧提供了用于支撑精细化的流量管理和安全管理的组件能力。通过托管模式,解耦了Istio组件与所管理的K8s集群的生命周期管理,使得架构更加灵活,提升了系统的可伸缩性。 服务网格是一个通过“Sidecar”模式进行服务治理简化的平台。整个服务网格可以划分为包括核心组件Istiod的“控制面”以及包括了每个服务的Sidecar的“数据面”。如果各位使用过服务网格,相信对上面的概念也算是略有了解了。 在第一次看config_dump中,我们知道每个Sidecar都是一个envoy应用,内部有着包含着listener、route、cluster、secret等部分的完整配置;envoy就是根据这些配置来改变自身行为,实现不同的流量控制手段的。而控制面组件的主要任务,就是将网格中的VirtualService、DestinationRule等资源“翻译”成envoy的具体配置,并将配置发送给数据面的每个envoy,我们简称这个过程为“配置推送”。 在envoy中,cluster配置对应着envoy内部的“集群”这个概念,一个cluster是指“Envoy 连接的一组逻辑相同的上游主机”,其实大多数时候也就是对应着一个具体的服务。 如果我们实际查看envoy的config_dump会发现,我们在什么都没做的时候,envoy内部就已经包含着一些动态下发的cluster了。这里就引出一个核心问题:服务网格的“服务发现”是如何完成的呢?我们通常会用“网格内服务”来称呼对应Pod注入了Sidecar的服务;那么,是不是说服务网格会将注入了Sidecar的“网格内服务”自动的加入每个Sidecar的cluster配置呢? 今天这篇文章就来主要聊一聊服务网格的“服务发现”和“配置推送”。 服务网格的“服务发现” 一个简单的小实验就可以解答上面的这些问题。我们在Kubernetes集群中安装服务网格平台(可以使用aliyun ASM + ACK来快速搭建服务网格平台),然后在default默认命名空间之外,再建立一个test命名空间。 然后,我们为default命名空间开启Sidecar自动注入,也就是说,default命名空间内的服务将自动成为“网格内”的服务,而test命名空间内的服务将不会注入Sidecar,也就是“网格外”的服务。 在服务网格ASM中,我们可以在控制台的“全局命名空间”管理中来方便地开启和关闭自动注入(开启/关闭后别忘了同步一下自动注入哦): 我们可以随意在这两个命名空间内部署一些中意的服务,我在这里向default命名空间部署了一个sleep服务。这个服务就如字面意思,里面是一个curl容器,并且一进去就一直在睡大觉 ( ̄o ̄).zZ ,可以进入这个服务的Pod方便地curl其它服务。 你可以在这里找到这个示例服务的YAML,将其拷贝到sleep.yaml,然后执行: kubectl apply -f sleep.yaml 同理,在test命名空间下,我们随便部署点啥服务,我这里部署了一个Istio使用者的老朋友bookinfo,可以在这里找到它的YAML,将其拷贝到bookinfo.yaml,然后执行: kubectl apply -f bookinfo.yaml -n test 顺带一提,为了不让sleep太寂寞,我还在default命名空间部署了一个httpbin应用陪他,不过不部署也无所谓┐(゚~゚)┌。 我们现在准备好了,大家可能猜到接下来要干啥了。我们就来看看——这个Sidecar里都有哪些cluster配置。 在第一次看config_dump中,我们是通过下载config_dump,再在那个成千上万行的大json里去找的cluster配置的。这回我们就不自虐了(● ̄(エ) ̄●),如果你使用Istio,可以用istioctl命令行工具方便地查看Sidecar中的各项配置的Summary;在服务网格ASM中这招可能行不通,不过ASM也有一个部分兼容的命令行工具asmctl,可以在这里下载。 以asmctl为例,可以用这个姿势来查看Sidecar内部的cluster配置(需要提前配置好数据面集群的Kubeconfig): $ asmctl proxy-config cluster sleep-557747455f-g4lcs SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE 80 - inbound ORIGINAL_DST BlackHoleCluster - - - STATIC InboundPassthroughClusterIpv4 - - - ORIGINAL_DST InboundPassthroughClusterIpv6 - - - ORIGINAL_DST PassthroughCluster - - - ORIGINAL_DST agent - - - STATIC asm-validation.istio-system.svc.cluster.local 443 - outbound EDS controlplane-metrics-aggregator.kube-system.svc.cluster.local 443 - outbound ORIGINAL_DST details.test.svc.cluster.local 9080 - outbound EDS envoy_accesslog_service - - - STRICT_DNS heapster.kube-system.svc.cluster.local 80 - outbound EDS httpbin.default.svc.cluster.local 8000 - outbound EDS istio-ingressgateway.istio-system.svc.cluster.local 80 - outbound EDS istio-ingressgateway.istio-system.svc.cluster.local 443 - outbound EDS istio-sidecar-injector.istio-system.svc.cluster.local 443 - outbound EDS istio-sidecar-injector.istio-system.svc.cluster.local 15014 - outbound EDS istiod.istio-system.svc.cluster.local 15012 - outbound ORIGINAL_DST kiali.istio-system.svc.cluster.local 20001 - outbound EDS kube-dns.kube-system.svc.cluster.local 53 - outbound EDS kube-dns.kube-system.svc.cluster.local 9153 - outbound EDS kubernetes.default.svc.cluster.local 443 - outbound EDS metrics-server.kube-system.svc.cluster.local 443 - outbound EDS productpage.test.svc.cluster.local 9080 - outbound EDS prometheus_stats - - - STATIC ratings.test.svc.cluster.local 9080 - outbound EDS reviews.test.svc.cluster.local 9080 - outbound EDS sds-grpc - - - STATIC sleep.default.svc.cluster.local 80 - outbound EDS storage-crd-validate-service.kube-system.svc.cluster.local 443 - outbound EDS storage-monitor-service.kube-system.svc.cluster.local 11280 - outbound EDS xds-grpc - - - STATIC zipkin - - - STRICT_DNS 这里就有一个有意思的事情了,虽然整套bookinfo应用都没有注入Sidecar,但我们还是能在sleep的Sidecar中找到productpage、reviews、ratings等bookinfo应用的服务信息。 这一切是怎么完成的呢?实际上Istio官方在这篇社区文章中,有对这一过程进行解释: In order to direct traffic within your mesh, Istio needs to know where all your endpoints are, and which services they belong to. To populate its own service registry, Istio connects to a service discovery system. For example, if you’ve installed Istio on a Kubernetes cluster, then Istio automatically detects the services and endpoints in that cluster. 简要地说,服务网格Istio并不进行服务发现。所有的服务都经由服务网格底层平台的服务发现(Kubernetes、Consul等,大多数情况下都是Kubernetes),通过控制面适配后传入网格自己的服务注册中心(也就是Sidecar的那一堆cluster配置了)。 In addition,如果你查看Sidecar中记录的endpoint,你会发现无论是否注入Sidecar,Kubernetes集群内所有Pod的ip地址都会记录在内。 尝试在test命名空间内部署下面这么个VirtualService: apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: namespace: test name: test-vs spec: hosts: - productpage.test.svc.cluster.local http: - match: - uri: prefix: /test/ rewrite: uri: / route: - destination: host: productpage.test.svc.cluster.local port: number: 9080 这个VirtualService实现了一个uri的重写,但目标host是一个“网格外”的服务productpage。 尝试一下,curl一个会被重写的路径/test/productpage kubectl exec -it sleep-557747455f-g4lcs -c sleep -- curl productpage.test:9080/test/productpage 会发现重写生效了,请求正常有返回。 上述行为可以说明,所谓“网格内外”只是以是否注入Sidecar确定的一个区分手段,并不是说网格本身和网格外的服务有着严格的隔离边界。在上面的例子中,VirtualService实际上生效于请求发送方Sidecar的route配置之中,而productpage服务即使没有Sidecar,也是会被服务网格控制面发现、并加入cluster配置之中的,有对应的cluster。因此,网格当然可以设定针对此cluster的路由规则,只要请求发送方的Pod是注入Sidecar的,VirtualService的功能就能正常工作。 当然,实际使用中,服务网格的其它资源可能生效于inbound配置之中,此时请求接收方也必须注入Sidecar才行。但Sidecar注入并不对网格的服务发现起到决定作用,这一点是可以肯定的 。 服务网格的“配置推送” 上面我们探索了服务网格的“服务发现”机制,可以说还是十分巧妙,这套机制既让服务网格免于再去实现一套冗余的服务发现机制,也方便网格本身与不同的底层平台进行对接。然而,仔细思考就会发现这其中存在的问题与隐藏的反直觉现实情况。 就以上面测试中我们在Kubernetes集群中部署的应用为例。Sleep应用与bookinfo应用是两个独立的应用,彼此之间其实没有太大关系(只不过想访问的话彼此还是访问的通而已)。在实际的生产环境中,相信很多用户都会有这样的实践:利用Kubernetes命名空间的隔离机制,在同一个集群中部署多个应用对外提供服务,每个应用包含几个微服务,而应用之间彼此的关系则比较独立。而其中又只有部分的应用需要使用服务网格的治理能力(多语言服务互通、蓝绿发布等),因此注入了Sidecar。 问题是,服务网格的控制面也不能假设用户服务之间的关系,因此还是需要一视同仁地watch集群内所有的服务与端点:-( 更难受的是,控制面还需要向及时向数据面的每一位Sidecar同步集群内最新的服务信息,这就导致了以下的“费力不讨好”局面: 1、一开始大家相安无事,岁月静好 2、Service2扩容了! 3、Istiod下发新的配置 4、尴尬的事情发生了,Service1和Service2其实是两个独立的应用,网格内的Sidecar不需要记录Service2的任何信息。 如果网格内的服务不多,这倒也不能造成什么巨大的问题,无非就是网格的控制面组件多做点无用功罢了。可是正所谓量变引起质变,无用功堆积起来就会成为不可忽视的大问题。如果您的集群中部署着成千上百个服务,而其中只有少量的服务加入了网格,网格的控制面就会淹没在大量的无效信息中,控制面组件的负载居高不下,并且不断向所有Sidecar推送他们并不需要的信息。 由于控制面组件不比网关,只是负责向Sidecar推送配置,控制面的负载过高可能很难引起服务网格用户的注意。然而一旦负载过高导致Pilot SLB超限/控制面组件重启等状况,用户就会轻则面临新的路由配置推送过慢、重则面临注入Sidecar的Pod起不来的窘境。因此,在享受服务网格为我们带来的便利的流量治理能力的同时,适度地关心服务网格控制面的身心健康,对控制面进行配置推送的优化,也是非常有必要的。 配置推送优化-之选择性服务发现 针对上面所述的状况,我们可以手动地去配置服务网格,让网格的控制面只去“发现”特定命名空间内的服务,而对其它的命名空间置之不理。在社区中,Istio于1.10版本后提供了“discovery selector”的能力(参考这篇文章)。而服务网格ASM也对应提供了“选择性服务发现”的能力,二者的机制是完全相同的。 我们以服务网格ASM的“选择性服务发现”为例。首先给网格内应用所在的命名空间打一个特定的标签,用来区分网格内与网格外应用所在的命名空间(注意是给数据面的命名空间打标签): # 在这里,default命名空间开启了自动注入,属于“网格内”命名空间 kubectl label namespace default in-mesh=yes # 其实我们也可以直接用 istio-injection:enabled标签,不用再打一个 进入我们的ASM实例管理页面,在左侧菜单中选择“配置推送优化 -> 选择性服务发现”。 在“选择性服务发现”页面中,选择“添加标签选择器” -> “添加标签精确匹配规则”,输入我们刚才打好的标签。 然后点击“确定”就好了,真是肥肠地方便。这之后网格会经过一个短暂的更新阶段,更新我们刚才写下的这个配置,然后重新进入“运行中”状态。 再来运行一遍我们最开始的指令,查看一下sleep服务的Sidecar中的cluster配置: $ asmctl proxy-config cluster sleep-557747455f-g4lcs SERVICE FQDN PORT SUBSET DIRECTION TYPE DESTINATION RULE 80 - inbound ORIGINAL_DST BlackHoleCluster - - - STATIC InboundPassthroughClusterIpv4 - - - ORIGINAL_DST InboundPassthroughClusterIpv6 - - - ORIGINAL_DST PassthroughCluster - - - ORIGINAL_DST agent - - - STATIC envoy_accesslog_service - - - STRICT_DNS httpbin.default.svc.cluster.local 8000 - outbound EDS kubernetes.default.svc.cluster.local 443 - outbound EDS prometheus_stats - - - STATIC sds-grpc - - - STATIC sleep.default.svc.cluster.local 80 - outbound EDS xds-grpc - - - STATIC zipkin - - - STRICT_DNS 可以看到sleep服务的Sidecar中已经没有任何一个bookinfo应用中服务的信息了,现在Sidecar中的配置看起来精简多了。可喜可贺可喜可贺\(^o^)/ 当然,我们做的不只是减少Sidecar中的配置数量而已。如上文所说,使用“选择性服务发现后”,控制面将不会watch除default命名空间外的任何服务,也因此控制面的工作负担得到了大幅削减,服务网格重回高效运转状态~我们也可以没有后顾之忧地向集群中部署其它服务了。 总结 服务网格的服务发现机制其实说起来是一个十分简单的东西,毕竟服务网格跟本就没有服务发现机制呢! 不过不去实际了解的话,我想很少有人能够直接就笃定每个Sidecar里那些林林总总的服务到底是怎么写到Sidecar配置里的。同时,Istiod作为服务网格中负责配置推送的核心组件,做的却是修改翻译和修改配置这样的后台工作,也导致网格的用户通常很难意识到控制面组件到底在进行什么工作,以及现有的实践是否给控制面组件造成了过大的多余负担。 希望这篇文章能让更多的网格用户意识到,配置推送的优化也是网格平台维护的重要一环。使用“选择性服务发现”的话,仅用1分钟的时间就能够大幅度优化网格的配置优化,减少使用网格时无谓的隐患,岂不美哉? 对于大多数用户来说,“选择性服务发现”就已经足够对配置推送进行优化了。不过如果你想做的更“绝”,还可以直接使用服务网格的Sidecar资源来对Sidecar的配置进行最大限度地优化。Sidecar资源的YAML真的很难写,不过服务网格ASM还提供了基于访问日志分析自动推荐的Sidecar资源来帮你完成这一伟业。 此为后话,不表但是可以参考https://developer.aliyun.com/article/848355。 参考 https://istio.io/latest/blog/2021/discovery-selectors/ https://help.aliyun.com/document_detail/386398.html https://developer.aliyun.com/article/894258 https://help.aliyun.com/document_detail/399726.html https://help.aliyun.com/document_detail/363023.html https://istio.io/latest/docs/concepts/traffic-management/
前言各位,知道的越多,就越会发现自己的无知。在面对服务网格这样的新兴概念之时,就更是如此了。回想昨日,满头大汗地研究VirtualService和DestinationRule是干什么用的自己仿佛还近在眼前。然而,在搞明白了服务网格的基本概念之后,我却发现自己甚至坠落进更大的疑惑之中了。如果你看过了一些istio的基本知识与概念,你应该知道istio为每个数据面的Pod都注入了一个Sidecar,这Sidecar也就是Envoy代理、会拦截所有业务容器收发的请求,并根据用户在网格中应用的VirtualService、DestinationRule等规则,对请求进行这样的以及那样的处理、并最终转发至路由目标(如果是出向请求/outbound,就向被请求方转发;如果是入向请求/inbound,就向业务容器自身转发)。而控制面则存在着一个核心的组件istiod,该组件的其中一个重要职责就是通过一组被称作xds的协议向每个Envoy代理下发配置,以改变Envoy代理处理与转发请求时的行为。那么,istiod到底向envoy下发了什么配置,而这些配置又如何影响envoy的行为,并最终使得服务网格成为一个有机整体的呢?如果每天只是看这些表层上的概念,相信我们一定这辈子都没法知道这个问题的答案了。俗话说,百闻不如一见,如果不知道这配置是什么样,那实际看一看这配置是什么东西便好了,相信至少也能获得一点启发。说干就干,我设计了一个简单的方式,让我们(至少我(・∀・*))能一睹envoy配置的芳容,同时大概也许能不被它绕晕,还能看出点名堂出来:1、创建一个服务网格,并部署一个bookinfo应用。相信如果你了解过istio,那一定很熟悉这个应用了,也很明白这个应用是干啥的。我这辈子部署这玩意不说几千、也有上百遍了。当然,我推荐你使用ASM的快速入门,帮你快速在ASM中部署一套bookinfo应用。2、在定义Istio资源这一步,我们不要做的那么快,而是每应用一种Istio资源,我们就把当时ASM网关和productpage这两个Pod(分别作为网关Pod与业务Pod的代表)中envoy的配置信息拉出来,研究一下istiod又往里面加了点啥东西。怎么拉envoy的配置信息?istio-proxy已经为我们准备了调试接口,我们只要进到容器里面下载一下就行了。就像这样:# 先确保kubectl连接的是你的数据面集群 kubecctl exec -it {你的网关或者业务Pod名} -c istio-proxy -n {你的网关或者业务Pod所在的命名空间} -- curl localhost:15000/config_dump > {一个文件名,你把配置下载到这个文件里}到目前为止,其实我们已经知道了点东西,至少我们现在知道这个配置叫做config_dump了,为自己喝彩吧!First Peek好的,那么赶紧来看看这个config_dump里都有啥吧。现在,我们在数据面中部署好了bookinfo应用和ASM网关,但是没有在网格中应用任何istio资源。productpage的config_dump是这样的(网关的也差不多):你会发现config_dump可真长!足有10000行!所以我在这里把里面的内容收起来了,只留下一点主干内容,以便于看清楚整体的配置结构。虽然它很长,但也不过是个json罢了。我们发现config_dump的主体就是这个configs,这是一个config的数组,包含了几个方面的配置(嗯,是句废话),我们发现每个配置都指定了一个独特的@type字段,来指定这段配置是有关哪个方面的配置。看到这里,我们可能要回顾一下Envoy的主要作用是什么,来对照着看一下这些配置。我们刚才说什么来着?如果你看过了一些istio的基本知识与概念,你应该知道istio为每个数据面的Pod都注入了一个Sidecar,这Sidecar也就是Envoy代理、会拦截所有业务容器收发的请求,并根据用户在网格中应用的VirtualService、DestinationRule等规则,对请求进行这样的以及那样的处理、并最终转发至路由目标对,就是这个,我们可以从中得到一些信息:1、Envoy拦截了业务容器请求(Envoy将请求的发送方称为downstream)2、Envoy对请求做了一些处理,比如限流、给请求加header之类的(Envoy将每一步处理都称为一个filter)3、Envoy最终将请求转发到路由目标(Envoy将请求的发送目标称为upstream)现在我们来看这些config。BootStrapConfigDump用于在Envoy启动时加载的一些静态配置,包括类似Sidecar的环境变量等信息。我们可以先不用在意这个。ListenersConfigDump —— 监听器这里存储着Envoy的“listeners”配置。顾名思义,listener就是Envoy的“监听器”。Envoy在拦截到请求后,会根据请求的地址与端口,将请求交给匹配的listener处理。我们看到这个ListenersConfigDump中的listener配置似乎分为两派:static_listners和dynamic_listeners。其实这俩内部是一样的,都是一堆listener的集合,不过static_listners里面是Envoy的静态配置,是Envoy建出来就带的,而dynamic_listeners内部的listener则是istiod通过LDS协议为Envoy下发的~我们好像已经get到一点istiod在给Envoy下发什么了,真不错。那么每个listner又长什么样呢?举个例子:还是太长了,不过作为入门者我们也不用考虑太多,可以重点关注这个json里面的这几个信息:name:每个listener都有个名字,和其它的都不一样,是个用来区分自己的idaddress:这个listener监听的地址与端口号。在这个例子中,这个listener监听0.0.0.0和9080,也就是说发送到任意地址的9080端口的请求都会被这个listener监听并处理filter_chains:一个filter的列表,这个就厉害了。直到刚才我们都在讨论的是Envoy拦截请求,到这里,我们终于要“处理”请求了。请求到达这个listener之后,Envoy就会将请求交给filter_chain中的这些filter,由配置的filter依次处理请求。我们详细看一下。实际上,Envoy内置了很多的filter,filter_chains里声明的则是filter的配置数据。我们注意到这个listener的filter_chains中目前就配置了一个filter,它的名字是envoy.filters.network.http_connection_manager,这是一个专门处理7层协议HTTP请求的filter。比较引起人兴趣的是这个filter内部通过http_filters这个字段又配置了几个小的子filter,看起来明显是负责对HTTP请求不同方面的处理的。比如展开的部分,这里有一个envoy.filters.http.fault,明显是用来处理故障注入的,而它的后面跟着一个envoy.filters.http.router,毫无疑问是用来对请求做路由转发的。目前就能够很明显的看出,路由转发也是“处理”的一环,实际上、它是Envoy处理请求的最后一步。这个设计倒也不令人以外,而且还是比较清晰的。你甚至从这个配置里就能想象得到Envoy对请求的处理过程:请求先到达envoy.filters.http.fault这个filter内部有一套机制,可以以一定的概率延迟几秒、再将请求交给envoy.filters.http.router去路由,这样故障注入就完成了,完美(虽然实际实现肯定还是差了不少,哈哈)诶?但是这个路由要转发到哪去是怎么决定的呢?这个就涉及到Envoy的路由配置了(route_config)。在envoy.filters.network.http_connection_manager里你还能看到一个叫rds的配置,里面配了一个route_config_name,这个route_config_name引用了一个名字:"9080";这个名字连接到我们下一节要说的内容。RoutesConfigDump —— 路由规则配置顾名思义,这里存着的就是Envoy的路由配置了。和listeners一样,RoutesConfigDump也分为static_route_configs和dynamic_route_configs,也是分别对应着静态的路由配置和动态下发的路由配置,内部的每个路由配置的结构也都是相同的。和listeners一样,我们再来看一下一个路由配置是长什么样的:注意到这个路由配置的name字段是9080,那么这一切就串起来了。上文中提到listener中配置了一个叫做envoy.filters.network.http_connection_manager的filter来处理和转发HTTP请求,它的子filterenvoy.filters.http.router通过读取这个叫做9080的路由配置(route_config)来对请求做最后的转发。可以看到一个route_config里面也没啥,最主要的内容就是它的virtual_hosts,里面是一堆虚拟主机(virtual_host),我们先看看一个虚拟主机里面有啥:虚拟主机是一个Envoy在路由过程中使用的概念,一组虚拟主机就是Envoy在路由配置中使用的顶级元素。其实可以先简单地将一个虚拟主机想象成一个服务即可。实际上,虚拟主机顾名思义,就是一个“虚拟”的主机,可以对我们的请求进行响应,其后面可能直接是一个服务,接受我们的所有请求并响应;也有可能是一堆服务,根据请求的不同特征,虚拟主机将请求发送给不同的服务、并返回响应,但对请求的发送方来说,虚拟主机的表现和一个服务是没啥区别的,都是接受请求并给出响应。原本Pod需要访问一个服务,我们需要使用k8s的DNS机制、将服务域名变为Service ip、再使用ip地址去访问服务。但加入了Sidecar拦截机制后,Envoy配置监听所有发送到某一端口(如9080)的请求,并统一交给一个listener处理;请求到了转发这一步时,Envoy其实是不能知道这个请求是发给哪个服务的,因为只要是9080端口的服务请求都混在这里。此时Envoy就需要一些额外的信息来决定转发目标了。我们看到一个虚拟主机最重要的字段就是它的“domains”字段,这里配置了一系列的匹配域名来与请求进行匹配。实际的做法是,Envoy通过获取请求中的host请求头字段、并与这里的domains一一比对,如果对上了就说明这个请求应该发送到这个虚拟主机;于是请求就被交给了这个虚拟主机来进行下一步的路由。在虚拟主机里,我们发现对于七层协议的HTTP请求还可以通过匹配请求的不同属性(如请求的路径、header等)来进行更细致的路由。在这个简单的例子里,虚拟主机details.default.svc.cluster.local:9080的“routes”字段中只有这么一条路由,它包含如下的“match”字段:... "match": { "prefix": "/" }, ...这说明只要是路径以根路径开头的请求都会走到这条路由,而这条路由中又包含着这么一个“route”字段:... "route": { "cluster": "outbound|9080||details.default.svc.cluster.local", "timeout": "0s", "retry_policy": { "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes", "num_retries": 2, "retry_host_predicate": [ { "name": "envoy.retry_host_predicates.previous_hosts" } ], "host_selection_retry_max_attempts": "5", "retriable_status_codes": [ 503 ] } ...这个字段就是在描述我们最终匹配到的路由目标了。针对这个目标,可以看到有一些诸如“retry_policy”、“timeout”等的字段来配置请求这个目标时的超时、重试策略等;不过最重要的还是“cluster”字段,它指定了这个路由目标对应着哪个上游“集群”,Envoy最终将请求发送到这个“集群”,而“集群”这个概念就连接到下面这部分的配置 —— ClustersConfigDump —— 集群Cluster —— “集群”,又是Envoy内部使用的一个概念。这里的集群明显不是说k8s集群,而是Envoy定义的一个概念,是指“Envoy 连接的一组逻辑相同的上游主机”。在大多数情况下,我们可以把这个“集群”就理解成k8s集群中的一个Service。一个Service通常对应着一组Pod、由这组Pod响应请求并提供同一种服务,而Envoy的这个“集群”实际可以理解成这种“Pod的集合”。不过Envoy的一个集群也不一定就对应着一个Service,因为集群是“一组逻辑相同的上游主机”,所以也有可能是别的符合定义的东西,比如说是服务的一个特定版本(如只是v2版本的reviews服务)。实际上istio著名的版本灰度能力就是基于这个做的,因为两个版本的同一服务实际上可以分成两个集群。(下直接称Cluster为集群,k8s的集群就叫k8s集群)我们刚才提到virtual host——虚拟主机,可以想象成一个虚拟主机可以路由到不同的服务。但是从Envoy的角度来说,实际上是一个一个虚拟主机可以路由到不同的集群,Envoy最终通过集群来向集群中的端点(大多数情况下就是k8s集群中的Pod)转发请求。同样的,ClustersConfigDump里面也有static_clusters和dynamic_active_clusters两种,这俩分别是啥意思相信看到现在你也已经明白了╮( ̄▽ ̄)╭。还是简单看一下一个cluster config里面有啥:目前这个阶段其实没必要关注太多,我们可以在这个配置里给集群中的Pod配置各种负载均衡或者超时策略等,比较重要的是这个name,实际上除了name其它配置都是可选的。集群的name大多数会由“|”分成四个部分,分别是 inbound 或 outbound 代表入向流量或出向流量、端口号、subcluster 名称(就是对应着destination rule里那个subset)、Service FQDN,由istio的服务发现进行配置,通过这个name我们很容易就能看出来这个集群对应的是k8s集群的哪个服务。你可能比较关心:既然集群对应着一组Pod,Envoy要在这些Pod之间做负载均衡转发,那这些Pod的地址放在哪了呢?没有Pod地址Envoy怎么知道往哪转发呢?实际上Pod的信息确实是有的,不过不在config_dump里,而是在另一个叫做clusters的配置里,你可以通过与下载config_dump类似的方式下载clusters:# 先确保kubectl连接的是你的数据面集群 kubecctl exec -it {你的网关或者业务Pod名} -c istio-proxy -n {你的网关或者业务Pod所在的命名空间} -- curl localhost:15000/clusters > {一个文件名,你把配置下载到这个文件里}这个clusters是一个plain text的配置,举个例子,里面有这么几行对应着集群outbound|9080||details.default.svc.cluster.local:里面记载着这个集群的最大连接数(max_connection)等配置、以及各个端点(Pod)的各项数据(如rq_total代表总共发送的请求数)等,具体这些都是啥意思就不再赘述了。SecretsConfigDump由于网格中的envoy之间互相通信会使用mTLS模式,因此每个Envoy通信时都需要提供本工作负载的证书,同时为了签发证书还需要istio ca的根证书,这些证书的信息保存在config_dump的这一配置之下。此处不再赘述。小结、与envoyctl至此,我们终于把config_dump中这几块主要的配置看了一遍,它们分别是Bootstrap、Listeners(监听器)、Routes(路由)、Clusters(集群)、Secrets,其中又涉及到virtual host(虚拟主机)等细分概念。总体来看,一个典型的HTTP请求在Envoy内部经历了以下事情:可以看到,整体上一个请求在Envoy内部的处理与转发过程中,listener、route、cluster这几个配置是环环相扣的,它们通过配置的name一层又一层地向下引用(listener内的filter引用route、route内的virtual_host引用cluster),形成了一条引用链,最终将请求从listener递交到具体的cluster。这里可以用一个小工具——envoyctl,来更加清晰地梳理这一关系。工具链接:https://github.com/djannot/envoyctl注意:这是一个用js写的小工具,所以要先安装node.js。这个工具的主要用途就是解析config_dump文件,然后给你画出一张表来。解析命令:envoyctl -f <Envoy config dump file>输出就是一张大表,这大表的每一行大抵都是下面的样子:可以看到这一行上,从左往右把listener的name、listener中指定的route_config的name、route_config中virtual_host匹配的域名、virtual_host内“match”字段指定的子路由匹配规则、virtual_host的路由转发目标、转发目标对应的cluster等信息给你依次列了一遍。这一行就对应着一个请求的处理链条,能够帮你迅速找到某个请求匹配到的处理流程。发生什么事了first peek之后,让我们继续开头说到的那个实验,来看看我们在定义各种Istio资源时,Istiod给Envoy到底下发了什么配置,使得Envoy的行为发生改变了呢?好的,假设我们已经开启了自动注入、在数据面集群部署了bookinfo与ASM网关。1、定义Gateway资源对应执行步骤:定义Gateway资源这一步主要作用于我们的网关,我们的网关实际上也是一个Envoy,只不过它不是作为业务容器的Sidecar、而是独自作为一个Pod存在的,所有的入向流量都要流经我们的ASM网关。在定义Gateway资源之前,网关的ListenersConfigDump是这样的:这个配置里面完全没有动态下发的listener配置。而定义好Gateway之后就变成了这样:可以看到Istiod为网关下发了一个dyamic_listener,它的名字是0.0.0.0_80,负责监听发往80端口的所有流量。这个listener内部也引用了一个新下发的route_config,它的名字是http.80。再来看看网关的RouteConfigDump,里面的确新增了一个叫做http.80的route_config!这个route_config内部配置了一个virtual_host,这个virtual_host匹配一个*(通配符,匹配所有域名),可以发现这个“*”正是我们在Gateway的hosts字段中配置的域名内容。但是这个virtual_host还没有配置任何路由目标,这很正常,因为我们此时还没有定义任何路由规则!此时对应关系已经比较明显,我们对照着Gateway资源的结构来看:apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: bookinfo-gateway spec: selector: istio: ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - "*"在Gateway的servers配置中,port部分对应着listener,Istiod会根据port的number和protocal下发一个动态的监听器给网关;而hosts字段则规定了这个listener对应的route_config中、virtual_hosts匹配的域名。这也解释了为什么我们的网关的Pod和Service已经规定了监听的端口,我们却仍然要定义一个Gateway资源把监听端口再指定一遍。因为我们需要Istiod为Envoy下发一个对应端口的listener、作为处理和转发链条的入口,才能让网关具备处理和转发请求的能力。2、定义虚拟服务对应执行步骤:定义虚拟服务我们定义了一个虚拟服务,让发往网关的特定请求能够路由到productpage服务,到这里我们访问入口网关的时候就已经可以看到productpage的页面了。由于这个虚拟服务还是通过gateways字段指定了生效的网关,所以主要还是看网关的config_dump发生了什么变化。查看刚才名为http.80的route_config,我们发现其virtual_hosts中已经增加了若干的路由目标:我们在虚拟服务中规定了五种匹配请求uri的规则:/productpage、/static、/login、/logout、/api/v1/products,将路径匹配这五种规则的请求都发送到productpage服务。可以发现virtual_hosts下正好也增加了五条子路由,其“match”字段也正好就是匹配这五个路径的(后面四个我给折叠了、要不然太长了),而这五条子路由的路由目标则无一例外都是outbound|9080||productpage.default.svc.cluster.local这个cluster(也就对应着productpage服务)。所以,我们的虚拟服务(VirtualService),实际上就对应着Envoy中的virtual_host。虚拟服务的hosts字段是为了指定virtual_host匹配的域名、从而将其中配置生效到正确的virtual_host;而虚拟服务中http路由字段下的“match”和“route”这两个字段(以http路由为例)正好就对应着virtual_host里一条子路由(你能够看到virtual_host里一条子路由其实也就是这俩字段)。3、定义目标规则对应执行步骤:定义目标规则在这一步,我们定义了一个目标规则(DestinationRule),为reviews服务指定了v1、v2、v3三个subset、其中v2版本的Pod使用LEAST_CONN类型的负载均衡、而v3版本的Pod使用RANDOM类型的负载均衡。我们刚才提到,在ClustersConfigDump中,每个cluster最重要的就是它的name,name由“|”分成四个部分,分别是 inbound 或 outbound 代表入向流量或出向流量、端口号、subcluster 名称、Service FQDN。在定义了上述的目标规则后,我们来看config_dump发生了什么变化。我截取了productpage的config_dump的前后变化做例子。在定义这个目标规则前,针对reviews服务,config_dump中只有以下的一个cluster配置:它的名字是outbound|9080||reviews.default.svc.cluster.local。而定义目标规则后,我们发现这个reviews的cluster分裂成了仨,其中一个如图所示(其它两个折叠了)可以看到这个cluster叫做:outbound|9080|v2|reviews.default.svc.cluster.local,当然我们还有v1和v3的。这个cluster的“lb_policy”一项配置被设定成了"LEAST_REQUEST",很明显对应着我们在目标规则中设定的“LEAST_CONN类型的负载均衡”。于是这也很明显了,目标规则实际上对应着config_dump中的cluster配置。诸如loadBalancer等字段实际上是修改对应cluster的配置、来实现不同负载均衡规则等功能;而subset实际上就是Envoy的cluster本身固有能力的一个封装。如果你很好奇,也可以在此时看一下Envoy的clusters,就是通过上面说过的这个指令:# 先确保kubectl连接的是你的数据面集群 kubecctl exec -it {你的网关或者业务Pod名} -c istio-proxy -n {你的网关或者业务Pod所在的命名空间} -- curl localhost:15000/clusters > {一个文件名,你把配置下载到这个文件里}里面记载的reviews的cluster也分成了三个:v1 v2 v3,对应的Pod地址正好就是三个版本的Pod地址,这里就不赘述了。4、增加新的虚拟服务对应执行步骤:增加新的虚拟服务这里又增加了一个新的虚拟服务,将来自productpage的请求在v2和v3版本的reviews之间进行50:50 的按权重路由。由于有了之前的经验,我们知道这个虚拟服务肯定是改virtual_host去了,我们来拿productpage的config_dump看一下,看看Istiod又下发了啥:这是匹配reviews的virtual_host内的子路由。可以发现原先只规定了一个cluster作为路由目标的地方变成了一个“weighted_clusters”,在其中规定了两个cluster并可以根据权重进行路由。可见权重路由也不过是Envoy的又一个能力之一罢了,只不过istio将其抽象封装在了虚拟服务的配置之中。结语虽然没看的东西很多,我们姑且算是走马观花地审视了一遍config_dump,也算窥探了一丢丢网格的奥秘吧。其实这么一看,istiod也并非做了多么复杂的逻辑,而主要是起到了一个翻译器的作用。subset、负载均衡、权重路由、故障注入……这些其实都是Envoy已有的能力、可以通过直接修改Envoy的配置值实现。而Gateway、VirtualService、DestinationRule则分别与listener、virtual_host、cluster一一对应、环环相扣,分别负责翻译Envoy请求处理链条中每一环的具体配置内容。通过将复杂的Envoy config_dump抽象成为具体的istio资源,用户就能够通过(相对)简单的方式激活Envoy的各项能力、实现流量的灵活治理(虽然还是挺难的(〃´皿`))。而未经抽象的各种能力、也可以通过EnvoyFilter来直接下发原汁原味的envoy配置,让入门用户和高级用户都能各取所需,不得不感慨服务网格的设计哲学。可悲的是,做完了所有这些事情、写下这篇文章之后,我感觉自己不会的更多了,可能这就是人生吧。:-( 共勉。参考istio 数据面调试指南 - zhonghua | 钟华的博客 | zhongfoxhttps://zhonghua.io/2020/02/12/istio-debug-with-envoy-log/详细解读Service Mesh的数据面Envoy - 简书https://www.jianshu.com/p/5720e913759bEnvoy 运维调试 - 简书https://www.jianshu.com/p/0fc4e20aca35Istio sidecar proxy 配置 · Istio 服务网格——云原生应用网络构建指南https://jimmysong.io/istio-handbook/data-plane/istio-sidecar-proxy-config.htmlIstio 的数据平面 Envoy Proxy 配置详解 · Service Mesh|服务网格中文社区https://www.servicemesher.com/blog/envoy-proxy-config-deep-dive/Istio 中的 Sidecar 注入及透明流量劫持过程详解 - 知乎https://zhuanlan.zhihu.com/p/137011071
在默认情况下,由于不能确定网格内服务之间的调用依赖关系,Sidecar的配置中保存了数据平面内所有服务的信息;同时,一次针对控制平面或数据平面的修改都会引起控制平面向数据平面所有Sidecar的一次配置推送。您可以使用Sidecar资源配置Sidecar、来调优与应用实例的出口和入口通信。您可以参考管理Sidecar资源,来自行创建并管理Sidecar资源。除此之外,服务网格ASM可以通过分析数据平面Sidecar产生的访问日志、抽取数据平面服务之间的调用依赖关系,为数据平面中的每个工作负载自动推荐Sidecar资源。在应用了推荐的Sidecar资源后,对应工作负载上的Sidecar将仅关注与自己有调用依赖关系的服务信息。这代表:Sidecar配置内将仅保留该Sidecar对应工作负载所依赖的服务信息与该Sidecar资源对应的工作负载无依赖关系的服务发生改变、或与该服务相关的资源发生改变(如虚拟服务等),都不会引起控制平面向该Sidecar的配置推送本文以针对实例应用Bookinfo中的微服务productpage推荐的Sidecar资源为例,介绍如何使用基于访问日志分析自动推荐的Sidecar资源。前提条件已创建ASM实例。具体操作,请参见创建ASM实例。已创建ACK集群。具体操作,请参见创建Kubernetes托管版集群。添加集群到ASM实例。具体操作,请参见添加集群到ASM实例。已经配置了使用日志服务采集数据平面的访问日志。具体操作,请参见使用日志服务采集数据平面的AccessLog。已经通过入门指引,在集群中部署了bookinfo示例应用。具体操作,请参见快速入门。步骤一:为访问日志分析产生访问日志参照快速入门,在ACK集群中部署Bookinfo示例应用,在ASM示例中部署所有Istio资源。在浏览器地址栏输入http://{入口网关服务的IP地址}/productpage。持续刷新,直至观察到Bookinfo应用页面在显示黑色星型图标和红色星型图标之间等权重地轮流切换。步骤二:为工作负载推荐Sidecar资源登录ASM控制台。在左侧导航栏,选择服务网格 > 网格管理。在网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理。在网格详情页面选择配置推送优化 > Sidecar资源推荐。如果已经执行过步骤一,此时在Sidecar资源推荐页面中将会出现包括productpage-v1在内的所有Bookinfo示例应用内的工作负载列表,且推荐任务状态列均会显示未推荐过。找到工作负载productpage-v1,在操作列中单击推荐。此时productpage-v1的推荐任务状态列变为推荐中,且暂时不可操作其它工作负载,请耐心等待直至推荐完成。推荐完成后,productpage-v1的推荐任务状态列变为推荐完成,且此时已经可以使用productpage-v1的查看与重新收集日志操作。在productpage-v1的操作列中单击查看,可以查看为该工作负载自动推荐的Sidecar资源yaml。为productpage-v1推荐的Sidecar资源预期结果如下,可以发现推荐结果中仅包含details和reviews两个服务,这是因为productpage服务仅与这两个服务之间产生调用依赖关系。apiVersion: networking.istio.io/v1beta1 kind: Sidecar metadata: name: asm-rec-for-default-productpage-v1 namespace: default spec: egress: - hosts: - '*/details.default.svc.cluster.local' - '*/reviews.default.svc.cluster.local' port: name: HTTP-9080 number: 9080 protocol: HTTP workloadSelector: labels: app: productpage version: v1在弹出的yaml预览面板中,确认推荐的Sidecar中是否包含了所有该工作负载依赖的所有服务。如确认无误,可以单击创建,ASM将会应用这一自动推荐的Sidecar资源,以减少productpage-v1工作负载的配置推送频率与推送数据量。可选步骤:重新为工作负载推荐Sidecar资源随着业务应用的升级,服务网格中服务之间的调用依赖关系可能会发生改变,此时之前ASM推荐的Sidecar资源将不再有效,此时建议重新收集日志,并重新为对应工作负载推荐Sidecar资源。登录ASM控制台。在左侧导航栏,选择服务网格 > 网格管理。在网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理。在网格详情页面选择配置推送优化 > Sidecar资源推荐。如果已经执行过步骤二,此时在Sidecar资源推荐页面中将会出现包括productpage-v1在内的所有Bookinfo示例应用内的工作负载列表,且productpage-v1的推荐任务状态列会显示推荐成功。找到工作负载productpage-v1,在操作列中单击重新收集日志,在随后的弹窗中单击确定。此时在步骤二中被成功应用的Sidecar资源将被删除,productpage-v1的推荐任务状态列会显示重新收集日志中。用户需要重新执行步骤一,重新为访问日志分析产生访问日志。用户需要重新执行步骤二,重新为工作负载productpage-v1推荐Sidecar资源。使用场景ASM的Sidecar资源推荐通过单独针对每个负载单独的服务依赖关系提供Sidecar资源,能够最大化精简数据面Sidecar配置与减少不必要的配置推送,但其需要针对数据面中的每个工作负载进行单独配置,且由于推荐基于访问日志,如果访问日志中没有工作负载的服务调用记录,可能造成推荐结果的不准确,因此推荐结果需要用户进行二次确认。如果选择性服务发现无法满足您的配置推送优化需求,您的单个命名空间中存在着大量的服务,需要对Sidecar配置进行最大限度的精简,您可以考虑使用ASM基于访问日志的Sidecar资源推荐功能,此功能可以大幅减少您手动应用Sidecar资源的困难。应用Sidecar资源后的配置推送优化效果本节以一个部署440个Pod的较大规模集群为例,测试并分析应用Sidecar资源后的服务网格配置推送优化效果。前提条件已创建ASM实例。具体操作,请参见创建ASM实例。已创建ACK集群。具体操作,请参见创建Kubernetes托管版集群。添加集群到ASM实例。具体操作,请参见添加集群到ASM实例。通过kubectl连接ACK集群。具体操作,请参见通过kubectl管理Kubernetes集群。已经配置了使用日志服务采集数据平面的访问日志。具体操作,请参见使用日志服务采集数据平面的AccessLog。在集群中部署测试应用本次测试通过使用多个sleep应用与httpbin应用模拟集群中存在数量庞大的服务,但服务与服务之间只存在少量调用依赖关系的场景。httpbin应用在启动后会在8000端口暴露一个http服务,可模拟集群内部大量被调用的服务;而sleep应用则包含一个curl容器,可以通过修改应用部署的command字段、让sleep应用在睡眠之前调用多个httpbin的容器提供的服务,可模拟集群内依赖其它服务的服务。在集群中部署多个httpbin应用注意,在部署测试应用前,需要确定default命名空间已经开启Sidecar自动注入,并开启使用日志服务采集数据平面的AccessLog。使用以下的yaml模板,创建多个httpbin应用yaml文件。apiVersion: v1 kind: ServiceAccount metadata: name: httpbin --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: httpbin-{i} service: httpbin-{i} name: httpbin-{i} spec: ports: - name: http port: 8000 targetPort: 80 selector: app: httpbin-0 --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: httpbin-{i} name: httpbin-{i} spec: replicas: 2 selector: matchLabels: app: httpbin-{i} version: v1 template: metadata: creationTimestamp: null labels: app: httpbin-{i} version: v1 spec: containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80 serviceAccountName: httpbin将yaml文件保存为httpbin-{i}.yaml,并在集群中应用。kubectl apply -f httpbin-{i}.yaml其中标明为{i}的部分可用具体数字代替,以生成多个不同的带编号的httpbin服务。使用此模板可以生成任意多个httpbin应用,具体应用数量限制取决于集群的规模。在本文的测试中,使用该模板生成了200个httpbin应用部署,在集群中共部署400个httpbin应用的Pod。在集群中部署sleep应用使用以下的yaml模板,创建多个sleep应用yaml文件。apiVersion: v1 kind: ServiceAccount metadata: name: sleep --- apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: sleep-{i} service: sleep-{i} name: sleep-{i} spec: ports: - name: http port: 80 targetPort: 0 selector: app: sleep-{i} --- apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: sleep-{i} name: sleep-{i} spec: replicas: 1 selector: matchLabels: app: sleep-{i} template: metadata: creationTimestamp: null labels: app: sleep-{i} spec: containers: - args: - curl httpbin-{i*10}:8000; curl httpbin-{i*10+1}:8000; curl httpbin-{i*10+2}:8000; curl httpbin-{i*10+3}:8000; curl httpbin-{i*10+4}:8000; curl httpbin-{i*10+5}:8000; curl httpbin-{i*10+6}:8000; curl httpbin-{i*10+7}:8000; curl httpbin-{i*10+8}:8000; curl httpbin-{i*10+9}:8000; sleep 3650d command: - /bin/sh - -c image: curlimages/curl imagePullPolicy: IfNotPresent name: sleep volumeMounts: - mountPath: /etc/sleep/tls name: secret-volume serviceAccountName: sleep terminationGracePeriodSeconds: 0 volumes: - name: secret-volume secret: optional: true secretName: sleep-secret将yaml文件保存为sleep-{i}.yaml,并在集群中应用。kubectl apply -f sleep-{i}.yaml其中标明为{i}的部分可用具体数字代替,以生成多个不同的带编号的sleep服务。在此模板中,通过向sleep应用部署的args字段增加curl httpbin-{i*10}:8000这样的命令参数,模拟向不同的httpbin应用的调用依赖,注意这里调用的httpbin服务的编号不能超过之前部署的httpbin服务编号,否则无法产生有效调用。在本例中模拟了每个sleep应用依赖10个httpbin应用,因此本文例中共部署20个sleep应用部署,20个sleep Pod。测试未使用Sidecar资源之前的控制面配置推送情况(一)测试使用Sidecar资源优化前每个Sidecar的配置大小执行以下命令,确定httpbin-0 Pod名称。kubectl get pod -n ns-in-mesh | grep httpbin-0预期输出httpbin-0-756995d867-jljgp 2/2 Running 0 9m15s httpbin-0-756995d867-whstr 2/2 Running 0 9m15s执行以下命令,下载httpbin-0 Pod的Sidecar配置到本地。kubectl exec -it httpbin-0-756995d867-jljgp -c istio-proxy -n ns-in-mesh -- curl -s localhost:15000/config_dump > config_dump.json执行以下命令,查看Sidecar配置文件的大小。du -sh config_dump.json预期输出1.2M config_dump.json在集群中部署了420个Pod的测试情况下,此时Sidecar的配置大小约为1.2M。考虑到集群中每个Pod都部署Sidecar,大量的Sidecar配置无疑加重了控制面的推送负担。(二)测试使用Sidecar资源优化前控制面的配置推送效率在ASM中为httpbin-0服务应用一个新的虚拟服务规则,可以触发控制面向数据面Sidear的一次配置推送,可以通过查看控制面日志内容来测试控制面在一次推送中的的配置推送效率。要查看控制面日志内容,请首先启用控制平面日志采集,请参考启用控制平面日志采集和日志告警。在服务网格中用下面的yaml新建一个针对httpbin-0服务进行超时处理的虚拟服务。有关如何新建虚拟服务,参见管理虚拟服务。apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-0-timeout namespace: default spec: hosts: - httpbin-0.default.svc.cluster.local http: - route: - destination: host: httpbin-0.default.svc.cluster.local timeout: 5s 进入控制平面日志项目的logstore,查看原始日志,可以将时间限定在15分钟(相对)来查看刚刚控制面产生的日志。预期日志内容021-12-01T10:20:09.708673Z info ads CDS: PUSH for node:httpbin-27-7dd8578b46-nkmvg.default resources:227 size:169.3kB 2021-12-01T10:20:09.710469Z info ads CDS: PUSH for node:httpbin-184-65d97797db-njst5.default resources:227 size:169.3kB 2021-12-01T10:20:09.713567Z info ads CDS: PUSH for node:httpbin-86-5b64586bbf-jv92w.default resources:227 size:169.3kB 2021-12-01T10:20:09.714514Z info ads LDS: PUSH for node:httpbin-86-5b64586bbf-jv92w.default resources:16 size:70.7kB 2021-12-01T10:20:09.792732Z info ads LDS: PUSH for node:httpbin-27-7dd8578b46-nkmvg.default resources:16 size:70.7kB 2021-12-01T10:20:09.792982Z info ads LDS: PUSH for node:httpbin-184-65d97797db-njst5.default resources:16 size:70.7kB 2021-12-01T10:20:09.796430Z info ads RDS: PUSH for node:httpbin-86-5b64586bbf-jv92w.default resources:8 size:137.4kB …… 2021-12-01T10:20:13.405850Z info ads RDS: PUSH for node:httpbin-156-68b85b4f79-2znmp.default resources:8 size:137.4kB 2021-12-01T10:20:13.406154Z info ads RDS: PUSH for node:httpbin-121-7c4cff97b9-sn5g4.default resources:8 size:137.4kB 2021-12-01T10:20:13.406420Z info ads CDS: PUSH for node:httpbin-161-7bc74c5fb5-ldgn4.default resources:227 size:169.3kB 2021-12-01T10:20:13.407230Z info ads LDS: PUSH for node:httpbin-161-7bc74c5fb5-ldgn4.default resources:16 size:70.7kB 2021-12-01T10:20:13.410147Z info ads RDS: PUSH for node:httpbin-161-7bc74c5fb5-ldgn4.default resources:8 size:137.4kB 2021-12-01T10:20:13.494840Z info ads RDS: PUSH for node:httpbin-57-69b756f779-db7vv.default resources:8 size:137.4kB在部署了420个Pod的测试环境下,可以发现新增一个虚拟服务会导致控制面向数据面的所有Sidecar推送变更,这其中会产生大量推送日志,且每次推送的数据量也比较大。最终在网格中应用一条虚拟服务规则导致了控制面长达约4秒的推送,这对于控制面来说是不可忽视的效率下降。(三)应用ASM的Sidecar资源参考《使用基于访问日志分析自动推荐的Sidecar资源》,为测试集群中的每个工作负载应用ASM为其推荐的Sidecar资源,以改善控制面的配置推送效率。(四)测试使用Sidecar资源优化后每个Sidecar的配置大小同样执行以下命令,下载httpbin-0 Pod的Sidecar配置到本地。kubectl exec -it httpbin-0-756995d867-jljgp -c istio-proxy -n ns-in-mesh -- curl -s localhost:15000/config_dump > config_dump.json同样执行以下命令,查看Sidecar配置文件的大小。du -sh config_dump.json预期输出105k config_dump.json在集群中部署了420个Pod的测试情况下,不难发现,在应用了Sidecar资源后,httpbin-0 Pod的Sidecar配置大小缩小了10倍以上,这无疑可以大大提高控制面向数据面Sidecar的配置推送效率。(二)测试使用Sidecar资源优化前控制面的配置推送效率重新在ASM中为httpbin-0服务应用上述虚拟服务规则,重新触发控制面向数据面Sidear的一次配置推送。在服务网格中删除并重新创建基于以下yaml的虚拟服务。有关如何管理虚拟服务,参见管理虚拟服务。apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: httpbin-0-timeout namespace: default spec: hosts: - httpbin-0.default.svc.cluster.local http: - route: - destination: host: httpbin-0.default.svc.cluster.local timeout: 5s 进入控制平面日志项目的logstore,查看原始日志,可以将时间限定在15分钟(相对)来查看刚刚控制面产生的日志。预期日志包含内容2021-12-01T12:12:43.498048Z info ads Push debounce stable[750] 1: 100.03379ms since last change, 100.033692ms since last push, full=true 2021-12-01T12:12:43.504270Z info ads XDS: Pushing:2021-12-01T12:12:43Z/493 Services:230 ConnectedEndpoints:421 Version:2021-12-01T12:12:43Z/493 2021-12-01T12:12:43.507451Z info ads CDS: PUSH for node:sleep-0-b68c8c5d9-5kww5.default resources:14 size:7.8kB 2021-12-01T12:12:43.507739Z info ads LDS: PUSH for node:sleep-0-b68c8c5d9-5kww5.default resources:3 size:15.5kB 2021-12-01T12:12:43.508029Z info ads RDS: PUSH for node:sleep-0-b68c8c5d9-5kww5.default resources:1 size:6.3kB不难发现,在应用了ASM推荐的Sidecar资源后,数据面的每个工作负载将不再关心与其没有依赖关系的服务相关变更。比如在此例中,在针对httpbin-0服务应用一条虚拟服务规则后,由于只有sleep-0应用与httpbin-0服务之间存在依赖关系,所以控制面仅向sleep-0 Pod的Sidecar推送了配置变更。同时,变更的数据量也缩小了约10倍,这些变化无疑都大幅度地提升了控制面向数据面的配置推送效率。在对文中的测试环境使用Sidecar资源进行优化之后,应用一条虚拟服务规则触发的配置推送仅持续了约0.01秒,相比优化前提升了约400倍的推送效率。
2022年07月