转载

Mesher 集成 Istio 实践

Mesher 集成 Istio 实践

背景

Pilot是Isito的控制面组件,提供服务发现和配置管理功能。因为Istio使用Envoy作为数据面,因此Pilot实现了Envoy所定义的xDS API,作为xDS Server向Envoy提供服务信息和配置信息。本文讲解Pilot xDS API的一些细节,并介绍Mesher对接Pilot的一些实践。
Mesher脱胎于go-chassis,一个go语言的微服务SDK,提供了路由、负载均衡、容错熔断、限流等微服务治理核心能力,Mesher直接在代码层面引用go-chassis的核心能力,并在此基础上构建了作为网络代理的功能。Mesher的架构中,一些关键功能都做了接口定义和插件化的实现,包括控制面的服务发现、配置管理。当前Mesher的控制面可以接入Apache ServiceComb的服务发现组件Service Center,而配置管理则支持多种配置形式,包括文件、环境变量和命令行等。此外,由于插件化的设计,Mesher也实现了对开源配置管理中心Apollo的支持。目前的最佳实践如下图所示:
(控制面使用Service Center作为服务注册与发现组件,Apollo作为配置管理组件)
因此,在Mesher的构架下,对接Istio Pilot服务发现,只需开发Pilot插件并实现Mesher的服务发现接口即可。

为什么要和Istio集成

目前Istio的数据面只有Envoy一种选择,即Service Mesh技术,与Mesher等同。它解放了开发者,让开发者无需学习开发框架,只需开发自己的业务代码,在部署运行期即可变为云原生服务,这一切都很棒。但是go chassis作为一个分布式开发框架,为追求性能的开发者提供了另一个选择,让开发者能够在使用统一控制面的情况下,提升go语言项目的性能,而其余语言则使用service mesh技术接入。

xDS API

xDS API Envoy 定义的一系列发现服务,即 x Discovery Service 。对于服务发现而言,服务往往代表一个提供某项功能的 API ,由一系列具体的实例组成。在 xDS API 中,服务被定义为 Cluster ,每个 Cluster 对应的实例定义为 Endpoint

xDS API 分为 v1 v2 两个版本, v1 为基于 http 协议的 RESTful API ,而 v2 则使用 gRPC 协议。目前 v1 已经为 deprecated 状态, Istio1.0 版本也不再提供 v1 API ,因此本文主要讨论 v2 API

xDS API  协议定义中,除了各种资源的 DS 接口,还定义了 ADS Aggregated Discovery Service ,即聚合发现服务。通过 ADS ,可以获取服务的多种信息,而 Pilot 也是通过 ADS 接口为数据面提供信息的。

ADS 接口中,通过 TypeUrl 来指定需要获取的资源类型,每种资源类型对应的 TypeUrl 如下:

资源类型 TypeUrl
Cluster type.googleapis.com/envoy.api.v2.Cluster
Endpoint type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
Router type.googleapis.com/envoy.api.v2.RouteConfiguration
Listener type.googleapis.com/envoy.api.v2.Listener

ADS Server 发送请求时,除了指定资源类型,还要包括 Node 信息, VersionInfo Nonce 。下面我们一一进行分析。

Node

其中NodeInfo为sidecar所在节点的信息,可以根据具体的环境获取。NodeInfo包含Id和Cluster两个字段,Pilot中约定Id的格式为:

{type}~{ipAddress}~{id}~{domain}

NodeId包含四部分,以~划分。其中,type的类型为Sidecar, Ingress或Router,因为Mesher是作为数据面代理运行,因此type为Sidecar。当type为Sidecar时,ipAddr必须为一个有效的IP地址,一般我们在sidecar中获取当前Pod的IP地址作为ipAddr。id为Pod名称和namespace名称,以横线相连。最后一部分domain则为完整的namespace,例如istio-system.svc.cluster.local。

VersionInfo和Nonce

另外,Cluster和Listener都是全局的,在获取Cluster和Listener的时候,不需要指定资源名称。而Ednpoint和Router都对应具体的Cluster,因此获取Endpoint和Router时,需要指定相关Cluster的名称。

实战Pilot xDS API

现在,申请服务信息的请求数据都已经分析完毕。接下来,我们就调用Pilot xDS API来获取Cluster信息,为了方便阅读,所有错误处理都略过并显式的进行占位声明:

import apiv2core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core"
// 构建ADS资源clientconn, _ := grpc.Dial(client.PilotAddr, grpc.WithInsecure())adsClient := v2.NewAggregatedDiscoveryServiceClient(conn)adsResClient, _ := adsClient.StreamAggregatedResources(context.Background())// 构建xDS资源请求对象req := &apiv2.DiscoveryRequest{ TypeUrl: "type.googleapis.com/envoy.api.v2.Cluster", VersionInfo: time.Now().String(), ResponseNonce: time.Now().String(), Node: &apiv2core.Node{ Id: "sidecar~192.168.1.20~myservice~default.svc.cluster.local", Cluster: "myservice", }}// 发送请求并接收ADS资源_:= adsResClient.Send(req)resp, _ := adsResClient.Recv()resources := resp.GetResources()

获取到ADS资源后,resources变量的类型为protobuf Any类型的数组,需要使用protobuf将Any类型的变量解析为具体的ADS资源类型:

// 将ADS资源解析为Clustervar cluster apiv2.Clusterclusters := []apiv2.Cluster{}for _, res := range resources {    _ := proto.Unmarshal(res.GetValue(), &cluster)      clusters = append(clusters, cluster)}

至此,我们已经从Pilot中获取到Cluster信息。关于Cluster的详细定义和说明,可以参考github.com/envoyproxy/data-plane-api。在Pilot中,Cluster.Name由4部分组成:

direction|port|subset|host

其中direction为inbound或outbound,表示网络流量的方向。port为该Cluster监听的端口,在Kubernetes环境下,就是Service所暴露的端口,subset为Kubernetes中Subset的名称,一般在DestinationRule中定义,每个Subset对应一组标签,用于路由、负载均衡等。而host为Kubernetes Service的完整名称,如booking.default.svc.cluster.local。

因此,从Cluster.Name中,我们可以获取非常重要的信息:

  • 服务的名称

  • 服务对应的标签Subset

接下来,我们利用这些信息获取服务实例。

使用服务名称获取Endpoint

对于服务发现,获取服务名称后,需要根据名称获取对应的服务示例,在 xDS 中,就是获取 Endpoints 。我们继续使用 ADS API ,获取 Endpoint 信息:

// 构建ADS资源Client和之前是一致的// 构建req时,略有不同:req.TypeUrl =  "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment" // TypeUrl为Endpoint相关的Urlreq.ResourceNames = []string{clusterName} // Endpoints属于某个Cluster,要指定Cluster的名称

获取 Endpiont 时,我们指定 request.ResourcesNames ,将 cluster Name 传入,获取该 Cluster 所有的 Endpoint 。返回的 Response 实际类型为 ClusterLoadAssignment 。该结构嵌套层次比较多,获取实际的 IP 和端口的代码如下:

var loadAssignment apiv2.ClusterLoadAssignmentfor _, res := range resources {      if err := proto.Unmarshal(res.GetValue(), &loadAssignment); err != nil {            break      }}
endpionts := loadAssignment.Endpointsfor _, endpoint := range endpionts { for _, lbendpoint := range endpoint.LbEndpoints { socketAddress := lbendpoint.Endpoint.Address.GetSocketAddress() // 获取服务实例对应的地址和端口 addr := socketAddress.Address port := socketAddress.GetPortValue() }}

使用服务名称和Subset获取Endpoin t

当用户在Kubernetes中部署DestinationRule后,同一个服务会获取到多个Cluster,其中subset为空字符串的Cluster,对应所有服务实例。而带有subset的Cluster,对应subset中labels指定的一组特定服务实例。例如,为booking服务设置如下DestinationRule:

apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata:  name: booking-destinationrulespec:  host: booking  subsets:  - name: v1    labels:      version: v1  - name: v2    labels:      version: v2  - name: v3    labels:      version: v3

那么,我们获取Cluster时,会得到4个Cluster,其Host都以booking开头:

inbound|8090||booking.default.svc.cluster.localinbound|8090|v1|booking.default.svc.cluster.localinbound|8090|v2|booking.default.svc.cluster.localinbound|8090|v3|booking.default.svc.cluster.local

其中第一个Cluster对应3个实例,这里每个实例都是“概念”上的,只要Kubernetes中的Pod满足label标签条件,都会出现在实例列表中,因此一个实例可能最终对应多个运行的Pod。subset为v1的Cluster,对应label为version=v1的实例,subset v2 v3亦是如此。这样,当Mesher进行服务发现时,可以根据Consumer提供的tags与subset对应的label进行对比,返回tags指定的特定实例。

在xDS API中,仅能通过Cluster.Name获取subset的名称,并不能获取subset对应的标签,因此我们需要调用kuber-apiserver相关的API,通过subset名称获取对应的标签。

import "k8s.io/client-go/rest"
config, _ = rest.InClusterConfig()config.APIPath = "apis"config.GroupVersion = &schema.GroupVersion{ Group: "networking.istio.io", Version: "v1alpha3",}config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: serializer.NewCodecFactory(runtime.NewScheme())}k8sRestClient, _ := rest.RESTClientFor(config)
k8sClient.Get()req.Resource("destinationrules")req.Namespace(namespace)
result := req.Do()rawBody, _ := result.Raw()

获取rawBody之后,只需按照DestinationRule的格式进行解析即可,不再赘述。

实现Pilot服务发现的集成

至此,我们已经从Pilot中获取到服务发现所需的全部信息。包括Cluster,Endpoint以及相关标签的处理。接下来,只需要实现Mesher服务发现接口并提供插件即可。在Mesher服务发现的接口中,将注册与发现进行了分离,Registrator接口用于服务的注册,ServiceDiscovery用于服务的发现。由于Pilot已经从其他组件中获取了服务信息,因此我们仅需要实现ServiceDiscovery接口即可。

Mesher 集成 Istio 实践

Mesher的服务注册&发现接口设计

type ServiceDiscovery interface {      GetMicroServiceID(appID, microServiceName, version, env string) (string, error)      GetAllMicroServices() ([]*MicroService, error)      GetMicroService(microServiceID string) (*MicroService, error)      GetMicroServiceInstances(consumerID, microServiceName string) ([]*MicroServiceInstance, error)      FindMicroServiceInstances(consumerID, microServiceName string, tags utiltags.Tags) ([]*MicroServiceInstance, error)      AutoSync()      Close() error}

具体的实现细节我们不再赘述,ServiceDiscovery中的MicroService对应xDS API中的Cluster,MicroServiceInstance对应Endpoint。其中的3个关键函数,我们做一个简要的说明:

关键函数 实现
GetMicroService 调用ADS API获取Clusters,根据MicroServiceID查找匹配的Cluster,转换成MicroService并返回
GetMicroServiceInstances 参数microServiceName作为Cluster名称,查找对应的Endpiont,根据地址和端口组成MicroServiceInstance列表并返回
FindMicroServiceInstances 同GetMicroServiceInstances,但是需要根据tags参数与subset中的labels进行匹配,并返回复合匹配条件的MicroServiceInstance列表

具体的实现逻辑,可以参考 Mesher Pilot plugin的代码: https://github.com/go-mesh/mesher/blob/master/plugins/registry/istiov2/registry.go

至此,数据面代理Mesher集成Pilot的服务发现就已经实现了。基于Mesher的插件化设计,集成Istio Pilot时只需引入插件的包路径即可:

import _ "github.com/go-mesh/mesher/plugins/registry/istiov2"

并且在conf/chassis.yaml中指定服务发现的类型为pilotv2:

cse:  service:    registry:      registrator:        disabled: true # 关闭自注册      serviceDiscovery:        type: pilotv2        address: grpc://istio-pilot.istio-system:15010 # 指定pilot的地址

具体可以参考 go-chassis集成Pilot示例: https://github.com/go-chassis/go-chassis-examples/tree/master/pilot-v2 mesher集成Pilot示例: https://github.com/go-mesh/mesher-examples/tree/master/pilotv2-example 。因为go-chassis的插件化设计,使得SDK和mesher sidecar都可以很方便的接入Pilot服务发现。

至此,我们已经完成Pilot服务发现的集成。xDS API提供了非常丰富的内容,除了服务发现,还包括路由规则、服务治理配置等等。在后续的文章中,我们会进一步介绍xDS API以及Pilot的相关集成。敬请大家关注!

Mesher 集成 Istio 实践

往期精彩回顾

ServiceComb Alpha 集群动态主节点实现

基于CSE的微服务架构实践-Spring Boot技术栈选型

单体应用微服务改造实践

Mesher 集成 Istio 实践

扫码加群

Mesher 集成 Istio 实践

更多精彩

Mesher 集成 Istio 实践

Mesher 集成 Istio 实践

Mesher 集成 Istio 实践

好看你就 点点

Mesher 集成 Istio 实践

“阅读原文” 给ServiceComb点个“Star”吧 ~

原文  https://mp.weixin.qq.com/s?__biz=MzUxNTEwNTg5Mg==&mid=2247486312&idx=1&sn=56e3633c6f397bdf3f5a9b12fe467f8e&chksm=f9bafe63cecd77755a053c9d758dcdbd48e36762e1ef1e6e3f3bde3e4cbbc031b8fc11a71f52&token=1122982385&lang=zh_CN
正文到此结束
Loading...