服务模型

首先,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, String> getMetadata();
 }

以上的服务定义的代码分析,结合官方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 {
    return nil, err
 }
 if err := s.initDiscoveryService(&args); err != nil {
    return nil, 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,
    }
 }

可以看到就是使用EurekaClient去连EurekaServer去获取服务发现数据,然后转换成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 interface 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实例列表,进而根据相应的负载均衡策略将请求转发到对应的目标实例上。

注:文中代码基于commit:505af9a54033c52137becca1149744b15aebd4ba

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

  1. [源码解析] 并行分布式框架 Celery 之 容错机制

    [源码解析] 并行分布式框架 Celery 之 容错机制 目录 [源码解析] 并行分布式框架 Celery 之 容错机制 0x00 摘要 0x01 概述 1.1 错误种类 1.2 失败维度 1.3 应 ...

  2. [源码解析] 从TimeoutException看Flink的心跳机制

    [源码解析] 从TimeoutException看Flink的心跳机制 目录 [源码解析] 从TimeoutException看Flink的心跳机制 0x00 摘要 0x01 缘由 0x02 背景概念 ...

  3. Apache DolphinScheduler 2.X保姆级源码解析,中国移动工程师揭秘服务调度启动全流程

    2022年1月,科学技术部高新技术司副司长梅建平在"第六届中国新金融高峰论坛"上表示,当前数据量已经大大超过了处理能力的上限,若信息技术仍然是渐进式发展,则数据处理能力的提升将远远 ...

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

    java中的所说的线程池,一般都是围绕着 ThreadPoolExecutor 来展开的.其他的实现基本都是基于它,或者模仿它的.所以只要理解 ThreadPoolExecutor, 就相当于完全理解 ...

  5. skywalking7 源码解析 (3) :agent启动服务分析以及性能影响

    skywalking必看的文章,转载自https://blog.csdn.net/u010928589/article/details/106608864/

  6. Istio技术与实践02:源码解析之Istio on Kubernetes 统一服务发现

    前言 文章Istio技术与实践01: 源码解析之Pilot多云平台服务发现机制结合Pilot的代码实现介绍了Istio的抽象服务模型和基于该模型的数据结构定义,了解到Istio上只是定义的服务发现的接 ...

  7. Android源码解析系列

    转载请标明出处:一片枫叶的专栏 知乎上看了一篇非常不错的博文:有没有必要阅读Android源码 看完之后痛定思过,平时所学往往是知其然然不知其所以然,所以为了更好的深入Android体系,决定学习an ...

  8. [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle

    [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Mingle 目录 [源码解析] 并行分布式框架 Celery 之 Lamport 逻辑时钟 & Ming ...

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

    http://www.idouba.net/spring-cloud-source-eureka-client-api/?utm_source=tuicool&utm_medium=refer ...

随机推荐

  1. UVALive 4394 String painter ——(区间DP)

    其实这个dp过程有点似懂非懂...代码如下: #include <stdio.h> #include <algorithm> #include <string.h> ...

  2. cs配合msf批量探测内网MS17-010漏洞

    第一步 Cobalt strike 派生 shell 给 MSF(前提有个beacon shell) 第二步 选择要派生的beacon,右键-->增加会话,选择刚刚配置的foreign监听器 第 ...

  3. jenkins权限问题

    今天用jenkins的时候,构建失败,看了下控制台输出,提示是缺少权限,以前也遇到过这个问题,当时是通过把相关文件夹权限设置为777解决的,这种办法有两个不好的地方,一是这样一来任何用户都能操作这个文 ...

  4. 走进JavaWeb技术世界1:Web后端与J2EE的由来

    转自:微信公众号 码农翻身 这个问题来自于QQ网友,一句两句说不清楚,索性写个文章. 我刚开始做Web开发的时候,根本没有前端,后端之说. 原因很简单,那个时候服务器端的代码就是一切:接受浏览器的请求 ...

  5. 去除IntelliJ IDEA对重复代码的检测

    方法1:  方法2:(比较简便) 

  6. python+Django+mysql环境搭建

    为什么我的毕业设计还要用到网站啊啊啊啊.什么鬼啊,又要做爱拍拍又要做网站???饶了我啊..我选择狗带.. 网站就用django做吧,毕竟之前做过一个电脑销售网站,希望能借鉴一下经验什么的,不要一切从头 ...

  7. matplotlib画图报错This figure includes Axes that are not compatible with tight_layout, so results might be incorrect.

    之前用以下代码将实验结果用matplotlib show出来 plt.plot(np.arange(len(aver_reward_list)), aver_reward_list) plt.ylab ...

  8. Flutter控制某个TextField获取焦点及失去焦点

    在项目中有时需要点击某个地方的时候让一个文本框获取焦点以弹起键盘~~比如前端经常使用的input.focus(),但是在flutter中没有.focus()这个方法~~不过我们可以通过FocusSco ...

  9. 周志华-机器学习西瓜书-第三章习题3.5 LDA

    本文为周志华机器学习西瓜书第三章课后习题3.5答案,编程实现线性判别分析LDA,数据集为书本第89页的数据 首先介绍LDA算法流程: LDA的一个手工计算数学实例: 课后习题的代码: # coding ...

  10. Scrapy - 小说爬虫

    实例解析 - 小说爬虫 页面分析 共有三级页面 一级页面 大目录 二级页面 章节目录 三级界面 章节内容 爬取准备 一级界面 http://www.daomubiji.com/ 二级页面xpath 直 ...