前言
服务网格是一个通过“Sidecar”模式进行服务治理简化的平台。整个服务网格可以划分为包括核心组件Istiod的“控制面”以及包括了每个服务的Sidecar的“数据面”。如果各位使用过服务网格,相信对上面的概念也算是略有了解了。
在服务网格Istio中,我们知道每个Sidecar都是一个envoy应用,内部有着包含着listener、route、cluster、secret等部分的完整配置;envoy就是根据这些配置来改变自身行为,实现不同的流量控制手段的。而控制面组件的主要任务,就是将网格中的VirtualService、DestinationRule等资源“翻译”成envoy的具体配置,并将配置发送给数据面的每个envoy,我们简称这个过程为“配置推送”。
在envoy中,cluster配置对应着envoy内部的“集群”这个概念,一个cluster是指“Envoy 连接的一组逻辑相同的上游主机”,其实大多数时候也就是对应着一个具体的服务。
如果我们实际查看envoy的完整配置会发现,我们在什么都没做的时候,envoy内部就已经包含着一些动态下发的cluster了,也就是说,Sidecar自动发现并包含了集群中的服务信息。这里就引出一个核心问题:服务网格的“服务发现”是如何完成的呢?我们通常会用“网格内服务”来称呼对应Pod注入了Sidecar的服务;那么,是不是说服务网格会将注入了Sidecar的“网格内服务”自动的加入每个Sidecar的cluster配置呢?
今天这篇文章就来主要聊一聊服务网格的“服务发现”和“配置推送”。
服务网格的“服务发现”
一个简单的小实验就可以解答上面的这些问题。我们在Kubernetes集群中安装服务网格平台(可以使用aliyun ASM + ACK来快速搭建服务网格平台),然后在default默认命名空间之外,再建立一个test命名空间。
然后,我们为default命名空间开启Sidecar自动注入,也就是说,default命名空间内的服务将自动成为“网格内”的服务,而test命名空间内的服务将不会注入Sidecar,也就是“网格外”的服务。
在服务网格ASM中,我们可以在控制台的“全局命名空间”管理中来方便地开启和关闭自动注入(开启/关闭后别忘了同步一下自动注入哦):
我们可以随意在这两个命名空间内部署一些中意的服务,我在这里向default命名空间部署了一个sleep服务。这个服务就如字面意思,里面是一个curl容器,并且一进去就一直在睡大觉 ( ̄o ̄).zZ ,可以进入这个服务的Pod方便地curl其它服务。
在Istio的社区官方github仓库中,可以找到sleep这个示例服务的YAML文件:istio/samples/sleep/sleep.yaml
,我们可以拷贝这个文件后执行:
kubectl apply -f sleep.yaml
同理,在test命名空间下,我们随便部署点啥服务,我这里部署了一个Istio使用者的老朋友bookinfo
,我们同样可以在Istio的官方github中找到它的YAML文件:istio/samples/bookinfo/platform/kube/bookinfo.yaml
,将其拷贝后执行:
kubectl apply -f bookinfo.yaml -n test
顺带一提,为了不让sleep太寂寞,我还在default命名空间部署了一个httpbin应用陪他,不过不部署也无所谓┐(゚~゚)┌。
我们现在准备好了,大家可能猜到接下来要干啥了。我们就来看看——这个Sidecar里都有哪些cluster配置。
如果你使用Istio,可以用istioctl命令行工具方便地查看Sidecar中的各项配置的总结信息;在服务网格ASM中这招可能行不通,不过ASM也有一个能够部分兼容的命令行工具asmctl,我们可以进入阿里云帮助中心,搜索asmctl
,找到“安装和使用诊断工具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官方在社区文章《Traffic Management》中,有对这一过程进行解释:
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配置了)。
此外,如果你查看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的功能就能正常工作。
当然,实际使用中,服务网格的其它资源可能生效于入站流量配置之中,此时请求接收方也必须注入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里记录的那些林林总总的服务到底是如何发现的。同时,Istiod作为服务网格中负责配置推送的核心组件,做的却是修改翻译和修改配置这样的后台工作,也导致网格的用户通常很难意识到控制面组件到底在进行什么工作,以及现有的实践是否给控制面组件造成了过大的多余负担。
希望这篇文章能让更多的服务网格用户意识到,配置推送的优化也是网格平台维护的重要一环。使用“选择性服务发现”的话,仅用1分钟的时间就能够大幅度优化网格的配置优化,减少使用网格时无谓的隐患,岂不美哉?
对于大多数用户来说,“选择性服务发现”就已经足够对配置推送进行优化了。不过如果您想做的更“绝”,还可以直接使用服务网格的Sidecar资源来对Sidecar的配置进行最大限度地优化。服务网格ASM针对Sidecar资源这一机制,也提供了基于访问日志分析自动推荐的Sidecar资源来帮助客户进行使用上的深度简化。欢迎使用服务网格ASM,即刻让您拥有简化的无侵入式服务治理能力!