1 - xDS概述

介绍Envoy的XDS API

在 Envoy 中,xDS 被称为数据平面API,是控制平面(如Istio)和数据平面之间的通讯协议。

xDS的含义

xDS 是指 “X Discovery Service”,这里的 “X” 代指多种服务发现协议,包括:

简写 全称 描述
LDS Listener Discovery Service 监听器发现服务
RDS Route Discovery Service 路由发现服务
CDS Cluster Discovery Service 集群发现服务
EDS Endpoint Discovery Service 集群成员发现服务
ADS Aggregated Discovery Service 聚合发现服务
HDS Health Discovery Service 健康度发现服务
SDS Secret Discovery Service 密钥发现服务
MS Metric Service 指标服务
RLS Rate Limit Service 限流发现服务
LRS Load Reporting service 负载报告服务
RTDS Runtime Discovery Service 运行时发现服务
CSDS Client Status Discovery Service 客户端状态发现服务
ECDS Extension Config Discovery Service 扩展配置发现服务
xDS X Discovery Service 以上诸多API的统称

备注:

  1. SDS 在 xDS v1版本中指的是 Service Discovery Service,后来 SDS 改名 Endpoint Discovery Service/EDS。再后来增加了 Security Discovery Service.
  2. 后来新增了一些协议,名字不再是 Discovery Service,但也归入xDS的名下

xDS的版本

目前 xDS 主要有三个版本:

  • v1: 最早的版本,基于传统的REST-JSON API。
  • v2: 基于 Protobuf 和双 REST/gRPC ,v2 API在 2020 年底停止使用,在2021年初完全停止支持
  • v3: 目前正在支持的版本。

参见:Supported API versions — envoy 1.20.0 documentation (envoyproxy.io)

xDS的格式

https://www.envoyproxy.io/docs/envoy/latest/configuration/overview/introduction

Envoy xDS APIs在api树中被定义为proto3 Protocol Buffer。它们支持:

  • 通过gRPC流式交付xDS API更新。这减少了资源需求,可以降低更新延迟。

  • 一个新的REST-JSON API,其中的JSON/YAML格式是通过proto3的canonical JSON映射机械地导出的。

  • 通过文件系统、REST-JSON或gRPC端点交付更新。

  • 通过扩展的端点分配API和向管理服务器报告负载和资源利用情况来实现高级负载平衡。

  • 需要时更强的一致性和排序属性。这些API仍然保持基线最终一致性模型。

有关Envoy和管理服务器之间xDS消息交换方面的进一步细节,请参见xDS协议描述。

2 - xDS术语和概念

xDS术语和概念介绍

2.1 - xDS基本术语

xDS的基本术语:主机、上游、下游、监听器、集群、网格、运行时配置

在深入了解xDS之前,必须了解 xds 和 envoy 使用的术语。

基本术语

官方文档

以下内容来自envoy官方文档:

https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/intro/terminology

A few definitions before we dive into the main architecture documentation. Some of the definitions are slightly contentious within the industry, however they are how Envoy uses them throughout the documentation and codebase, so c’est la vie.

在我们深入了解主要的架构文档之前,有几个定义。其中一些定义在行业内略有争议,但这些定义是Envoy在整个文档和代码库中的使用方式,所以这就是生活。

Host: An entity capable of network communication (application on a mobile phone, server, etc.). In this documentation a host is a logical network application. A physical piece of hardware could possibly have multiple hosts running on it as long as each of them can be independently addressed.

主机: 一个能够进行网络通信的实体(手机上的应用程序、服务器等)。在本文档中,主机是逻辑网络应用。一个物理硬件上可能有多个主机在运行,只要它们能独立寻址。

Downstream: A downstream host connects to Envoy, sends requests, and receives responses.

下游:下游主机连接到Envoy,发送请求并接收响应。

Upstream: An upstream host receives connections and requests from Envoy and returns responses.

上游: 上游主机接收来自Envoy的连接和请求并返回响应。

Listener: A listener is a named network location (e.g., port, unix domain socket, etc.) that can be connected to by downstream clients. Envoy exposes one or more listeners that downstream hosts connect to.

监听器: 监听器是命名的网络位置(例如:端口、unix domain socket等),可以被下游客户端连接。Envoy暴露一个或多个监听器,下游主机可以连接到这些监听器。

Cluster: A cluster is a group of logically similar upstream hosts that Envoy connects to. Envoy discovers the members of a cluster via service discovery. It optionally determines the health of cluster members via active health checking. The cluster member that Envoy routes a request to is determined by the load balancing policy.

集群:集群是Envoy连接到的逻辑上相似的一组上游主机。Envoy通过服务发现来发现集群的成员。它可以选择通过主动健康检查来确定集群成员的健康状况。Envoy通过负载平衡策略决定将请求路由到某个集群成员。

Mesh: A group of hosts that coordinate to provide a consistent network topology. In this documentation, an “Envoy mesh” is a group of Envoy proxies that form a message passing substrate for a distributed system comprised of many different services and application platforms.

网格:协调提供一致网络拓扑结构的一组主机。在本文档中,“Envoy mesh"是一组Envoy代理,它们构成了分布式系统的消息传递基础,这个分布式系统由很多不同服务和应用程序平台组成。

Runtime configuration: Out of band realtime configuration system deployed alongside Envoy. Configuration settings can be altered that will affect operation without needing to restart Envoy or change the primary configuration.

运行时配置:外置实时配置系统,和 Envoy 一起部署。可以更改配置设置,影响操作,而无需重启 Envoy 或更改主要配置。

对术语的理解

下面这个图可以更好的帮助理解上述术语的概念和含义:

  • 主机/上游/下游:请求由下游主机发起,envoy通过监听器接收到请求之后转发给上游主机
  • 符合转发要求的上游主机可能有多个,这多个上游主机被称为"集群”,envoy通过负载均衡算法选择其中进行请求转发

其他术语

和xDS相关的其他概念还有:

概念 描述
Management Server 实现v2 Envoy API的逻辑服务器。 这不一定是单个物理机器,因为它可以被复制/分片, 并且用于不同xDS API的API服务可以在不同的物理机器上实现。

区域概念

Concept 概念 描述
Locality 区域性 Envoy实例或端点运行的位置。这包括地域/region,分区/zone和子分区/sub-zone标识。
Region 地域 分区(zone)所在的地理区域。
Zone 分区 AWS中的Availability Zone (AZ), GCP中的Zone
Sub-zone 子分区 Envoy实例或端点在分区内运行的位置。这允许在分区内存在多个负载均衡目标。

术语的中文翻译

envoy和xds的术语在翻译中文时,为了保持一致,建议遵循envoy文档翻译小组的约定:

https://github.com/cloudnativeto/envoy/blob/zh/docs/root/term.md

参考文档

2.2 - xDS核心概念

xDS的核心概念: 监听器、路由、集群和端点

监听器、路由、集群和端点是xDS的四个核心概念。

Envoy代理模式:请求转发

xDS的术语中定义了主机、上游、下游等概念,这些是envoy作为代理的基本工作模式:请求转发。即接收一个来自下游主机的请求,然后根据各种逻辑进行处理和决策,最后将请求转发给某个上游主机。

请求转发相关的xDS概念

Concept 概念 描述
Listener 监听器 监听器是命名网地址(例如,端口、unix domain socket等),可以被下游客户端连接。Envoy 暴露一个或者多个监听器给下游主机连接
Router 路由 路由是一组将虚拟主机(virtual hosts)与群集(cluster)匹配的规则(rule),允许您创建流量转移规则
Cluster 集群 集群是指 Envoy 连接到的逻辑上相同的一组上游主机
Endpoint 端点 Envoy将“端点(Endpoint)”定义为群集(Cluster)中可用的IP和端口

转发概念示意图如下:

Listener

Listener:Envoy工作的基础 简单理解,Listener是Envoy打开的一个监听端口,用于接收来自Downstream(客户端)连接。Envoy可以支持复数个Listener。多个Listener之间几乎所有的配置都是隔离的。Listener配置中核心包括监听地址、Filter链等。

Listener对应的配置/资源发现服务称之为LDS。LDS是Envoy正常工作的基础,没有LDS,Envoy就不能实现端口监听(如果启动配置也没有提供静态Listener的话),其他所有xDS服务也失去了作用。

Router

Router:上下游之间的桥梁 Listener可以接收来自下游的连接,Cluster可以将流量发送给具体的上游服务,而Router则决定Listener在接收到下游连接和数据之后,应该将数据交给哪一个Cluster处理。它定义了数据分发的规则。虽然说到Router大部分时候都可以默认理解为HTTP路由,但是Envoy支持多种协议,如Dubbo、Redis等,所以此处Router泛指所有用于桥接Listener和后端服务(不限定HTTP)的规则与资源集合。

Route对应的配置/资源发现服务称之为RDS。Router中最核心配置包含匹配规则和目标Cluster,此外,也可能包含重试、分流、限流等等。

Cluster

Cluster:对上游服务的抽象 在Envoy中,每个Upstream上游服务都被抽象成一个Cluster。Cluster包含该服务的连接池、超时时间、endpoints地址、端口、类型(类型决定了Envoy获取该Cluster具体可以访问的endpoint方法)等等。

Cluster对应的配置/资源发现服务称之为CDS。一般情况下,CDS服务会将其发现的所有可访问服务全量推送给Envoy。与CDS紧密相关的另一种服务称之为EDS。CDS服务负责Cluster资源的推送。而当该Cluster类型为EDS时,说明该Cluster的所有endpoints需要由xDS服务下发,而不使用DNS等去解析。下发endpoints的服务就称之为EDS。

Endpoint

xDS和请求转发概念的对应关系

在Envoy v2 API中,RDS路由指向集群,CDS提供集群配置,通过EDS发现集群成员。

xDS API 示意图如下:

参考文档

2.3 - xDS的ADS

ADS介绍:为什么要在LDS/RDS/CDS/EDS上引入ADS?

为什么有了LDS/RDS/CDS/EDS,还要引入ADS?

2.4 - envoy的filter机制

envoy的filter机制:提供强大的可扩展能力

Envoy通过Filter机制提供了极为强大的可扩展能力。

Filter:强大源于可扩展

Filter,通俗的讲,就是插件。通过Filter机制,Envoy提供了极为强大的可扩展能力。在Envoy中,很多核心功能都使用Filter来实现。比如对于Http流量和服务的治理就是依赖HttpConnectionManager(Network Filter,负责协议解析)以及Router(负责流量分发)两个插件来实现。利用Filter机制,Envoy理论上可以实现任意协议的支持以及协议之间的转换,可以对请求流量进行全方位的修改和定制。强大的Filter机制带来的不仅仅是强大的可扩展性,同时还有优秀的可维护性。Filter机制让Envoy的使用者可以在不侵入社区源码的基础上对Envoy做各个方面的增强。

Filter本身并没有专门的xDS服务来发现配置。Filter所有配置都是嵌入在LDS、RDS以及CDS(Cluster Network Filter)中的。

参考文档:

2.5 - 增量xDS

增量xDS

增量xDS

增量xDS

Envoy通过xDS协议与控制面实现配置数据的交换。当控制面检测中配置变化(比如通过Kubernetes Watch到新service或者其他的CRD资源更新),会向Envoy发送一个discoveryResponse来将更新后的配置下发到Envoy。之后,Envoy主线程在接受到数据之后,通过向各个工作线程中追加配置更新事件来完成配置的实际更新和生效。

但是,需要注意的是,控制面下发的discoveryResponse是一个全量的配置。换言之,哪怕是修改了一条路由中的一个小小请求头匹配,所有Listener、Cluster、Router都必须更新,Envoy会用接受到的新配置替换旧配置。使用现有更新方案虽然逻辑简单明了,但是在负载较多、配置量较大时,会造成大量的流量浪费和不必要的计算开销。

尤其是对于Sidecar模式下的Envoy,该问题会更加的明显。网格中服务需要访问其他服务时,其流量首先会被Envoy Sidecar所拦截,之后由Sidecar将请求转发给对应的服务。由于Sidecar并不了解其代理的服务依赖网格中哪些其他服务,所以它会记录服务网格中所有服务的相关信息。但是,事实上一个网格服务往往只会依赖网格中少量的几个服务。

因为上述问题,Envoy社区提出了delta xDS方案,实现增量的xDS配置更新。简单的说,在delta增量更新方案中,当配置发生变化时,只有发生变化的一项配置(配置的最小单位为一个完整的proto message)需要下发和更新。

基于delta增量更新方案,可以实现以下三种新的功能:

  • Lazy loading:按照具体需要订阅相关资源。全量xDS中,每个Envoy Sidecar都会缓存大量的Cluster配置,但是实际部分Cluster从未被访问过,甚至将数据流量导向此类Cluster的相关路由配置都不存在,此类的Cluster配置只会浪费内存和降低Envoy效率。使用delta增量更新方案,可以在实际的配置被使用时再订阅该资源,从控制面获取相关配置(首次访问性能会受到一定影响)。
  • 增量更新:当部分资源更新时,如某个Cluster配置发生变化,某条Router修改了参数,那么只有对应的Cluster或Router配置会被下发和更新。在负载较多、配置量较大时,该功能可以有效减少网络内因配置更新而引入的数据流量。
  • 缓存擦除(或者说on demand loading**)**:根据Envoy当前负载实际请求动态调整订阅资源类型,对于不再活跃的配置,取消订阅,从Envoy内存中擦除,直到相关配置再次被使用。通过该功能,可以有效限制Envoy配置所占用的数据量,在超大规模应用场景中,可以有效减少Envoy内存开销。

参考文档:

3 - xDS API定义

xDS定义方式

3.1 - xDS中服务定义概述

xDS服务定义概述

3.2 - xDS的服务定义

xDS服务定义方式

通用模式

LDS/RDS/CDS/EDS

LDS/RDS/CDS/EDS 这四个xDS API的定义非常类似,模式都是一致的。

LDS:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/listener/v3/lds.proto

service ListenerDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.listener.v3.Listener";

  rpc DeltaListeners(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc StreamListeners(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc FetchListeners(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:listeners";
    option (google.api.http).body = "*";
  }
}

RDS:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/route/v3/rds.proto

service RouteDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.route.v3.RouteConfiguration";

  rpc StreamRoutes(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaRoutes(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchRoutes(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:routes";
    option (google.api.http).body = "*";
  }
}

CDS:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/cluster/v3/cds.proto

service ClusterDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.cluster.v3.Cluster";

  rpc StreamClusters(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaClusters(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchClusters(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:clusters";
    option (google.api.http).body = "*";
  }
}

EDS:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/endpoint/v3/eds.proto

service EndpointDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.endpoint.v3.ClusterLoadAssignment";

  rpc StreamEndpoints(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaEndpoints(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchEndpoints(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:endpoints";
    option (google.api.http).body = "*";
  }
}

LDS/RDS/CDS/EDS 这四个xDS API的定义方式是非常类似的:

  • 都有一个单次调用的 Fetch***() 方法和一个gRPC双向流的 Stream***()方法,以及一个用于实现增量xDS的 Delta***()方法
  • 而且四个xDS API的这3个方法的参数和应答都是一样的:DiscoveryRequest / DiscoveryResponse / DeltaDiscoveryRequest / DeltaDiscoveryResponse

ADS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/discovery/v3/ads.proto

service AggregatedDiscoveryService {
  // This is a gRPC-only API.
  rpc StreamAggregatedResources(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

  rpc DeltaAggregatedResources(stream DeltaDiscoveryRequest)
      returns (stream DeltaDiscoveryResponse) {
  }
}

ADS的定义类似LDS/RDS/CDS/EDS,只是缺少单次调用的 Fetch***() 方法。

LEDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/endpoint/v3/leds.proto

service LocalityEndpointDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.endpoint.v3.LbEndpoint";

  // State-of-the-World (DiscoveryRequest) and REST are not supported.

  // The resource_names_subscribe resource_names_unsubscribe fields in DeltaDiscoveryRequest
  // specify a list of glob collections to subscribe to updates for.
  rpc DeltaLocalityEndpoints(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }
}

LEDS的定义也类似LDS/RDS/CDS/EDS,但只有一个 Delta***() 方法。

SRDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/route/v3/srds.proto

service ScopedRoutesDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.route.v3.ScopedRouteConfiguration";

  rpc StreamScopedRoutes(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaScopedRoutes(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchScopedRoutes(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:scoped-routes";
    option (google.api.http).body = "*";
  }
}

SRDS的定义和LDS/RDS/CDS/EDS完全一致,三个方法都有。

RTDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/runtime/v3/rtds.proto

service RuntimeDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.service.runtime.v3.Runtime";

  rpc StreamRuntime(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaRuntime(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchRuntime(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:runtime";
    option (google.api.http).body = "*";
  }
}

RTDS的定义和LDS/RDS/CDS/EDS完全一致,三个方法都有。

SDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/secret/v3/sds.proto

service SecretDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.extensions.transport_sockets.tls.v3.Secret";

  rpc DeltaSecrets(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc StreamSecrets(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc FetchSecrets(discovery.v3.DiscoveryRequest) returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:secrets";
    option (google.api.http).body = "*";
  }
}

SDS的定义和LDS/RDS/CDS/EDS完全一致,三个方法都有。

ECDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/extension/v3/config_discovery.proto

service ExtensionConfigDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.config.core.v3.TypedExtensionConfig";

  rpc StreamExtensionConfigs(stream discovery.v3.DiscoveryRequest)
      returns (stream discovery.v3.DiscoveryResponse) {
  }

  rpc DeltaExtensionConfigs(stream discovery.v3.DeltaDiscoveryRequest)
      returns (stream discovery.v3.DeltaDiscoveryResponse) {
  }

  rpc FetchExtensionConfigs(discovery.v3.DiscoveryRequest)
      returns (discovery.v3.DiscoveryResponse) {
    option (google.api.http).post = "/v3/discovery:extension_configs";
    option (google.api.http).body = "*";
  }
}

ECDS的定义和LDS/RDS/CDS/EDS完全一致,三个方法都有。

和通用模式类似

HDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/health/v3/hds.proto

service HealthDiscoveryService {
  rpc StreamHealthCheck(stream HealthCheckRequestOrEndpointHealthResponse)
      returns (stream HealthCheckSpecifier) {
  }

  rpc FetchHealthCheck(HealthCheckRequestOrEndpointHealthResponse) returns (HealthCheckSpecifier) {
    option (google.api.http).post = "/v3/discovery:health_check";
    option (google.api.http).body = "*";
  }
}

HDS的定义和LDS/RDS/CDS/EDS类似,定义有 Fetch***() 方法和 Stream***()方法。由于是健康检查,不存在增量,因此不需要定义 Delta***()方法。

另外就是Request/Response的消息体不再采用通用的消息体,而是HDS自己的定义,这也是因为健康检查的信息和LDS/RDS/CDS/EDS差异较大的原因。

LRS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/load_stats/v3/lrs.proto

service LoadReportingService {
  rpc StreamLoadStats(stream LoadStatsRequest) returns (stream LoadStatsResponse) {
  }
}

LRS 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

MS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/metrics/v3/metrics_service.proto

service MetricsService {
  rpc StreamMetrics(stream StreamMetricsMessage) returns (StreamMetricsResponse) {
  }
}

MS 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

CSDS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/status/v3/csds.proto

service ClientStatusDiscoveryService {
  rpc StreamClientStatus(stream ClientStatusRequest) returns (stream ClientStatusResponse) {
  }

  rpc FetchClientStatus(ClientStatusRequest) returns (ClientStatusResponse) {
    option (google.api.http).post = "/v3/discovery:client_status";
    option (google.api.http).body = "*";
  }
}

CSDS 只定义了 Stream***()方法和 Fetch***() ,Request/Response的消息体也不采用通用的消息体,而是自己定义。

TAP

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/tap/v3/tap.proto

service TapSinkService {
  rpc StreamTaps(stream StreamTapsRequest) returns (StreamTapsResponse) {
  }
}

TAP 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

TraceService

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/trace/v3/trace_service.proto

service TraceService {
  rpc StreamTraces(stream StreamTracesMessage) returns (StreamTracesResponse) {
  }
}

TraceService 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

EventReportingService

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/event_reporting/v3/event_reporting_service.proto

service EventReportingService {
  rpc StreamEvents(stream StreamEventsRequest) returns (stream StreamEventsResponse) {
  }
}

EventReportingService 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

ALS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/accesslog/v3/als.proto

service AccessLogService {
  rpc StreamAccessLogs(stream StreamAccessLogsMessage) returns (StreamAccessLogsResponse) {
  }
}

AccessLogService 只定义了 Stream***()方法,Request/Response的消息体也不采用通用的消息体,而是自己定义。

不采用通用模式

RLS

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/ratelimit/v3/rls.proto

service RateLimitService {
  rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) {
  }
}

ExternalProcessor

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/ext_proc/v3alpha/external_processor.proto

service ExternalProcessor {
  rpc Process(stream ProcessingRequest) returns (stream ProcessingResponse) {
  }
}

Authorization

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/auth/v3/external_auth.proto

service Authorization {
  rpc Check(CheckRequest) returns (CheckResponse) {
  }
}

3.3 - xDS中的通用消息定义

xDS通用消息定义

通用消息定义

https://github.com/envoyproxy/envoy/blob/main/api/envoy/service/discovery/v3/discovery.proto

DiscoveryRequest消息

message DiscoveryRequest {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryRequest";

  string version_info = 1;

  config.core.v3.Node node = 2;

  repeated string resource_names = 3;

  string type_url = 4;

  string response_nonce = 5;

  google.rpc.Status error_detail = 6;
}

DiscoveryRequest 用同样的API为给定 envoy 节点请求一组相同类型的版本化资源。

Field Type Description
version_info string 请求消息中提供的 version_info 使用最近成功处理的响应接收的version_info,或者如果是第一个请求则设置为空。预期在收到响应之后不会发送新请求,直到客户端实例准备好对新配置进行ACK/NACK。 通过分别返回应用的新API配置版本或先前的API配置版本来进行ACK/NACK。 每个type_url(见下文)都有一个与之关联的独立版本。
node core.Node 发起请求的节点
resource_names string[] 要订阅的资源列表,例如集群名称列表或路由配置名称列表。 如果为空,则返回API的所有资源。 LDS/CDS可以设置空resource_names,这将导致返回Envoy实例的所有资源。 然后,LDS和CDS响应将暗示需要通过EDS/RDS获取许多资源,这些资源将被明确列举在resource_names中。
type_url string 正在请求的资源的类型, e.g. “type.googleapis.com/envoy.api.v2.ClusterLoadAssignment”。对于单独的xDS API(如CDS,LDS等)发出的请求中是隐含的,但是对于ADS需要明确设定。
response_nonce string 对应于DiscoveryResponse的nonce,进行 ACK/NACK。 请参阅上面有关version_info和DiscoveryResponse nonce注释的讨论。 只有当1)这是一个非持久流的xDS,如HTTP,或2)客户端尚未接受这个xDS流中的更新时,它才可能是空的(与delta不同,它只对新的明确ACKs进行填充)。
error_detail google.rpc.Status 当前一个DiscoveryResponse无法更新配置时,将填充此选项。 error_details中的message字段提供与失败相关的客户端内部异常。 它仅供手动调试时使用,不保证在客户端版本中提供的字符串是稳定的。

DiscoveryResponse消息

message DiscoveryResponse {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DiscoveryResponse";

  string version_info = 1;

  repeated google.protobuf.Any resources = 2;

  bool canary = 3;

  string type_url = 4;

  string nonce = 5;

  config.core.v3.ControlPlane control_plane = 6;
}

DiscoveryResponse 提供一组相同类型的版本化资源以响应 DiscoveryRequest。

Field Type Description
version_info string 响应数据的版本。
resources google.protobuf.Any[] 响应资源。这些资源是有类型的,而且取决于被调用的API。
canary bool Canary用于支持两个Envoy命令行标志。

* --terminat-on-canary-transition-failure。设置后,Envoy能够在检测到配置卡在canary时终止。考虑下面这个例子的更新顺序:
- 管理服务器成功应用了一个canary配置。
- 管理服务器回滚到一个生产配置。
- Envoy拒绝新的生产配置。 由于没有合理的方法来继续接收配置更新,Envoy将终止并从一个干净的地方应用生产配置。
* --dry-run-canary. 如果设置了这个选项,就不会应用金丝雀响应,只通过干运行来验证。
type_url string 资源类型URL。 在通过ADS复用时识别xDS API。

必须与resources中的type_url一致(如果非空)。
nonce string 对于基于gRPC的订阅,nonce提供了一种在后续DiscoveryRequest中明确ACK特定DiscoveryResponse的方法。 客户端可能已经在此DiscoveryResponse之前在流上向管理服务器发送了其他消息,这些消息在响应发送时未被处理。 nonce允许管理服务器忽略先前版本的任何后续DiscoveryRequest,直到带有nonce的DiscoveryRequest。nonce是可选的,对于不是基于stream的xDS实现来说不是必须的。
control_plane config.core.v3.ControlPlane 发送应答的控制平面实例。

DeltaDiscoveryRequest消息

message DeltaDiscoveryRequest {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.DeltaDiscoveryRequest";

  config.core.v3.Node node = 1;

  string type_url = 2;

  repeated string resource_names_subscribe = 3;

  repeated string resource_names_unsubscribe = 4;

  map<string, string> initial_resource_versions = 5;

  string response_nonce = 6;

  google.rpc.Status error_detail = 7;
}

DeltaDiscoveryRequest 和 DeltaDiscoveryResponse 用于 Delta xDS 的新gRPC端点。

使用Delta xDS,DeltaDiscoveryResponses不需要包含跟踪资源的完整快照。 相反,DeltaDiscoveryResponses是xDS客户端状态的差异。

在Delta XDS中,每个资源都有版本,允许以资源粒度跟踪状态。

xDS Delta 会话始终位于 gRPC 双向流的上下文中。 允许 xDS 服务器跟踪连接到它的 xDS 客户端的状态。

在 Delta xDS 中,nonce字段是必需的,用于将 DeltaDiscoveryResponse 与 DeltaDiscoveryRequest 配对进行 ACK或NACK。 可选地,响应消息级别 system_version_info 仅用于调试目的。

DeltaDiscoveryRequest扮演两个独立的角色。任何DeltaDiscoveryRequest都可以是以下两者中的一个或两个:[1] 通知服务器客户端对哪些资源感兴趣(使用 resource_names_subscribe 和 resource_names_unsubscribe),或者 [2] (N) ACK先前来自服务器的资源更新(使用 response_nonce,出现 error_detail 则变成 NACK)。此外,重新连接的gRPC流的第一个消息(对于给定的type_url)有第三个作用:使用 initial_resource_versions 字段,告知服务器客户端已经拥有的资源(及其版本)。

与世界现状一样,当多种资源类型被复用时(ADS),所有的请求/确认/更新在逻辑上被type_url隔离:集群ACK存在于与之前的路由NACK完全不同的世界。 特别是,在每个gRPC流的 “开始” 时发送的initial_resource_versions实际上包含了每个type_url的消息,每个都有自己的initial_resource_versions。

Field Type Description
node core.Node 响应数据的版本。
type_url string 正在请求的资源的类型, e.g. “type.googleapis.com/envoy.api.v2.ClusterLoadAssignment”。如果资源只通过 xds_resource_subscribexds_resources_unsubscribe 来引用,则不需要设置。
resource_names_subscribe string[] 要添加到跟踪资源列表的资源名称列表。
resource_names_unsubscribe string[] 要从跟踪资源列表中删除的资源名称列表。
initial_resource_versions map<string, string> 通知服务器xDS客户端所知道的资源版本,以使客户端即使在gRPC流重新连接的情况下也能继续同一个逻辑xDS会话。以下情况可以不用设置:[1]在会话的第一个流中,因为客户端还没有任何资源 [2]在流的第一个消息之后的任何消息中(对于一个给定的type_url),因为服务器已经正确地跟踪了客户端的状态。 (在ADS中,重新连接的流中每个 type_url 的第一条消息会填充这个地图)。

该map的键是xDS客户端已知的xDS资源的名称。map的值是不透明的资源版本。
response_nonce string 当 DeltaDiscoveryRequest 是响应前一个 DeltaDiscoveryResponse 的 ACK 或NACK 消息时,response_nonce 必须是 DeltaDiscoveryResponse 中的nonce。

否则必须省略 response_nonce。
error_detail google.rpc.Status 当前一个DiscoveryResponse无法更新配置时,将填充此选项。 error_details中的message字段提供与失败相关的客户端内部异常。

resource_names_subscribe 和 resource_names_unsubscribe 字段的额外说明:

DeltaDiscoveryRequests 允许客户端在流的上下文中向被跟踪的资源集合添加或删除单个资源。

resource_names_subscribe 列表中的所有资源名称都将添加到跟踪资源集合中,而 resource_names_unsubscribe 列表中的所有资源名称将从该组跟踪资源中删除。

与xDS不同,空的 resource_names_subscribe 或 resource_names_unsubscribe 列表仅表示不会向资源列表添加或删除任何资源。

xDS 服务器必须为所有跟踪的资源发送更新,但也可以发送客户端尚未订阅的资源的更新。 此行为类似 xDS。

注意:服务器必须响应所有在 resource_names_subscribe 中列出的资源,即使它认为客户端拥有它们的最新版本。原因是:客户可能已经放弃了它们,但在它有机会发送 unsubscribe 消息之前又恢复了兴趣。参见DeltaSubscriptionStateTest.RemoveThenAdd。

这两个字段可以在任何 DeltaDiscoveryRequest 中设置,包括 ACKs 和 initial_resource_versions。

DeltaDiscoveryResponse消息

message DeltaDiscoveryResponse {
  option (udpa.annotations.versioning).previous_message_type =
      "envoy.api.v2.DeltaDiscoveryResponse";

  string system_version_info = 1;

  repeated Resource resources = 2;

  string type_url = 4;

  repeated string removed_resources = 6;

  string nonce = 5;

  config.core.v3.ControlPlane control_plane = 7;
}
Field Type Description
system_version_info string 响应数据的版本(用于debug)。
resources Resource[] 响应资源。这些资源是有类型的,他们的类型必须和 DeltaDiscoveryRequest 中的 type url 匹配。
type_url string 资源类型URL。 在通过ADS复用时识别xDS API。

必须与resources中的type_url一致(如果非空)。
removed_resources string[] 被删除的资源的资源名称,这些资源在xDS客户端也应该被删除。

如果要删除的资源不存在则可以忽略。
nonce string nonce为 DeltaDiscoveryRequests 提供唯一一种在ACN或者NACK时引用到 DeltaDiscoveryResponse 的方法。 nonce是必需的。
control_plane config.core.v3.ControlPlane 发送应答的控制平面实例。

引用到的消息定义

Resource

Delta 中用到的 Resource 消息:

message Resource {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.Resource";

  message CacheControl {
    bool do_not_cache = 1;
  }

  string name = 3;

  repeated string aliases = 4;

  string version = 1;

  google.protobuf.Any resource = 2;

  google.protobuf.Duration ttl = 6;

  CacheControl cache_control = 7;
}
Field Type Description
name string 资源的名称,以区别于其他同类型的资源。
aliases string[] 别名是该资源可以使用的其他名称的列表。
version string[] 资源层面的版本。它允许xDS跟踪单个资源的状态。
resource google.protobuf.Any 被跟踪的资源。
ttl google.protobuf.Duration 资源的生存时间值。对于每个资源,都会启动一个定时器。每次收到带有新的TTL的资源时,该计时器就会被重置。如果收到的资源没有设置TTL,则该资源的定时器被删除。计时器过期后,该资源的配置将被删除。

可以通过发送一个不改变资源版本的响应来刷新或改变TTL。在这种情况下,资源字段不需要被填充,这允许轻量级的 “心跳” 更新,以保持一个有TTL的资源的活力。

TTL功能是为了支持那些在管理服务器故障时应该被删除的配置。例如,该功能可用于故障注入测试,在Envoy与管理服务器失去联系的情况下,故障注入应该被终止。
cache_control CacheControl 该资源的缓存控制属性。

config.core.v3.Node

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/core/v3/base.proto

Node 用于识别特定的Envoy实例。节点标识符被提交给管理服务器,管理服务器可以使用这个标识符来区分它服务的每个Envoy的配置。

message Node {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.Node";

  reserved 5;

  reserved "build_version";

  string id = 1;

  string cluster = 2;

  google.protobuf.Struct metadata = 3;

  map<string, xds.core.v3.ContextParams> dynamic_parameters = 12;

  Locality locality = 4;

  string user_agent_name = 6;

  oneof user_agent_version_type {
    string user_agent_version = 7;

    BuildVersion user_agent_build_version = 8;
  }

  repeated Extension extensions = 9;

  repeated string client_features = 10;

  repeated Address listening_addresses = 11
      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];
}
Field Type Description
id string Envoy节点的不透明的节点标识符。这也提供了本地服务节点的名称。
cluster string 定义了运行Envoy的本地服务集群名称。
metadata google.protobuf.Struct 扩展节点标识符的不透明元数据。Envoy将把这个直接传递给管理服务器。
dynamic_parameters map<string, xds.core.v3.ContextParams> 从xDS资源 type URL 到动态上下文参数的映射。这些在运行时可能会有变化(与本消息中的其他字段不同)。例如,xDS客户端可能有一个分片标识符,在xDS客户端的生命周期内会发生变化。在Envoy中,这将通过更新 Server::Instance 的 LocalInfo 上下文提供者上的动态上下文来实现。在未来的发现请求中,分片ID动态参数会出现在这个字段中。
locality Locality 指定Envoy实例的运行区域。
user_agent_name string 自由格式的字符串,用于识别请求配置的实体。

例如,“envoy “或 “grpc”。
extensions extensions[] 节点支持的扩展及其版本的列表。
client_features string 客户端功能支持列表。这些是在Envoy API库中描述的众所周知的功能,适用于某一API的主要版本。客户端功能使用反向DNS命名方案,例如com.acme.feature

参见 xDS 客户端可能支持的功能列表。
listening_addresses string 节点上已知的监听端口,作为管理服务器的通用提示,用于过滤 :ref:listeners <config_listeners> 返回。例如,如果有一个监听器绑定到80端口,列表中可以选择包含SocketAddress (0.0.0.0,80)。这个字段是可选的,只是一个提示。

config.core.v3.ControlPlane

识别Envoy所连接的特定控制平面实例。

message ControlPlane {
  option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.ControlPlane";

  string identifier = 1;
}
Type Description
identifier string 一个不透明的控制平面标识符,唯一标识控制平面的一个实例。这可以用来识别Envoy连接到哪个控制平面实例。

type_url取值

以下是几个常见的 type_url:

资源名 type_url的值
Listener “type.googleapis.com/envoy.config.listener.v3.Listener”
Cluster “type.googleapis.com/envoy.config.cluster.v3.Cluster”
ClusterLoadAssignment “type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment”
Secret “type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret”
RouteConfiguration “type.googleapis.com/envoy.config.route.v3.RouteConfiguration”
VirtualHost “type.googleapis.com/envoy.config.route.v3.VirtualHost”
ScopedRouteConfiguration “type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration”
Runtime “type.googleapis.com/envoy.service.runtime.v3.Runtime”

type_url 取值的规律是 "type.googleapis.com" 前缀 + 资源名称,其在envoy中的实现代码在头文件 source/common/config/resource_name.h 中:

/**
 * Get type url from api type.
 */
template <typename Current> std::string getTypeUrl() {
  return "type.googleapis.com/" + getResourceName<Current>(); 
}

如 Listener 的定义在 proto 文件 https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener.proto 中,

syntax = "proto3";

package envoy.config.listener.v3;

message Listener {
......
}

因此 Listener/LDS 的 type_url 就是 "type.googleapis.com/envoy.config.listener.v3.Listener"

4 - xDS API通讯协议

xDS API的通讯协议和工作流程

4.1 - xDS REST和gRPC协议

xDS REST和gRPC协议

备注:内容来自 xDS REST and gRPC protocol

Envoy通过文件系统或通过查询一个或多个管理服务器来发现其各种动态资源。这些发现服务及其相应的API统称为xDS。 通过订阅,指定要监视的文件系统路径,启动gRPC流或轮询REST-JSON URL来请求资源。后两种方法涉及使用DiscoveryRequest proto 载荷发送请求。在所有方法中资源以DiscoveryResponse proto 负载的形式发送。我们在下面讨论每种类型的订阅。

资源类型

xDS API中的每个配置资源都有关联的类型。资源类型遵循版本方案。资源类型是独立于下面描述的传输方式的版本。

支持以下 v3 xDS 资源类型:

类型URL的概念出现在下面,其形式为type.googleapis.com/<资源类型>–例如,type.googleapis.com/envoy.config.cluster.v3.Cluster为一个Cluster资源。在Envoy的各种请求和管理服务器的响应中,都会说明资源类型URL。

文件系统订阅

提供动态配置的最简单方法是将其放置在 ConfigSource 中指定的众所周知的路径中。Envoy将使用inotify(Mac OS X上的kqueue)来监视文件的更改,并在更新时解析文件中的DiscoveryResponse proto。二进制protobufs,JSON,YAML和proto文本是DiscoveryResponse支持的格式。

除了统计计数器和日志以外,没有任何机制可用于文件系统订阅ACK/NACK更新。如果发生配置更新拒绝,xDS API的最后一个有效配置将继续适用。

流式gRPC订阅

API流

对于典型的HTTP路由场景,客户端配置的核心资源类型是Listener、RouteConfiguration、Cluster和ClusterLoadAssignment。每个Listener资源可以指向一个RouteConfiguration资源,RouteConfiguration资源可以指向一个或多个Cluster资源,而每个Cluster资源可以指向一个ClusterLoadAssignment资源。

Envoy在启动时获取所有Listener和Cluster资源。然后,它获取 Listener 和 Cluster 资源所需要的任何RouteConfiguration和ClusterLoadAssignment资源。实际上,每个 Listener 或 Cluster 资源都是Envoy配置树的一部分的根。

一个非代理客户端,如gRPC,可能会从获取它感兴趣的特定Listener资源开始。然后获取这些监听器资源所需的RouteConfiguration资源,接着是这些RouteConfiguration资源所需的任何 Cluster 资源,然后是 Cluster 资源所需的ClusterLoadAssignment资源。实际上,最初的Listener资源是客户配置树的根。

xDS传输协议的变体

四种变体

通过流式gRPC使用的xDS传输协议有四种变体,它们涵盖了两个维度的所有组合。

第一个维度是全量(State of the World / SotW,后面统称为全量)与增量。SotW方法是xDS最初使用的机制,客户必须在每次请求中指定其感兴趣的所有资源名称,对于LDS和CDS资源,服务器必须在每次请求中返回客户订阅的所有资源。这意味着,如果客户已经订阅了99个资源,并想增加一个资源,它必须发送一个包含所有100个资源名称的请求,而不是只发送一个新的资源。而对于LDS和CDS资源,服务器就必须通过发送所有100个资源来响应,即使已经订阅的99个资源没有变化。这种机制可能是一种可扩展性限制,这就是为什么引入了增量协议变体。增量方法允许客户端和服务器只表示相对于其先前状态的延迟–也就是说,客户端可以说它想增加或删除对某个特定资源名称的订阅,而不重新发送那些没有变化的资源,而服务器可以只为那些已经变化的资源发送更新。增量协议还提供了一种延迟加载资源的机制。关于增量协议的细节,见下面的增量xDS。

第二个维度是为每个资源类型使用单独的gRPC流,而不是将所有资源类型聚合到一个gRPC流中。前一种方法是xDS使用的原始机制,它提供最终一致性模型。后一种方法是为需要明确控制顺序的环境而添加的。详情请见下面的最终一致性考虑。

因此,xDS传输协议的四种变体是:

  1. 全量(基本xDS)全量,每种资源类型有单独的gRPC流

  2. 增量xDS:增量,每种资源类型单独的gRPC流

  3. 聚合发现服务(ADS):全量,所有资源类型的聚合流

  4. 增量ADS:增量,所有资源类型的聚合流

每种变体的RPC服务和方法

对于非聚合协议变体,每种资源类型都有一个单独的RPC服务。这些RPC服务中的每一个都可以为全量和增量协议变体提供方法。下面是每种资源类型的RPC服务和方法:

  • Listener: Listener Discovery Service (LDS) - 全量: ListenerDiscoveryService.StreamListeners - 增量: ListenerDiscoveryService.DeltaListeners
  • RouteConfiguration: Route Discovery Service (RDS) - 全量: RouteDiscoveryService.StreamRoutes - 增量: RouteDiscoveryService.DeltaRoutes
  • ScopedRouteConfiguration: Scoped Route Discovery Service (SRDS) - 全量: ScopedRouteDiscoveryService.StreamScopedRoutes - 增量: ScopedRouteDiscoveryService.DeltaScopedRoutes
  • VirtualHost: Virtual Host Discovery Service (VHDS) - 全量: N/A - 增量: VirtualHostDiscoveryService.DeltaVirtualHosts
  • Cluster: Cluster Discovery Service (CDS) - 全量: ClusterDiscoveryService.StreamClusters - 增量: ClusterDiscoveryService.DeltaClusters
  • ClusterLoadAssignment: Endpoint Discovery Service (EDS) - 全量: EndpointDiscoveryService.StreamEndpoints - 增量: EndpointDiscoveryService.DeltaEndpoints
  • Secret: Secret Discovery Service (SDS) - 全量: SecretDiscoveryService.StreamSecrets - 增量: SecretDiscoveryService.DeltaSecrets
  • Runtime: Runtime Discovery Service (RTDS) - 全量: RuntimeDiscoveryService.StreamRuntime - 增量: RuntimeDiscoveryService.DeltaRuntime

在聚合协议变体中,所有的资源类型都在单一的gRPC流中被复用,其中每个资源类型都被视为聚合流中的单独的逻辑流。实际上,它只是通过将每种资源类型的请求和响应视为单一聚合流上的单独子流,将上述所有单独的API合并为一个流。聚合协议变体的RPC服务和方法是:

  • 全量: AggregatedDiscoveryService.StreamAggregatedResources
  • 增量: AggregatedDiscoveryService.DeltaAggregatedResources

对于所有的全量方法,请求类型是DiscoveryRequest,响应类型是DiscoveryResponse。

对于所有的增量方法,请求类型是DeltaDiscoveryRequest,响应类型是DeltaDiscoveryResponse。

配置要使用的变体

在xDS API中,ConfigSource消息表明如何获得特定类型的资源。如果ConfigSource包含一个gRPC ApiConfigSource,它指向管理服务器的上游集群;这将为每个xDS资源类型启动一个独立的双向gRPC流,有可能到不同的管理服务器。如果ConfigSource包含AggregatedConfigSource,它告诉客户端使用ADS。

目前,客户端被期望得到一些本地配置,告诉它如何获得 Listener 和 Cluster 资源。Listener 资源可能包括一个表明如何获得RouteConfiguration资源的ConfigSource,而 Cluster 资源可能包括一个表明如何获得ClusterLoadAssignment资源的ConfigSource。

客户端配置

在Envoy中,bootstrap文件包含两个ConfigSource消息,一个表示如何获得Listener资源,另一个表示如何获得Cluster资源。它还包含一个单独的ApiConfigSource消息,指示如何联系ADS服务器,只要ConfigSource消息(无论是在bootstrap文件中还是在从管理服务器获得的Listener或Cluster资源中)包含AggregatedConfigSource消息,就会用到它。

在使用xDS的gRPC客户端中,只支持ADS,bootstrap文件包含ADS服务器的名称,它将用于所有资源。Listener和Cluster资源中的ConfigSource消息必须包含AggregatedConfigSource消息。

xDS传输协议

传输API版本

除了上面描述的资源类型版本外,xDS协议还有一个与之相关的传输版本。这为诸如DiscoveryRequest和DiscoveryResponse之类的消息提供了类型版本。它也被编码在gRPC方法名称中,因此服务器可以根据客户端调用的方法来确定它的版本。

基本协议概述

每个xDS流以来自客户端的DiscoveryRequest开始,其中指定了要订阅的资源列表、与订阅的资源相对应的type URL、节点标识符,以及一个可选的资源类型实例版本,表示客户端已经看到的资源类型的最新版本(详见 ACK/NACK和资源类型实例版本)。

然后,服务器将发送一个DiscoveryResponse,其中包含客户端订阅的、自客户端表示它已经看到的最后一个资源类型实例版本以来已经改变的任何资源。当订阅的资源发生变化时,服务器可以在任何时候发送额外的响应。

每当客户端收到一个新的响应,它将发送另一个请求,表明响应中的资源是否有效(详见 ACK/NACK和资源类型实例版本)。

所有的服务器响应将包含一个nonce,客户端的所有后续请求必须将 response_nonce 字段设置为该流上从服务器收到的最新的nonce。这允许服务器确定一个给定的请求与哪个响应相关,这避免了SotW全量协议变体中的各种竞争条件。请注意,nonce只在单个xDS流的上下文中有效;它不能在流重启后继续存在。

只有流上的第一个请求被保证携带节点标识符。同一流上的后续发现请求可能携带一个空的节点标识符。无论同一流上的发现响应是否被接受,这都是真实的。如果在流上出现不止一次,节点标识符应该始终是相同的。因此,只检查第一个消息的节点标识符就足够了。

ACK/NACK和资源类型实例版本

每个xDS资源类型都有一个版本字符串,表示该资源类型的版本。每当该类型的一个资源发生变化时,版本就会改变。

在xDS服务器发送的响应中,version_info 字段表示该资源类型的当前版本。然后,客户向服务器发送另一个请求,其中的 version_info 字段表示客户看到的最新的有效版本。这为服务器提供了一种方法,以确定它何时发送了一个被客户认为无效的版本。

(在增量协议的变体中,资源类型实例的版本由服务器在 system_version_info 字段中发送。然而,这个信息实际上并不被客户端用来通知哪些资源是有效的,因为增量 API 变体有一个单独的机制来处理这个问题。)

资源类型的实例版本对每个资源类型都是隔离的。当使用聚合协议变体时,每个资源类型都有自己的版本,即使所有资源类型都在同一个流上被发送。

资源类型实例版本对每个xDS服务器也是独立的(xDS服务器由唯一的ConfigSource识别)。当从多个xDS服务器获得一个给定类型的资源时,每个xDS服务器将有一个不同的版本概念。

注意,资源类型的版本不是单个xDS流的属性,而是资源本身的属性。如果流被破坏,客户创建了新的流,客户在新流上的初始请求应该表明客户在前一个流上看到的最新版本。服务器可以决定通过不重新发送客户端在前一个流中已经看到的资源来进行优化,但前提是他们知道客户端没有订阅之前没有订阅过的新资源。例如,当唯一的订阅是通配符订阅时,服务器对LDS和CDS做这种优化通常是安全的,而且在客户将始终订阅完全相同的资源集的环境中,这种优化也是安全的。

一个EDS请求的例子可能是:

version_info:
node: { id: envoy }
resource_names:
- foo
- bar
type_url: type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
response_nonce:

管理服务器可以立即或在所请求的资源可用时以DiscoveryResponse进行回复,例如:

version_info: X
resources:
- foo ClusterLoadAssignment proto encoding
- bar ClusterLoadAssignment proto encoding
type_url: type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
nonce: A

在处理完 DiscoveryResponse 后,Envoy将在流上发送一个新的请求,指定最后成功应用的版本和管理服务器提供的nonce。版本为Envoy和管理服务器提供了一个关于当前应用的配置的共享概念,以及一个用于 ACK/NACK 配置更新的机制。

ACK

如果更新被成功应用,version_info 将是X,如顺序图中所示。

NACK

如果Envoy拒绝配置更新X,它将回复弹出的 error_detail 和它之前的版本,在这种情况下是空的初始版本。error_detail有更多的细节,围绕着消息字段中弹出的确切错误信息:

在顺序图中,使用以下格式来缩写信息:

  • DiscoveryRequest: (V=version_info,R=resource_names,N=response_nonce,T=type_url)
  • DiscoveryResponse: (V=version_info,R=resources,N=nonce,T=type_url)

在NACK之后,API更新可能在新的版本Y上成功:

服务器检测NACK的首选机制是在客户端发送的请求中寻找 error_detail 字段的存在。一些较早的服务器可能会通过查看请求中的 version 和 nonce 来检测NACK:如果请求中的版本不等于服务器用该nonce发送的版本,那么客户就拒绝了最新的版本。然而,这种方法对 LDS 和 CDS 以外的API不起作用,因为客户可能会动态地改变他们所订阅的资源集,除非服务器以某种方式安排在任何一个客户订阅新资源时增加资源类型实例的版本。具体来说,考虑下面的例子:

ACK和NACK语义摘要
  • xDS客户端应该对从管理服务器收到的每个DiscoveryResponse进行ACK或NACK。response_nonce字段告诉服务器,ACK或NACK与哪个响应相关。

  • ACK标志着成功的配置更新,并包含来自 DiscoveryResponse 的版本信息。

  • NACK标志着不成功的配置,并由 error_detail 字段的存在表示。version_info 表示客户机正在使用的最新版本,尽管在客户机从现有版本订阅了一个新资源而该新资源无效的情况下,该版本可能不是旧版本(见上面的例子)。

何时发送更新

管理服务器应该只在DiscoveryResponse中的资源发生变化时向Envoy客户端发送更新。Envoy会在任何DiscoveryResponse被接受或拒绝后立即用包含ACK/NACK的DiscoveryRequest来回复。如果管理服务器提供相同的资源集,而不是等待发生变化,就会在客户端和管理服务器上造成不必要的工作,这可能会严重影响性能。

在流中,新的 DiscoveryRequests 会取代先前具有相同资源类型的任何 DiscoveryRequests。这意味着管理服务器只需要对每个流中任何给定资源类型的最新DiscoveryRequest作出响应。

客户端如何指定返回哪些资源?

xDS请求允许客户指定一组资源名称,作为对服务器的提示,说明客户对哪些资源感兴趣。在SotW全量协议变体中,这是通过在 DiscoveryRequest 中指定的资源名称完成的;在增量协议变体中,这是通过 DeltaDiscoveryRequest 中的 resource_names_subscribe 和 resource_names_unsubscribe 字段完成的。

通常情况下(例外情况见下文),请求必须指定客户端感兴趣的资源名称集。管理服务器必须提供所请求的资源,如果它们存在的话。客户端将默默地忽略任何提供的、没有明确请求的资源。当客户端发送一个新的请求来改变被请求的资源集时,服务器必须重新发送任何新请求的资源,即使它之前发送了这些资源而没有被请求,并且自那时起资源没有改变。如果资源名称列表变成空的,这意味着客户端对指定类型的任何资源不再感兴趣。

对于 Listener 和 cluster 资源类型,还有一个 “通配符” 订阅,当订阅特殊名称 "*" 时,会触发该订阅。在这种情况下,服务器应该使用站点特定的业务逻辑来确定客户端感兴趣的全部资源集,通常是基于客户端的节点标识。

由于历史原因,如果客户端发送了一个给定资源类型的请求,但从未明确订阅过任何资源名称(即在SotW中,该资源类型的流中的所有请求都有一个空的 resource_names 字段,或者在增量中,从未在该资源类型的流中发送过一个非空的 resource_names_subscribe 字段的请求),服务器应该将其与处理客户端明确订阅了 "*" 的情况相同。然而,一旦客户端明确订阅了一个资源名称(无论是 "*" 还是其他名称),那么这种传统的语义就不再可用;在这一点上,清除订阅的资源列表被解释为取消订阅(参见从资源中取消订阅),而不是订阅 "*"

例如,在SotW全量中:

  • 客户端发送了一个未设置资源名称的请求。服务器将其解释为对 "*" 的订阅。

  • 客户端发送一个请求,将资源名称设置为 "*" 和 “A”。服务器将此解释为继续现有的对 "*" 的订阅,并添加一个对 “A” 的新订阅。

  • 客户端发送一个请求,资源名称设置为 “A”。服务器将此解释为取消对 "*" 的订阅并继续对 “A “的现有订阅。

  • 客户端发送了一个请求,但没有设置 resource_names。服务器将此解释为取消对 "A" 的订阅(即,客户现在已经取消了对所有资源的订阅)。尽管这个请求与第一个请求相同,但它不会被解释为通配符订阅,因为之前在这个流上已经有一个为这个资源类型设置了 resource_names 字段的请求。

而在增量中:

  • 客户端发送了一个请求,但没有设置 resource_names_subscribe。服务器将其理解为对 "*" 的订阅。

  • 客户端发送请求,并将 resource_names_subscribe 设置为 “A”。服务器将此解释为继续现有的对 "*" 的订阅,并添加一个对 “A” 的新订阅。

  • 客户端发送一个请求,并将 resource_names_unsubscribe 设置为 "*"。服务器将此解释为取消对 "*" 的订阅并继续对 "A" 的现有订阅。

  • 客户端发送一个请求,并将 resource_names_unsubscribe 设置为 “A”。服务器将此解释为取消对 “A” 的订阅(即,客户现在已经取消了对所有资源的订阅)。虽然现在订阅的资源集是空的,就像初始请求后一样,但它不会被解释为通配符订阅,因为之前在这个流上已经有一个针对这个资源类型的请求设置了 resource_names_subscribe 字段。

客户端行为

Envoy将始终使用通配符来订阅 Listener 和 Cluster 资源。然而,其他xDS客户端(如使用xDS的gRPC客户端)可以明确订阅这些资源类型的特定资源名称,例如,如果他们只有一个子监听器,并且已经从一些带外配置中知道它的名称。

将资源分组到响应中

在增量协议的变体中,服务器在其自身的响应中发送每个资源。这意味着,如果服务器之前发送了100个资源,而其中只有一个资源发生了变化,那么它可以发送一个仅包含变化的资源的响应;它不需要重新发送未发生变化的99个资源,而且客户端不得删除未变化的资源。

在SotW全量协议变体中,除了 Listener 和 Cluster 以外的所有资源类型都以与增量协议变体相同的方式被分组为响应。然而,Listener 和 Cluster 资源类型的处理方式不同:服务器必须包括世界的完整状态,这意味着必须包括客户端需要的所有相关类型的资源,即使它们自上次响应以来没有变化。这意味着,如果服务器之前发送了100个资源,而其中只有一个资源发生了变化,那么它必须重新发送所有的100个资源,甚至是没有被修改的99个。

请注意,所有的协议变体都是以整个命名的资源为单位进行操作。没有任何机制可以提供命名资源中重复字段的增量更新。最值得注意的是,目前还没有机制来增量更新EDS响应中的单个端点。

重复的资源名称

服务器发送一个包含两次相同资源名称的响应是一个错误。客户端应该拒绝包含同一资源名称的多个实例的响应。

删除资源

在增量协议的变体中,服务器通过响应中的 remove_resources 字段向客户端发出信号,表示应该删除某个资源。这告诉客户端从其本地缓存中删除该资源。

在SotW全量协议的变体中,删除资源的标准更加复杂。对于 Listener 和 Cluster 类型,如果以前看到的资源没有出现在新的响应中,这表明该资源已被删除,客户端必须删除它;不包含任何资源的响应意味着要删除该类型的所有资源。然而,对于其他资源类型,API没有提供任何机制让服务器告诉客户端资源已经被删除;相反,删除是通过父资源被改变为不再引用子资源来隐含地表示的。例如,当客户机收到 LDS 更新,删除先前指向 RouteConfiguration A的Listener 时,如果没有其他 Listener 指向 RouteConfiguration A,那么客户机可能会删除A。对于这些资源类型,从客户机的角度来看,空的 DiscoveryResponse 实际上是一个无用功。

了解所请求的资源何时不存在?

SotW全量协议的变体没有提供任何明确的机制来确定所请求的资源何时不存在。

Listener 和 Cluster 资源类型的响应必须包括客户端请求的所有资源。然而,客户可能无法仅根据响应中不存在的资源而知道该资源不存在,因为更新的交付最终是一致的:如果客户最初发送了对资源A的请求,然后发送了对资源A和B的请求,然后看到只包含资源A的响应,客户不能断定资源B不存在,因为该响应可能是在服务器看到第二个请求之前,根据第一个请求发送。

对于其它资源类型,因为每个资源都可以在它自己的响应中发送,所以没有办法从下一个响应中知道新请求的资源是否存在,因为下一个响应可能是先前已经订阅的另一个资源的无关的更新。

因此,客户端在发送新资源的请求后,应该使用一个超时(建议持续时间为15秒),超时后如果没有收到资源,他们会认为请求的资源不存在。在Envoy中,这是在资源预热期间为 RouteConfiguration 和 ClusterLoadAssignment 资源做的。

请注意,即使所请求的资源在客户端请求时不存在,该资源也可能在任何时候被创建。管理服务器必须记住客户端请求的资源集,如果这些资源中的一个后来突然出现,服务器必须向客户端发送一个更新,告知它新的资源。最初看到一个不存在的资源的客户必须准备好随时创建该资源。

取消对资源的订阅

在增量协议变体中,可以通过 resource_names_unsubscribe 字段来取消对资源的订阅。

在SotW全量协议变体中,每个请求必须在 resource_names 字段中包含被订阅的资源名称的完整列表,因此取消对一组资源的订阅是通过发送一个包含所有仍被订阅的资源名称但不包含被取消订阅的资源名称的新请求完成的。例如,如果客户端之前订阅了资源A和B,但希望取消对B的订阅,它必须发送一个只包含资源A的新请求。

请注意,对于客户端使用 “通配符” 订阅的 Listener 和 Cluster 资源类型(详见客户端如何指定返回哪些资源),被订阅的资源集由服务器而不是客户端决定,因此客户端不能单独取消订阅这些资源;它只能从通配符中作为一个整体取消订阅。

在一个流中请求多个资源

对于 EDS/RDS,Envoy可以为每个给定类型的资源生成一个不同的流(例如,如果每个ConfigSource都有自己不同的上游集群的管理服务器),或者在给定资源类型的多个资源请求被送到同一个管理服务器时,可以将它们合并在一起。虽然这有待于具体实施,但管理服务器应该能够在每个请求中处理一个或多个给定资源类型的资源名称。下面两个序列图对获取两个EDS资源 {foo, bar} 有效。

资源更新

如上所述,Envoy可能会更新它在每个 ACK/NACK 特定 DiscoveryResponse 的 DiscoveryRequest 中呈现给管理服务器的资源名称列表。此外,Envoy以后可能会在给定的 system_info 上发出额外的DiscoveryRequest,以便用新的资源提示来更新管理服务器。例如,如果Envoy处于EDS的X版本,并且只知道集群foo,但是后来收到CDS更新,并且还知道了bar,那么它可能会为X发出一个额外的 DiscoveryRequest,将 {foo,bar} 作为 resource_names

这里可能会出现一个竞赛条件;如果Envoy在X处发出资源提示更新后,但在管理服务器处理该更新之前,它回复了一个新的版本Y,那么该资源提示更新可能会被解释为通过提出一个X version_info 来拒绝Y。为了避免这种情况,管理服务器提供了一个nonce,Envoy用它来表示每个DiscoveryRequest所对应的特定DiscoveryResponse。

管理服务器不应该为任何具有过期 nonce 的 DiscoveryRequest 发送 DiscoveryResponse。在 DiscoveryResponse 中向Envoy提交了一个较新的nonce后,nonce就会变得过时了。管理服务器不需要发送更新,直到它确定有新的版本可用。早期的一个版本的请求也会变得陈旧。它可以在一个版本上处理多个DiscoveryRequests,直到新版本准备就绪。

上述资源更新顺序的一个含义是,Envoy并不期望它发出的每个DiscoveryRequests都有一个DiscoveryResponse。

资源预热

Cluster 和 Listener 在为请求提供服务之前要经过预热。这个过程既发生在Envoy初始化过程中,也发生在集群或监听器被更新时。只有当管理服务器提供了ClusterLoadAssignment响应时,集群的预热才会完成。同样,只有当管理服务器提供 RouteConfiguration 时,如果监听器指向一个RDS配置,监听器的预热才会完成。管理服务器应该在预热期间提供EDS/RDS更新。如果管理服务器不提供EDS/RDS响应,Envoy将不会在初始化阶段初始化自己,通过CDS/LDS发送的更新将不会生效,直到EDS/RDS响应被提供。

最终一致性的考虑

由于Envoy的xDS APIs最终是一致的,流量在更新期间可能会短暂下降。例如,如果只有集群X是通过CDS/EDS知道的,RouteConfiguration引用了集群X,然后在CDS/EDS更新提供Y之前被调整为集群Y,流量将被封锁,直到Y被Envoy实例所知道。

对于某些应用来说,暂时的流量下降是可以接受的,客户端或其他Envoy侧设备的重试将隐藏这种下降。对于其他不能容忍掉线的情况,可以通过提供一个包含X和Y的CDS/EDS更新,然后RDS更新从X重新指向Y,然后CDS/EDS更新放弃X来避免流量掉线。

一般来说,为了避免流量下降,更新的顺序应该遵循先做后断(a make before break)的模式,其中:

  • CDS的更新(如果有的话)必须总是先推送。

  • EDS的更新(如果有的话)必须在各集群的CDS更新之后到达。

  • LDS更新必须在相应的CDS/EDS更新之后到达。

  • 与新添加的Listener相关的RDS更新必须在CDS/EDS/LDS更新之后到达。

  • 与新添加的RouteConfigurations相关的VHDS更新(如果有的话)必须在RDS更新后到达。

  • 陈旧的CDS集群和相关的EDS端点(不再被引用的)就可以被删除。

如果没有添加新的 clusters/routes/listener ,或者在更新期间暂时放弃流量是可以接受的,xDS更新可以独立推送。请注意,在LDS更新的情况下,Listener 在接收流量之前会被预热,也就是说,如果配置了RDS,则通过RDS获取依赖路由。在添加/删除/更新集群的时候,集群会被预热。另一方面,路由不被预热,也就是说,在推送路由的更新之前,管理平面必须确保路由所引用的集群已经到位。

TTL

如果管理服务器无法到达,Envoy收到的最后一个已知配置将持续到连接重新建立。对于某些服务,这不可取的。例如,在故障注入服务的情况下,管理服务器在错误的时间崩溃可能会使Envoy处于一个不理想的状态。TTL设置允许Envoy在与管理服务器失去联系后,在指定的时间段内删除一组资源。例如,这可以用来在管理服务器不能再被联系到时终止故障注入测试。

对于支持 xds.config.supported-resource-ttl 客户端功能的客户端,可以在每个资源上指定一个TTL字段。每个资源将有自己的TTL过期时间,在这个时候,资源将被过期。每个xDS类型可能有不同的方式来处理这种过期。

要更新与一个资源相关的TTL,管理服务器用一个新的TTL重新发送资源。要删除TTL,管理服务器重新发送资源时不设置TTL字段。

为了允许轻量级的TTL更新(“心跳”),可以发送一个响应,提供一个资源,其资源未设置,版本与最近发送的版本相匹配,可用于更新TTL。这些资源将不会被视为资源更新,而只是作为TTL更新。

SotW TTL

为了在SotW xDS中使用TTL,相关资源必须被包裹在一个资源中。这允许设置与SotW的Delta xDS相同的TTL字段,而无需改变SotW的API。SotW也支持心跳:响应中任何看起来像心跳资源的资源将只被用来更新TTL。

这个功能是由 xds.config.supported-resource-in-sotw 客户端功能控制的。

聚合发现服务

在管理服务器处于分布式部署的情况下,要提供上述的顺序保证以避免流量下降是很有挑战性的。ADS允许单个管理服务器通过单个gRPC流来提供所有API更新。这提供了对更新进行仔细排序以避免流量下降的能力。有了ADS,单一的流被用于多个独立的 DiscoveryRequest/DiscoveryResponse 序列,通过 type URL进行复用。对于任何给定的 type URL,上述 DiscoveryRequest 和 DiscoveryResponse 消息的排序适用。更新序列的例子可能看起来像:

每个Envoy实例可以使用一个ADS流。

用于配置ADS的最小 bootstrap.yaml 片段的例子是:

node:
  # set <cluster identifier>
  cluster: envoy_cluster
  # set <node identifier>
  id: envoy_node

dynamic_resources:
  ads_config:
    api_type: GRPC
    transport_api_version: V3
    grpc_services:
    - envoy_grpc:
        cluster_name: ads_cluster
  cds_config:
    resource_api_version: V3
    ads: {}
  lds_config:
    resource_api_version: V3
    ads: {}

static_resources:
  clusters:
  - name: ads_cluster
    type: STRICT_DNS
    load_assignment:
      cluster_name: ads_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                # set <ADS management server address>
                address: my-control-plane
                # set <ADS management server port>
                port_value: 777
    # It is recommended to configure either HTTP/2 or TCP keepalives in order to detect
    # connection issues, and allow Envoy to reconnect. TCP keepalive is less expensive, but
    # may be inadequate if there is a TCP proxy between Envoy and the management server.
    # HTTP/2 keepalive is slightly more expensive, but may detect issues through more types
    # of intermediate proxies.
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options:
            connection_keepalive:
              interval: 30s
              timeout: 5s
    upstream_connection_options:
      tcp_keepalive: {}

递增的xDS

增量xDS是一个单独的xDS端点:

  • 允许协议以 资源/资源名称 deltas(“Delta xDS”)的方式在网上进行通信。这支持xDS资源的可扩展性目标。当一个集群被修改时,管理服务器不需要交付所有的100k集群,而只需要交付改变了的单一集群。

  • 允许Envoy按需(on-demand) /延迟(lazily)地请求额外的资源。例如,只有在对集群的请求到达时才请求该集群。

增量xDS会话总是在gRPC双向流的背景下进行。这使得xDS服务器可以跟踪连接到它的xDS客户端的状态。目前还没有增量xDS的REST版本。

在delta xDS协议中,nonce字段是必需的,用于将 DeltaDiscoveryResponse 与 DeltaDiscoveryRequest ACK或NACK配对。可选的是,响应消息级别的system_version_info 只为调试目的出现。

DeltaDiscoveryRequest可以在以下情况下发送:

  • xDS 双向gRPC流中的初始消息。

  • 作为对先前 DeltaDiscoveryResponse 的ACK或NACK响应。在这种情况下,response_nonce 被设置为响应中的 nonce 值。ACK或NACK由error_detail的缺席或存在决定。

  • 来自客户端的自发 DeltaDiscoveryRequest。可以用来动态添加或删除跟踪的资源名称集合中的元素。在这种情况下, response_nonce 必须被省略。

请注意,虽然可以在请求中设置 response_nonce,但服务器必须尊重订阅状态的变化,即使nonce是陈旧的。nonce可用于将ack/nack与服务器响应联系起来,但不应该用于拒绝过时的请求。

在这第一个例子中,客户端连接并收到第一个更新,它ACK了。第二个更新失败了,客户端NACK了这个更新。后来,xDS客户端自发地请求 “wc “资源。

在重新连接时,增量xDS客户端可以通过在 initial_resource_versions 告诉服务器它的已知资源,以避免它们在网络上重新发送。因为没有假设从以前的流中保留状态,重新连接的客户端必须向服务器提供它感兴趣的所有资源名称。

请注意,对于 “通配符” 订阅(详见客户端如何指定返回哪些资源),请求必须在 resource_names_subscribe 字段中指定 “*",或者(传统行为)请求必须在 resource_names_subscribe 和 resource_names_unsubscribe 中都没有资源。

资源名称

资源由资源名称或别名来识别。如果存在资源的别名,可以通过 DeltaDiscoveryResponse 的资源中的别名字段来识别。资源名称将在DeltaDiscoveryResponse的资源中的名称字段中返回。

订阅资源

客户端可以在 DeltaDiscoveryRequest 的 resource_names_subscribe 字段中发送一个别名或资源的名称,以便订阅资源。资源的名称和别名都应该被检查,以确定有关实体是否被订阅了。

resource_names_subscribe 字段可能包含服务器认为客户已经订阅的资源名称,而且还拥有最新的版本。然而,服务器仍然必须在响应中提供这些资源;由于服务器隐藏的实现细节,客户可能已经 “忘记"了这些资源,尽管表面上仍然被订阅。

取消订阅资源

当客户对某些资源失去兴趣时,它将通过 DeltaDiscoveryRequest 的 resource_names_unsubscribe 字段来表示。与 resource_names_subscribe 一样,这些可能是资源名称或别名。

resource_names_unsubscribe 字段可能包含多余的资源名称,服务器认为客户端已经不订阅这些资源了。服务器必须干净利落地处理这样的请求;它可以简单地忽略这些幽灵式的取消订阅。

在大多数情况下(例外情况见下文),如果一个请求除了取消订阅一个资源外什么都不做,服务器不需要发送任何响应;特别是,服务器一般不需要在remove_resources字段中发送一个带有取消订阅的资源名称的响应。

然而,上述情况有一个例外。当客户端有一个通配符订阅("*")和对另一个特定资源名称的订阅时,有可能该特定资源名称也包括在通配符订阅中,所以如果客户端取消对该特定资源名称的订阅,它不知道是否要继续缓存该资源。为了解决这个问题,服务器必须发送一个响应,在 remove_resources 字段(如果它不包括在通配符中)或resources字段(如果它包括在通配符中)中包括特定资源。

了解所请求的资源何时不存在?

当客户端订阅的资源不存在时,服务器将发送一个 DeltaDiscoveryResponse 消息,在 removed_resources 字段中包含该资源的名称。这允许客户端快速确定资源是否存在,而无需像SotW协议变体中那样等待超时。然而,我们仍然鼓励客户使用超时,以防止管理服务器未能及时发送响应的情况。

REST-JSON轮询订阅

通过REST端点的同步(长)轮询也可用于xDS单体API。上述消息的排序是类似的,只是没有向管理服务器保持持久的流。预计在任何时间点都只有一个未完成的请求,因此在REST-JSON中,响应非ce是可选的。proto3的JSON规范性转换被用来对DiscoveryRequest和DiscoveryResponse消息进行编码。ADS不适用于REST-JSON轮询。

当轮询周期被设置为一个较小的值,并打算进行长时间的轮询时,那么还需要避免发送DiscoveryResponse,除非通过资源更新对基础资源进行了更改。

5 - xDS API版本

xDS API的版本

5.1 - xDS协议的API版本指南

xDS协议的API版本指南

内存翻译自 https://github.com/envoyproxy/envoy/blob/main/api/API_VERSIONING.md

API版本指南

Envoy项目和 xDS工作组 认真对待API的稳定性和版本问题。提供稳定的API是确保API采用和生态系统成功的必要步骤。下面我们阐述了旨在提供这种稳定性的API版本管理准则。

API语义版本化

Envoy API由一系列包组成,例如:envoy.admin.v2alphaenvoy.service.trace.v2。每个包都是独立的版本,采用基于https://cloud.google.com/apis/design/versioning 的protobuf语义版本化方案。

包的主版本(major version)在其名称(和目录结构)中得到体现。例如,tracing API包的第二版被命名为 envoy.service.trace.v2,其组成的protos位于api/envoy/service/trace/v2中。每个protobuf都必须直接存在于版本包的命名空间中,我们不允许子包,如envoy.service.trace.v2.somethingelse

小版本和补丁版本将在未来实施,这项工作在 https://github.com/envoyproxy/envoy/issues/8416 中进行跟踪。

在日常讨论和GitHub标签中,我们指的是v2v3vN...等API。这有一个特定的技术含义。Envoy API中的任何特定信息,例如envoy.config.bootstrap.v3.Bootstrap,都会转而引用Envoy API中的一些包。这些包可能在vNv(N-1),等等。从技术上讲,Envoy API是一个版本包命名空间的DAG。当我们谈论vN xDS API时,我们实际上指的是根配置资源的N(例如bootstrap,xDS资源,如Cluster)。v3 API的引导配置是envoy.config.bootstrap.v3.Bootstrap,尽管它可能会转而引用envoy.service.trace.v2

向后兼容

一般来说,在包的主API版本内,我们不允许任何破坏性的改变。指导原则是,无论是通讯格式还是protobuf编译器生成的语言绑定,都不应该在变化中出现不能向后兼容的变化。具体来说:

  • 字段不应该被重新编号或改变其类型。这是标准的proto开发程序。
  • 不能重命名proto的字段或包的命名空间。这在本质上是危险的,因为:
    • 字段重命名会破坏协议的兼容性。这比标准的proto开发程序更严格,因为它不会破坏二进制通讯格式。然而,它破坏 YAML/JSON 加载到 protos 以及文本protos。由于我们认为 YAML/JSON 是第一类输入,我们不能改变字段名。
    • 对于服务定义,gRPC端点URL是由包的命名空间推断出来的,所以这将破坏客户/服务器的通信。
    • 对于嵌入 “Any” 对象的消息,type URL,即包命名空间的一部分,可以被Envoy或其他API消费代码使用。目前,这适用于嵌入 “DiscoveryResponse” 对象的顶级资源,例如 Cluster, Listener 等。
    • 消耗的代码将被破坏,需要修改源代码以配合API的变化。
  • 其他一些变化被认为是对Envoy API的破坏,在protobuf的兼容性方面通常被认为是安全的:
    • 将一个单例字段升级为重复字段,例如uint32 foo = 1;升级为repeated uint32 foo = 1。 这改变了JSON格式的表示,因此被认为是一个破坏性的改变。
    • 用 “oneof” 来包装一个现有的字段。这对 protobuf 或 JSON/YAML 格式没有影响,但对Go等语言中的各种消费存根有干扰,造成不必要的搅动。
    • 增加 protoc-gen-validate 注释的严格性。如果这些更严格的条件是对已经在结构上或文档中隐含的行为进行建模,则可以允许有例外。

上述策略也有例外:

  • 在引入新的API字段或消息后的14天内所做的更改,前提是该新字段或消息未被包含在 Envoy 发布中。
  • 标记为 vNalpha 的API版本。在alpha主版本中,允许任意的破坏性改变。
  • 任何带有[#not-implemented-hide:...注释的字段、消息或枚举。
  • 任何带有 (udpa.annotations.file_status).work_in_progress, (xds.annotations.v3.file_status).work_in_progress (xds.annotations.v3.message_status).work_in_progress 的 proto, 或者 (xds.annotations.v3.field_status).work_in_progress 选项注解。

请注意,对包装类型的默认值的改变,例如google.protobuf.UInt32Value,不受上述政策的约束。任何需要在Envoy API或主要版本内实现的稳定性的管理服务器都应该为这些字段设置明确的值。

API生命周期

一个新的主版本是xDS API生态系统中的重大事件,不可避免地需要客户端(Envoy、gRPC)和大量的控制平面的支持,从简单的内部定制管理服务器到供应商运行的xDS即服务(xDS-as-a-service)产品。xDS API牧羊人 将在以下限制条件下决定增加一个新的主要版本:

  • 在现有支持的主版本中,xDS APIs存在足够的技术债务,以证明xDS客户端/服务器实现的成本负担。
  • 自上一个主版本被削减以来,至少已经过去了一年。
  • 与Envoy社区(通过Envoy社区会议、Slack上的#xds频道)以及gRPC OSS社区(通过与语言维护者联系)进行协商。这不是一个否决的过程;API牧羊人保留了在权衡这些投入和上述前两个考虑因素后推进新的主要API版本的权利。

在新的主版本发布后,API的生命周期遵循废弃时钟。Envoy在任何时候都会支持任何API包的最多三个主要版本:

  • 当前稳定的主版本,如v3。
  • 上一个稳定主版本,如v2。这是为了确保我们为所支持的主要版本提供至少1年的时间。通过同时支持两个稳定的主要版本,这使得控制平面和Envoy的推出也更容易协调。在新的当前稳定的主要版本推出后,之前的这个稳定的主要版本将被支持整整1年,之后它将从Envoy实现中移除。
  • 可以选择下一个实验性的alpha主版本,例如v4alpha。这是下一个稳定大版本的候选发布版本。只有当当前的稳定大版本需要在下一个周期进行突破性的改变时,才会生成这个版本,例如,废弃或字段重命名。这个发布候选版本是通过 protoxform 工具从当前稳定的主要版本中机械地生成的,使用了诸如 deprecated = true 等注释。这不是一个可供人类编辑的工件。

一个例子是,在2020年12月底,如果v4主版本是合理的,我们可能会冻结envoy.config.bootstrap.v4alpha,然后这个包将成为当前稳定的主要版本envoy.config.bootstrap.v4envoy.config.bootstrap.v3包将成为之前的稳定主版本,并且对envoy.config.bootstrap.v2的支持将从Envoy实现中放弃。需要注意的是,如果被引用的软件包没有发生变化,那么一些过渡性引用的软件包,例如envoy.config.filter.network.foo.v2在这个版本中可能仍然是2版本。如果此时没有主版本的理由,削减v4的决定可能会在2021年或以后的某个时间点发生,然而v2支持仍将在2020年底被移除。

这个API生命周期和时钟的含义是,Envoy API中任何被废弃的功能将至少保留1-2年的实现支持。

我们目前正在制定一个策略,引入次要版本(https://github.com/envoyproxy/envoy/issues/8416)。这将使xDS API的次要版本在每次废弃和字段引入/修改时都会发生变化。这将为控制平面提供一个机会,使其有条件支持客户端和主要/次要API版本。目前正在讨论,但没有最终确定的是,在一个主要版本中支持了一年之后,Envoy客户端将不再支持过时的功能。请将关于这个问题的任何想法发布到https://github.com/envoyproxy/envoy/issues/8416。

新的 API 特性

Envoy的API可以被 安全扩展,增加新的包、消息、枚举、字段和枚举值,同时保持向后兼容。对一个给定的包的API的添加通常应该只对当前稳定的主版本进行。这个政策的基本原理是:

  • 该功能对使用当前稳定主版本的Envoy用户来说是立即可用的。如果该功能被放在 “vNalpha “中,情况就不是这样了。
  • vNalpha可以从vN中机械地生成,而不需要开发者在两个位置都维护新功能。
  • 我们鼓励Envoy用户从以前的版本转移到当前的稳定大版本,以使用新功能。

什么时候可以对软件包的上一个稳定的主要版本进行API修改?

作为一个务实的让步,我们允许在一个主要的API版本增加后的一个季度内对上一个稳定的主要版本进行API功能的增加。对上一个稳定的主版本的任何修改都必须以一致的方式反映在当前稳定的主要版本中。

如何进行跨主要版本的突破性修改

我们在一个主版本中保持向后兼容,但允许跨主版本的破坏性改变。这使得API的废弃、清理、重构和重组成为可能。Envoy API有一个风格化的工作流程来实现这一点。有两种规定的方法,取决于变化是机械的还是手动的。

机械破坏性变更

字段废弃、重命名等是机械性的改变,由 protoxform 工具支持。这些是由注释指导的。

手动破坏性变更

手动更改不同于机械更改,如字段废弃,因为一般来说,它需要在Envoy中手动实现新的代码和测试。例如,如果一个开发者想在路由配置中把 HeaderMatcherStringMatcher 统一起来,这可能是这类变化的一个候选者。需要采取以下步骤:

  1. 新版本的功能,例如 NewHeaderMatcher 消息应该和引用字段一起被添加到路由配置原语的当前稳定主版本中。
  2. 应该改变Envoy的实现,以便从(1)中添加的字段中使用配置。 应该编写翻译代码(和测试),以便从现有的字段和消息映射到(1)。
  3. 旧的消息/enum/字段/enum值应该被注释为废弃的。
  4. 在下一个主版本中,protoxform将自动删除废弃的版本。

这种先做后破的方法确保了API主要版本的发布是可预测的、机械的,并且大部分的Envoy代码和测试变化都是由功能开发者而不是API所有者拥有的。除了上述过程之外,不会有重大的 “vN “举措来解决技术债务。

客户端功能

不是所有的客户端都会支持某个主API版本中的所有字段和特性。一般来说,最好是使用Protobuf语义来支持,例如:

  • 忽略一个字段的内容就足以表明该支持在一个客户端中是缺失的。
  • 如果需要对一系列客户端的支持,同时设置废弃的和新的方法来表达一个字段(在这里不涉及巨大的开销或操作)。

这种方法并不总是有效,例如:

  • 一个路由匹配器的连接条件不应该仅仅因为客户端缺少实现匹配的能力而被忽略;这可能会导致路由策略被绕过。
  • 客户端可能期望服务器以某种格式或编码提供响应,例如不透明的扩展配置的JSON编码的Struct-in-Any表示。

为了这个目的,我们有客户端功能

One Definition Rule (ODR)

为了避免维护包的两个以上的稳定的主要版本,并应对钻石依赖,我们对包如何被过境引用增加了一个限制;包在其横向依赖集中最多可以有另一个包的一个版本。这意味着一些软件包在发布周期中会有一个重大的版本升级,只是为了让它们赶上其依赖关系的当前稳定版本。

通过对软件包之间的相互引用有严格的规定,可以避免这种复杂性和折腾。包的组织和 BUILD 的可见性约束应该被用来限制,以保持任何给定包的依赖树的浅层深度。

尽量减少折腾的影响

除了稳定性之外,API版本策略还有一个明确的目标,即尽量减少开发者对Envoy社区、其他API客户端(如gRPC)、管理服务器供应商和更广泛的API工具生态系统的开销。为了减少技术债务和支持API的发展,在主版本之间有一定数量的API折腾是可取的,但过多的API折腾会造成升级的成本和障碍。

我们认为弃用是强制性的变化。任何弃用都会在下一个稳定的API版本中被移除。

其他机械性的破坏性变化被认为是谨慎性的。这些变化包括字段重命名等,主要反映在protobuf注释中。`protoxform’工具可以决定通过推迟应用自由裁量的变化来尽量减少API的折腾,直到一个主版本周期,各自的信息正在经历一个强制性的变化。

Envoy的API结构有助于最大限度地减少版本间的流失。开发者应该设计和拆分包,使高流失率的程序,如HTTP连接管理器,被隔离在包中,并有一个浅的参考层次。

6 - xDS资料收集

收集xDS的各种资料

官方资料

官方网站

文档

社区资料

网上文章