Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制

服务模型

首先,Istio作为一个(微)服务治理的平台,和其他的微服务模型一样也提供了Service,ServiceInstance这样抽象服务模型。如Service的定义中所表达的,一个服务有一个全域名,可以有一个或多个侦听端口。

type Service struct {

   // Hostname of the service, e.g. "catalog.mystore.com"
   Hostname Hostname `json:"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 connections
   Ports 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。可以看到支持这么几个Protocol

const (

   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< span="" style="word-wrap: break-word;box-sizing: border-box;outline: none;-webkit-appearance: none;word-break: break-word;-webkit-tap-highlight-color: transparent;">, String> getMetadata();
 }</string<><string< span="" style="word-wrap: break-word;box-sizing: border-box;outline: none;-webkit-appearance: none;word-break: break-word;-webkit-tap-highlight-color: transparent;"></string<>

以上的服务定义的代码分析,结合官方spec可以非常清楚的定义了服务发现的数据模型。但是,Istio本身没有提供服务发现注册和服务发现的能力,翻遍代码目录也找不到一个存储服务注册表的服务。Discovery部分的文档是这样来描述的:

对于服务注册,Istio认为已经存在一个服务注册表来维护应用程序的服务实例(Pod、VM),包括服务实例会自动注册这个服务注册表上;不健康的实例从目录中删除。而服务发现的功能是Pilot提供了通用的服务发现接口,供数据面调用动态更新实例。

即:Istio本身不提供服务发现能力,而是提供了一种adapter的机制来适配各种不同的平台。

多平台支持的Adpater机制

具体讲,Istio的服务发现在Pilot中完成,通过以下框图可以看到,Pilot提供了一种平台Adapter,可以对接多种不同的平台获取服务注册信息,并转换成Istio通用的抽象模型。

从pilot的代码目录也可以清楚看到,至少支持consul、k8s、eureka、cloudfoundry等平台。

服务发现的主要行为定

 服务发现的几重要方法方法和前面看到的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机制的工作过程.

 主要流程分析

1.服务发现服务入口

Pilot有三个独立的服务分别是agent,discovery和sidecar-injector。分别提供sidecar的管理,服务发现和策略管理,sidecar自动注入的功能。Discovery的入口都是pilot的pilot-discovery。

在service初始化时候,初始化ServiceController 和 DiscoveryService。

if err := s.initServiceControllers(&args); err != nil {
  returnnil, err
}
if err := s.initDiscoveryService(&args); err != nil {
  returnnil, err
}

前者是构造一个controller来构造服务发现数据,后者是提供一个DiscoveryService,发布服务发现数据,后面的分析可以看到这个DiscoveryService向Envoy提供的服务发现数据正是来自Controller构造的数据。我们分开来看。

2.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,
   }
}

ServiceDiscovery中定义的几个重要方法,我们拿最重要的InstancesByPort来看下在Eureka下是怎么支持,其他的几个都类似。可以看到就是使用Eureka client去连Eureka server去获取服务发现数据,然后转换成istio通用的Service和ServiceInstance的数据结构。分别要转换convertServices convertServiceInstances convertPorts convertProtocol等。

// InstancesByPort implements a service catalog operation
func (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 getApplications
   if err = json.Unmarshal(data, &apps); err != nil {
      return nil, err
   }
   return apps.Applications.Applications, nil
}

Application是本地对Instinstance对象的包装。

type application struct {
Name string `json:"name"`
Instances []*instance `json:"instance"`
} 

又看到了eureka熟悉的ServiceInstance的定义。当年有个同志提到一个方案是往metadata这个map里塞租户信息,在eureka上做多租。

type instance struct { // nolint: maligned
   Hostname   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使用的。

3.DiscoveryService 发布服务发现数据

在pilot server初始化的时候,除了前面初始化了一个controller外,还有一个重要的initDiscoveryService初始化Discoveryservice。

environment := model.Environment{
   Mesh:             s.mesh,
   IstioConfigStore: model.MakeIstioStore(s.configController),
   ServiceDiscovery: s.ServiceController,
   ..
}
…
s.EnvoyXdsServer = envoyv2.NewDiscoveryServer(environment, v1alpha3.NewConfigGenerator(registry.NewPlugins()))
s.EnvoyXdsServer.Register(s.GRPCServer)
..

即构造gRPC server提供了对外的服务发现接口。DiscoveryServer定义如下

//Pilot支持Evnoy V2的xds的API
type DiscoveryServer struct {
   // env is the model environment.
   env model.Environment
   ConfigGenerator *v1alpha3.ConfigGeneratorImpl
   modelMutex      sync.RWMutex
   services        []*model.Service
   virtualServices []*networking.VirtualService
   virtualServiceConfigs []model.Config
}

即提供了这个grpc的服务发现Server,sidecar通过这个server获取服务发现的数据,而server使用到的各个服务发现的功能通过Environment中的ServiceDiscovery句柄来完成。从前面environment的构造可以看到这个ServiceDiscovery正是上一个init构造的controller。

// Environment provides an aggregate environmental API for Pilot
type Environment struct {
   // Discovery inte**ce for listing services and instances.
   ServiceDiscovery 

DiscoveryServer在如下文件中开发了对应的接口,即所谓的XDS API,可以看到这些API都定义在envoyproxy/go-control-plane/envoy/service/discovery/v2 下面,即对应数据面服务发现的标准API。Pilot和很Envoy这套API的通信方式,包括接口定义我们在后面详细展开。

这样几个功能组件的交互会是这个样子。

  1. Controller使用EurekaClient来获取服务列表,提供转换后的标准的服务发现接口和数据结构。
  2. Discoveryserver基于Controller上维护的服务发现数据,发布成gRPC协议的服务供Envoy使用。

非常不幸的是,码完这篇文字码完的时候,收到社区里merge了这个PR :因为Eureka v2.0 has been discontinued,Istio服务发现里removed eureka adapter 。即1.0版本后再也看不到Istio对Eureka的支持了。这里描述的例子真的就成为一个例子了。

总结

我们以官方文档上这张经典的图来端到端的串下整个服务发现的逻辑:

  1. Pilot中定义了Istio通用的服务发现模型,即开始分析到的几个数据结构;
  2. Pilot使用adapter方式对接不同的(云平台的)的服务目录,提取服务注册信息;
  3. Pilot使用将2中服务注册信息转换成1中定义的自定义的数据结构。
  4. Pilot提供标准的服务发现接口供数据面调用。
  5. 数据面获取服务服务发现数据,并基于这些数据更新sidecar后端的LB实例列表,进而根据相应的负载均衡策略将请求转发到对应的目标实例上。

文中着重描述以上的通用模板流程和一般机制,很多细节忽略掉了。后续根据需要对于以上点上的重要功能会展开。如以上2和3步骤在Kubernetes中如何支持将在后面一篇文章《Istio 技术与实践02:Istio源码分析之Istio+Kubernetes的服务发现》中重点描述,将了解到在Kubernetes环境下,Istio如何使用Pilot服务发现的Adapter方式集成Kubernetes的Service资源,从而解决长久以来在Kubernetes上运行微服务使用两套名字服务的尴尬局面。

原文地址:https://www.cnblogs.com/CCE-SWR/p/9989869.html

时间: 2024-11-05 23:22:10

Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制的相关文章

Java并发编程高阶技术 高性能并发框架源码解析与实战

第1章 课程介绍(Java并发编程进阶课程)什么是Disruptor?它一个高性能的异步处理框架,号称"单线程每秒可处理600W个订单"的神器,本课程目标:彻底精通一个如此优秀的开源框架,面试秒杀面试官.本章会带领小伙伴们先了解课程大纲与重点,然后模拟千万,亿级数据进行压力测试.让大家感性认知到Disruptor的强大.... 第2章 并发编程框架核心讲解本章带大家学习并发编程框架的基本使用与API,并介绍其内部各种组件的原理和运行机制.从而为后面的深入学习打下坚实的基础.如果对Dis

Curator源码解析(五)Curator的连接和重试机制

转载请注明出处: jiq?钦's technical Blog 本文将主要关注Curator是如何处理连接丢失和会话终止这两个关键问题的. 1.   连接丢失的处理 Curator中利用类ConnectionState来管理客户端到ZooKeeper集群的连接状态,其中用到原子布尔型变量来标识当前连接是否已经建立: private finalAtomicBoolean isConnected= newAtomicBoolean(false); 在事件处理函数中(ConnectionState实现

线程池技术之:ThreadPoolExecutor 源码解析

java中的所说的线程池,一般都是围绕着 ThreadPoolExecutor 来展开的.其他的实现基本都是基于它,或者模仿它的.所以只要理解 ThreadPoolExecutor, 就相当于完全理解了线程池的精髓. 其实要理解一个东西,一般地,我们最好是要抱着自己的疑问或者理解去的.否则,往往收获甚微. 理解 ThreadPoolExecutor, 我们可以先理解一个线程池的意义: 本质上是提供预先定义好的n个线程,供调用方直接运行任务的一个工具. 线程池解决的问题: 1. 提高任务执行的响应

Spring-cloud &amp; Netflix 源码解析:Eureka 服务注册发现接口 ****

http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=referral *************************** 先关注下netflix eureka server 原生提供的接口.https://github.com/Netflix/eureka/wiki/Eureka-REST-operations 这是对非java的服务使用eureka时可以使用的r

Django settings源码解析

Django settings源码 Django中有两个配置文件 局部配置:配置文件settings.py,即项目同名文件夹下的settings.py文件 全局配置:django内部全局的配置文件settings.py,需要导入才能看到 from django.conf import settings # 是一个对象,单例模式 from django.conf import global_settings # 真正的默认配置文件 特点: 先加载全局配置,再加载局部配置,以局部优先 源码解析 点进

Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析

王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 腾讯微云:http://url.cn/TnGbdC 360云盘:http://yunpan.cn/cQ4c2UALDjSKy 访问密码 45e2土豆:http://www.tudou.com/programs/view/NGgUD5FBQaA/优酷:http://v.youku.com/v_show/id_

Spark技术内幕:Worker源码与架构解析

首先通过一张Spark的架构图来了解Worker在Spark中的作用和地位: Worker所起的作用有以下几个: 1. 接受Master的指令,启动或者杀掉Executor 2. 接受Master的指令,启动或者杀掉Driver 3. 报告Executor/Driver的状态到Master 4. 心跳到Master,心跳超时则Master认为Worker已经挂了不能工作了 5. 向GUI报告Worker的状态 说白了,Worker就是整个集群真正干活的.首先看一下Worker重要的数据结构: v

[Spark內核] 第42课:Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践

本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 Executor 中的每个并行执行的Task (100万个Task) 都要查询这张表的话,那我们通过 Broadcast 的方式就只需要往每个Executor 把这张表发送一次就行了,Executor 中的每个运行的 Task 查询这张唯一的表,而不是每次执行的时候都从 Driver 中获得这张表

SPRING技术内幕,Spring源码深度解析

 SPRING技术内幕,Spring源码深度解析 SPRING技术内幕:深入解析SPRING架构与设计原理(第2版)[带书签].pdf: http://www.t00y.com/file/78131650 Spring源码深度解析 [郝佳编著] sample.pdf: http://www.t00y.com/file/78131634 [jingshuishenliu.400gb.com]Spring Data.pdf: http://www.t00y.com/file/78256084 [