本文共 7569 字,大约阅读时间需要 25 分钟。
服务模型
首先,Istio作为一个(微)的平台,和其他的微服务模型一样也提供了Service,ServiceInstance这样抽象服务模型。如Service的定义中所表达的,一个服务有一个全域名,可以有一个或多个侦听端口。type Service struct { // Hostname of the service, e.g. "catalog.mystore.com"Hostname Hostnamejson:"hostname"
Address string json:"address,omitempty"
Addresses map[string]string json:"addresses,omitempty"
// Ports is the set of network ports where the service is listening for connectionsPorts PortList json:"ports,omitempty"
ExternalName Hostname json:"external"
...}当然这里的Service不只是mesh里定义的service,还可以是通过serviceEntry接入的外部服务。每个port的定义在这里:type Port struct { Name string json:"name,omitempty"
Port int json:"port"
Protocol Protocol json:"protocol,omitempty"
}除了port号外,还有 一个name和protocol。可以看到支持如下几个Protocolconst (ProtocolGRPC Protocol = "GRPC"ProtocolHTTPS Protocol = "HTTPS"ProtocolHTTP2 Protocol = "HTTP2"ProtocolHTTP Protocol = "HTTP"ProtocolTCP Protocol = "TCP"ProtocolUDP Protocol = "UDP"ProtocolMongo Protocol = "Mongo"ProtocolRedis Protocol = "Redis"ProtocolUnsupported Protocol = "UnsupportedProtocol")而每个服务实例ServiceInstance的定义如下type ServiceInstance struct { Endpoint NetworkEndpoint json:"endpoint,omitempty"
Service *Service json:"service,omitempty"
Labels Labels json:"labels,omitempty"
AvailabilityZone string json:"az,omitempty"
ServiceAccount string json:"serviceaccount,omitempty"
}熟悉SpringCloud的朋友对比下SpringCloud中对应interface,可以看到主要字段基本完全一样。public interface ServiceInstance { String getServiceId();String getHost();int getPort();boolean isSecure();URI getUri();Map<String, String> getMetadata();}以上的服务定义的代码分析,结合官方spec可以非常清楚的定义了服务发现的数据模型。但是,本身没有提供服务发现注册和服务发现的能力,翻遍代码目录也找不到一个存储服务注册表的服务。Discovery部分的文档是这样来描述的:对于服务注册,Istio认为已经存在一个服务注册表来维护应用程序的服务实例(Pod、VM),包括服务实例会自动注册这个服务注册表上;不健康的实例从目录中删除。而服务发现的功能是Pilot提供了通用的服务发现接口,供数据面调用动态更新实例。 即Istio本身不提供服务发现能力,而是提供了一种的机制来适配各种不同的平台。
多平台支持的Adpater机制
具体讲,Istio的服务发现在Pilot中完成,通过以下框图可以看到,Pilot提供了一种平台Adapter,可以对接多种不同的平台获取服务注册信息,并转换成Istio通用的抽象模型。服务发现的主要行为定义
服务发现的几重要方法方法和前面看到的Service的抽象模型一起定义在service中。,可以认为是Istio服务发现的几个主要行为。// ServiceDiscovery enumerates Istio service instances.type ServiceDiscovery interface { // 服务列表Services() ([]Service, error)// 根据域名的得到服务GetService(hostname Hostname) (Service, error)// 被InstancesByPort代替Instances(hostname Hostname, ports []string, labels LabelsCollection) ([]ServiceInstance, error)//根据端口和标签检索服务实例,最重要的以方法。InstancesByPort(hostname Hostname, servicePort int, labels LabelsCollection) ([]ServiceInstance, error)//根据proxy查询服务实例,如果是sidecar和pod装在一起,则返回该服务实例,如果只是装了sidecar,类似gateway,则返回空GetProxyServiceInstances(Proxy) ([]ServiceInstance, error)ManagementPorts(addr string) PortList}下面选择其中最简单也可能是大家最熟悉的Eureka的实现来看下这个adapter机制的工作过程主要流程分析
Controller对接不同平台维护服务发现数据
首先看Controller。在initServiceControllers根据不同的registry类型构造不同的conteroller实现。如对于Eureka的注册类型,构造了一个Eurkea的controller。case serviceregistry.EurekaRegistry:eurekaClient := eureka.NewClient(args.Service.Eureka.ServerURL)serviceControllers.AddRegistry(aggregate.Registry{ Name: serviceregistry.ServiceRegistry(r),ClusterID: string(serviceregistry.EurekaRegistry),Controller: eureka.NewController(eurekaClient, args.Service.Eureka.Interval),ServiceDiscovery: eureka.NewServiceDiscovery(eurekaClient),ServiceAccounts: eureka.NewServiceAccounts(),})可以看到controller里包装了Eureka的client作为句柄,不难猜到服务发现的逻辑正式这个client连Eureka的名字服务的server获取到。func NewController(client Client, interval time.Duration) model.Controller { return &controller{ interval: interval,serviceHandlers: make([]serviceHandler, 0),instanceHandlers: make([]instanceHandler, 0),client: client,}}可以看到就是使用EurekaClient去连EurekaServer去获取服务发现数据,然后转换成Istio通用的Service和ServiceInstance的数据结构。分别要转换convertServices convertServiceInstances,convertPorts,convertProtocol等。// InstancesByPort implements a service catalog operationfunc (sd serviceDiscovery) InstancesByPort(hostname model.Hostname, port int,tagsList model.LabelsCollection) ([]model.ServiceInstance, error) {apps, err := sd.client.Applications()
services := convertServices(apps, map[model.Hostname]bool{hostname: true})out := make([]model.ServiceInstance, 0)for _, instance := range convertServiceInstances(services, apps) { out = append(out, instance)}return out, nil}Eureka client或服务发现数据看一眼,其实就是通过Rest方式访问/eureka/v2/apps连Eureka集群来获取服务实例的列表。func (c client) Applications() ([]*application, error) {
req, err := http.NewRequest("GET", c.url+appsPath, nil)req.Header.Set("Accept", "application/json")resp, err := c.client.Do(req)data, err := ioutil.ReadAll(resp.Body)var apps getApplicationsif err = json.Unmarshal(data, &apps); err != nil { return nil, err}return apps.Applications.Applications, nil
}Application是本地对Instinstance对象的包装。type application struct { Name stringjson:"name"
Instances []*instance json:"instance"
}又看到了eureka熟悉的ServiceInstance的定义。当年有个同志提到一个方案是往metadata这个map里塞租户信息,在eureka上做多租。type instance struct { // nolint: malignedHostname string json:"hostName"
IPAddress string json:"ipAddr"
Status string json:"status"
Port port json:"port"
SecurePort port json:"securePort"
Metadata metadata json:"metadata,omitempty"
}以上我们就看完了服务发现数据生成的过程。对接名字服务的服务发现接口,获取数据,转换成Istio抽象模型中定义的标准格式。下面看下这些服务发现数据怎么提供出去被Envoy使用的。 总结
我们以官方文档上这张经典的图来端到端的串下整个服务发现的逻辑:注:文中代码基于commit:505af9a54033c52137becca1149744b15aebd4ba
转载于:https://blog.51cto.com/13831707/2152703