作者:尹航
作为业内首个全托管 Istio 兼容的阿里云服务网格产品 ASM,一开始从架构上就保持了与社区、业界趋势的一致性,控制平面的组件托管在阿里云侧,与数据面侧的用户集群独立。ASM 产品是基于社区 Istio 定制实现的,在托管的控制面侧提供了用于支撑精细化的流量管理和安全管理的组件能力。通过托管模式,解耦了 Istio 组件与所管理的 K8s 集群的生命周期管理,使得架构更加灵活,提升了系统的可伸缩性。从 2022 年 4 月 1 日起,阿里云服务网格 ASM 正式推出商业化版本, 提供了更丰富的能力、更大的规模支持及更完善的技术保障,更好地满足客户的不同需求场景,详情可点击原文链接查看。
服务网格是一个通过“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
这里就有一个有意思的事情了,虽然整套 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,即刻让您拥有简化的无侵入式服务治理能力!
从 2022 年 4 月 1 日起,阿里云服务网格 ASM 正式推出商业化版本, 提供了更丰富的能力、更大的规模支持及更完善的技术保障,更好地满足客户的不同需求场景。