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
Clustertype.googleapis.com/envoy.api.v2.Cluster
Endpointtype.googleapis.com/envoy.api.v2.ClusterLoadAssignment
Routertype.googleapis.com/envoy.api.v2.RouteConfiguration
Listenertype.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

VersionInfo和Nonce分别代表xDS客户端最近一次申请的资源的版本信息和Nonce随机值。客户端首次申请ADS资源时,VersionInfo和Nonce为空,Pilot返回资源时,会返回资源对应的VersionInfo和Nonce,客户端需要保存VersionInfo和Nonce,当再次请求资源时,带上这两个值,以便xDS API Server对服务的状态进行管理。在Pilot的实现中,VersionInfo和Nonce都是以当前时间戳作为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获取Endpoint

当用户在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的服务注册&发现接口设计

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的相关集成。敬请大家关注!

往期精彩回顾

ServiceComb Alpha 集群动态主节点实现

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

单体应用微服务改造实践

扫码加群

更多精彩

好看你就点点我

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netgen是一个开源的三维网格生成器,可用于生成具有复杂几何形状的高质量网格,适用于各种工程和科学应用。Netgen Mesher GUI是Netgen的图形用户界面,它提供了一个直观和用户友好的方式来进行网格生成。 Netgen Mesher GUI具有一系列功能和工具,使用户可以轻松地导入几何模型、设置网格参数并生成网格。它支持多种几何文件格式,如STL、STEP、IGES等,用户可以直接导入这些文件并通过GUI对其进行处理。此外,GUI还提供了丰富的几何操作工具,如平移、旋转、缩放和修剪等,使用户可以对几何模型进行修改和调整。 在设置网格参数方面,GUI提供了一些选项和控件,使用户可以自定义生成的网格。用户可以设置网格分辨率、边界条件、网格类型等。此外,GUI还提供了一些自动调整参数的功能,帮助用户更好地生成适合其应用的网格。 当用户完成参数设置后,他们可以通过简单地点击生成按钮来生成网格。生成过程将在界面中显示,并且用户可以实时查看生成的网格的预览。如果用户对生成的网格不满意,他们可以调整参数并再次生成,直到达到他们的要求。 总的来说,Netgen Mesher GUI提供了一个功能强大且易于使用的界面,使用户能够轻松地进行网格生成。它的直观性和用户友好性使得即使没有深入了解网格生成算法也可以使用它进行网格生成。无论是初学者还是专业人士,都可以通过Netgen Mesher GUI方便地进行高质量网格的生成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值