1 - xDS介绍

xDS介绍

1.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协议描述。

1.2 - xDS术语和概念

xDS术语和概念介绍

1.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

参考文档

1.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 示意图如下:

参考文档

1.2.3 - xDS的ADS

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

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

1.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)中的。

参考文档:

1.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内存开销。

参考文档:

1.3 - xDS API定义

xDS定义方式

1.3.1 - xDS中服务定义概述

xDS服务定义概述

1.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) {
  }
}

1.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"

1.4 - xDS API通讯协议

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

1.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,除非通过资源更新对基础资源进行了更改。

1.5 - xDS API版本

xDS API的版本

1.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连接管理器,被隔离在包中,并有一个浅的参考层次。

1.6 - xDS资料收集

收集xDS的各种资料

官方资料

官方网站

文档

社区资料

网上文章

2 - XDS API中的LDS

Envoy的LDS

2.1 - XDS API中的LDS概述

介绍Envoy的XDS API中的LDS

Listener

Listener Filter

LDS介绍

LDS是Listener Discovery Service的首字母缩写。

监听器发现服务(LDS)是一个可选的 API,Envoy 将调用它来动态获取监听器。Envoy 将协调 API 响应,并根据需要添加、修改或删除已知的监听器。

监听器更新的语义如下:

  • 每个监听器必须有一个独特的名字。如果没有提供名称,Envoy 将创建一个 UUID。要动态更新的监听器,管理服务必须提供监听器的唯一名称。
  • 当一个监听器被添加,在参与连接处理之前,会先进入“预热”阶段。例如,如果监听器引用 RDS 配置,那么在监听器迁移到 “active” 之前,将会解析并提取该配置。
  • 监听器一旦创建,实际上就会保持不变。因此,更新监听器时,会创建一个全新的监听器(使用相同的侦听套接字)。新增加的监听者都会通过上面所描述的相同“预热”过程。
  • 当更新或删除监听器时,旧的监听器将被置于 “draining(逐出)” 状态,就像整个服务重新启动时一样。监听器移除之后,该监听器所拥有的连接,经过一段时间优雅地关闭(如果可能的话)剩余的连接。逐出时间通过 --drain-time-s 选项设置。

注意: 任何在 Envoy 配置中静态定义的监听器都不能通过 LDS API 进行修改或删除。

2.2 - xDS中Listener的概念

xDS中Listener的概念

2.2.1 - [译]Envoy中的Listener

翻译Envoy中的Listener介绍内容

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

Listeners

The Envoy configuration supports any number of listeners within a single process. Generally we recommend running a single Envoy per machine regardless of the number of configured listeners. This allows for easier operation and a single source of statistics. Envoy supports both TCP and UDP listeners.

Envoy 支持单个进程中配置任意数量的监听器。通常情况下,部署 Envoy 的数量与监听器数量无关,建议每台机器部署一个 Envoy 即可。这使得操作会更加简便,而且每台机器只有一个监控数据来源。Envoy 同时支持 TCP 和 UDP 监听器。

TCP

Each listener is independently configured with some number filter chains, where an individual chain is selected based on its match criteria. An individual filter chain is composed of one or more network level (L3/L4) filters. When a new connection is received on a listener, the appropriate filter chain is selected, and the configured connection local filter stack is instantiated and begins processing subsequent events. The generic listener architecture is used to perform the vast majority of different proxy tasks that Envoy is used for (e.g., rate limiting, TLS client authentication, HTTP connection management, MongoDB sniffing, raw TCP proxy, etc.).

每个监听器都独立配置了多个 过滤器链,其中根据其 匹配条件 选择某个过滤器链。 一个独立的过滤器链由一个或多个网络层(L3/L4) 过滤器 组成。 当监听器上接收到新连接时,会选择适当的过滤器链,接着实例化配置的本地筛选器堆栈和处理后续事件。 通用监听器系统架构通常用于处理各不相同的代理任务,例如 Envoy 用于( 限速、TLS 客户端身份验证、HTTP 连接管理、MongoDB 嗅探、原始 TCP 代理 等)。

Listeners are optionally also configured with some number of listener filters. These filters are processed before the network level filters, and have the opportunity to manipulate the connection metadata, usually to influence how the connection is processed by later filters or clusters.

监听器还可以选择配置一些 监听过滤器。 这些过滤器在网络层过滤器之前处理,并且有机会去操作连接元数据,这样通常是为了影响后续过滤器或集群如何处理连接。

Listeners can also be fetched dynamically via the listener discovery service (LDS).

还可以通过 监听器发现服务 (LDS) 动态获取监听器。

UDP

Envoy also supports UDP listeners and specifically UDP listener filters. UDP listener filters are instantiated once per worker and are global to that worker. Each listener filter processes each UDP datagram that is received by the worker listening on the port. In practice, UDP listeners are configured with the SO_REUSEPORT kernel option which will cause the kernel to consistently hash each UDP 4-tuple to the same worker. This allows a UDP listener filter to be “session” oriented if it so desires. A built-in example of this functionality is the UDP proxy listener filter.

Envoy 还支持 UDP 监听器和 UDP 监听过滤器 。 每个工作线程都会实例化一次 UDP 监听过滤器,并且 UDP 监听过滤器对于工作线程来讲是全局可见的。 每个工作线程监听端口接收到的 UDP 数据报文由监听过滤器处理。 实际上,UDP 监听器配置有一个 SO_REUSEPORT 内核选项,这将导致内核会将属于同一个 UDP 四个元组的 UDP 报文分配给同一个工作线程。 这就允许 UDP 监听过滤器在需要时可以打开会话保持功能。 这个功能的一个内置示例就是 UDP 代理 监听过滤器。

参考文档

2.3 - xDS中Listener Filter的概念

xDS中Listener Filter的概念

2.3.1 - [译]Envoy中的Listener Filter

翻译Envoy中的Listener Filter介绍内容

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

As discussed in the listener section, listener filters may be used to manipulate connection metadata. The main purpose of listener filters is to make adding further system integration functions easier by not requiring changes to Envoy core functionality, and also make interaction between multiple such features more explicit.

如 监听器 一节所述, 监听器过滤器可以用于操纵连接元数据。 监听器过滤器的主要目的是更方便地添加系统集成功能,而无需更改 Envoy 核心功能,并使多个此类功能之间的交互更加明确。

The API for listener filters is relatively simple since ultimately these filters operate on newly accepted sockets. Filters in the chain can stop and subsequently continue iteration to further filters. This allows for more complex scenarios such as calling a rate limiting service, etc. Envoy already includes several listener filters that are documented in this architecture overview as well as the configuration reference.

监听器过滤器的 API 相对简单,因为最终这些过滤器是在新接收的套接字上操作的。可停止链中的过滤器并继续执行后续的过滤器。这允许去运作更复杂的业务场景,例如调用 限速服务 等。 Envoy 包含多个监听器过滤器,这些过滤器在架构概述以及 配置参考 中都有记录。

参考文档

2.3.2 - [译]Envoy中的Listener Filter Chain

翻译Envoy中的Listener Filter Chain介绍内容

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

As discussed in the listener section, network level (L3/L4) filters form the core of Envoy connection handling.

正如在 Listener 部分所讨论的,网络级(L3/L4)过滤器构成了Envoy连接处理的核心。

The network filters are chained in a ordered list known as filter chain. Each listener has multiple filter chains and an optional default filter chain. associated with each filter chain. If the best match filter chain cannot be found, the default filter chain will be chosen to serve the request. If the default filter chain is not supplied, the connection will be closed.

网络过滤器在一个有序的列表中被链起来,称为过滤器链。每个监听器都有多个过滤器链和一个与每个过滤器链相关的可选默认过滤器链。如果找不到最佳匹配的过滤链,将选择默认的过滤链来处理请求。如果没有提供默认的过滤链,连接将被关闭。

Filter chain only update

Filter chains can be updated independently. Upon listener config update, if the listener manager determines that the listener update is a filter chain only update, the listener update will be executed by adding, updating and removing filter chains. The connections owned by these destroying filter chains will be drained as described in listener drain.

过滤链可以独立更新。在 Listener 配置更新时,如果 listener 管理器确定 listener 更新只是更新过滤链,listener 更新将通过添加、更新和删除过滤链来执行。这些破坏的过滤链所拥有的连接将被逐出,如 listener drain 中所描述的那样。

If the new filter chain and the old filter chain is protobuf message equivalent, the corresponding filter chain runtime info survives. The connections owned by the survived filter chains remain open.

如果新的过滤链和旧的过滤链是 protobuf 消息等价的,那么相应的过滤链运行时信息就会存活。存活的过滤链所拥有的连接保持打开。

Not all the listener config updates can be executed by filter chain update. For example, if the listener metadata is updated within the new listener config, the new metadata must be picked up by the new filter chains. In this case, the entire listener is drained and updated.

并非所有的 listener 配置更新都可以通过过滤链更新来执行。例如,如果 listener 元数据在新的 listener 配置中被更新,新的元数据必须被新的过滤链所接收。在这种情况下,整个 listener 会被逐出并更新。

2.3.3 - [译]Envoy中的Network (L3/L4) filters

翻译Envoy中的Network (L3/L4) filters介绍内容

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

As discussed in the listener section, network level (L3/L4) filters form the core of Envoy connection handling. The filter API allows for different sets of filters to be mixed and matched and attached to a given listener. There are three different types of network filters:

正如在 listener 部分所讨论的,网络级(L3/L4)过滤器构成了Envoy连接处理的核心。过滤器API允许混合和匹配不同的过滤器集合,并附加到给定的监听器。有三种不同类型的网络过滤器:

  • Read: Read filters are invoked when Envoy receives data from a downstream connection.
  • Write: Write filters are invoked when Envoy is about to send data to a downstream connection.
  • Read/Write: Read/Write filters are invoked both when Envoy receives data from a downstream connection and when it is about to send data to a downstream connection.
  • :当Envoy收到来自下游连接的数据时,会调用读过滤器。

  • :写过滤器在Envoy收到下游连接的数据时被调用。当Envoy要向下游连接发送数据时,会调用写过滤器。

  • 读/写:读/写过滤器在Envoy从下游连接接收数据和即将向下游连接发送数据时都会被调用。

The API for network level filters is relatively simple since ultimately the filters operate on raw bytes and a small number of connection events (e.g., TLS handshake complete, connection disconnected locally or remotely, etc.). Filters in the chain can stop and subsequently continue iteration to further filters. This allows for more complex scenarios such as calling a rate limiting service, etc. Network level filters can also share state (static and dynamic) among themselves within the context of a single downstream connection. Refer to data sharing between filters for more details. Envoy already includes several network level filters that are documented in this architecture overview as well as the configuration reference.

网络级过滤器的API相对简单,因为最终过滤器操作的是原始字节和少量的连接事件(例如,TLS握手完成,连接在本地或远程断开,等等)。链上的过滤器可以停止,随后继续迭代到更多的过滤器。这允许更复杂的场景,如调用速率限制服务等。网络级过滤器也可以在单个下游连接的范围内相互共享状态(静态和动态)。更多细节请参考过滤器之间的数据共享。Envoy已经包含了几个网络级过滤器,在这个架构概述以及配置参考中都有记录。

2.4 - xDS中LDS API的定义和实现

xDS中Listener API的定义和实现

2.4.1 - LDS API的概述

介绍LDS API的服务定义和参数说明

LDS服务定义

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 = "*";
  }
}

请求参数

对于 DeltaDiscoveryRequest 和 DiscoveryRequest,请求参数中的 type_url 字段的取值是 “type.googleapis.com/envoy.config.listener.v3.Listener” (或者隐含)。

应答参数

对于 DiscoveryResponse 和 DeltaDiscoveryResponse,返回的资源类型被抽象为 google.protobuf.Any ,对于LDS返回的实际是 Listener 这个消息体,定义在 api/envoy/config/listener/v3/listener.proto 文件中:

package envoy.config.listener.v3;

message Listener {

  string name = 1;

  core.v3.Address address = 2 [(validate.rules).message = {required: true}];


  repeated FilterChain filter_chains = 3;

  repeated ListenerFilter listener_filters = 9;

  ......
}

详细的字段定义请见后续的详细解说。

2.4.2 - LDS API中的Listener配置

LDS API中的Listener配置

Listener配置

Listener 的配置详细定义在 xDS API 中 Linsenter 的 proto 定义文件中,地址如下:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener.proto#L39

Json格式

Listerner配置的JSON格式如下所示:

{
  "name": "...",
  "address": "{...}",
  "stat_prefix": "...",
  "filter_chains": [],
  "use_original_dst": "{...}",
  "default_filter_chain": "{...}",
  "per_connection_buffer_limit_bytes": "{...}",
  "metadata": "{...}",
  "drain_type": "...",
  "listener_filters": [],
  "listener_filters_timeout": "{...}",
  "continue_on_listener_filters_timeout": "...",
  "transparent": "{...}",
  "freebind": "{...}",
  "socket_options": [],
  "tcp_fast_open_queue_length": "{...}",
  "traffic_direction": "...",
  "udp_listener_config": "{...}",
  "api_listener": "{...}",
  "connection_balance_config": "{...}",
  "reuse_port": "...",
  "enable_reuse_port": "{...}",
  "access_log": [],
  "tcp_backlog_size": "{...}",
  "bind_to_port": "{...}"
}

proto格式

proto 文件定义如下:

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

  string name = 1;

  core.v3.Address address = 2 [(validate.rules).message = {required: true}];

  string stat_prefix = 28;

  repeated FilterChain filter_chains = 3;

  google.protobuf.BoolValue use_original_dst = 4;

  FilterChain default_filter_chain = 25;

  google.protobuf.UInt32Value per_connection_buffer_limit_bytes = 5
      [(udpa.annotations.security).configure_for_untrusted_downstream = true];

  core.v3.Metadata metadata = 6;

  DrainType drain_type = 8;

  repeated ListenerFilter listener_filters = 9;

  google.protobuf.Duration listener_filters_timeout = 15;

  bool continue_on_listener_filters_timeout = 17;

  google.protobuf.BoolValue transparent = 10;

  google.protobuf.BoolValue freebind = 11;

  repeated core.v3.SocketOption socket_options = 13;

  google.protobuf.UInt32Value tcp_fast_open_queue_length = 12;

  core.v3.TrafficDirection traffic_direction = 16;

  UdpListenerConfig udp_listener_config = 18;

  ApiListener api_listener = 19;

  ConnectionBalanceConfig connection_balance_config = 20;

  google.protobuf.BoolValue enable_reuse_port = 29;

  repeated accesslog.v3.AccessLog access_log = 22;

  google.protobuf.UInt32Value tcp_backlog_size = 24;

  google.protobuf.BoolValue bind_to_port = 26;
}

Listener字段说明

具体字段的说明:

字段 格式 说明
name string 该监听器的唯一名称。如果没有提供名称,Envoy将为监听器分配一个内部UUID。如果监听器要通过LDS动态更新或删除,必须提供唯一的名字。
address core.v3.Address, REQUIRED 听众应该收听的地址。一般来说,这个地址必须是唯一的,不过这要由操作系统的绑定规则决定。例如,多个监听器可以在Linux上监听端口0,因为实际的端口将由操作系统分配。
stat_prefix string 可选的前缀,用于监听器的统计信息。如果为空,统计信息将以 listener.<address as string> 为根。如果非空,统计信息将以listener.<stat_prefix>为根。
filter_chains FilterChain, REQUIRED 考虑用于这个监听器的过滤链列表。最符合 FilterChainMatch 条件的 FilterChain 将用于连接。
use_original_dst google.protobuf.BoolValue 如果使用iptables重定向连接,代理接收连接的端口可能与原始目标地址不同。当这个标志被设置为 “true” 时,监听器会将重定向的连接交给与原始目标地址相关的监听器。如果没有与原始目标地址相关联的监听器,连接将由接收它的监听器处理。默认为false。
default_filter_chain FilterChain 如果没有一个过滤器链相匹配,则为默认过滤器链。如果没有提供默认的过滤链,连接将被关闭。在这个字段中,过滤链的匹配被忽略。
per_connection_buffer_limit_bytes UInt32Value 对监听器新建连接的读写缓冲区大小的软限制。如果未指定,则使用实现定义的默认值(1MiB)。
metadata core.v3.Metadata 监听器元数据
drain_type DrainType 在监听器范围内执行的逐出类型。
listener_filters ListenerFilter[] Listener 过滤器有机会操作和增加连接元数据,例如在连接过滤器链匹配中使用。这些过滤器会在任何过滤器链之前运行。顺序很重要,因为过滤器是在监听器接受套接字后,在创建连接前,按顺序处理的。当监听器套接字地址中的协议是UDP时,可以指定UDP监听器过滤器。UDP监听器目前只支持一个过滤器。
listener_filters_timeout google.protobuf.Duration 等待所有监听器过滤器完成操作的超时时间。如果达到超时,接受的套接字将被关闭,而不创建连接,除非continue_on_listener_filters_timeout被设置为true。指定0来禁用超时。如果不指定,将使用默认的15s超时。
continue_on_listener_filters_timeout bool 当监听器过滤器超时时,是否应该创建连接。默认为false。
transparent google.protobuf.BoolValue 监听器是否应该被设置为透明套接字。

当这个标志设置为 “true “时,连接可以使用iptables TPROXY目标重定向到监听器,在这种情况下,接受的连接会保留原来的源地址和目的地址以及端口。这个标志应该与 original_dst 监听器过滤器结合使用,以标记连接的本地地址为 “restored”。这可以用来把每个重定向的连接移交给与该连接的目的地址相关的另一个监听器。没有使用TPROXY的套接字的直接连接无法与使用TPROXY重定向的连接区分开来,因此会被当作重定向的连接。当这个标志被设置为false时,监听器的套接字会被明确地重置为非透明的。设置这个标志需要Envoy以CAP_NET_ADMIN能力运行。当这个标志没有设置时(默认),套接字不会被修改,也就是说,透明选项既不会被设置也不会被重置。
freebind google.protobuf.BoolValue 监听器是否应设置 IP_FREEBIND 套接字选项。 当此标志设置为true时,可以将监听器绑定到未在运行Envoy的系统上配置的IP地址。 当此标志设置为false时,套接字上的选项 IP_FREEBIND 被禁用。 如果未设置此标志(默认值),则不会修改套接字,即既未启用也未禁用该选项。
socket_options core.v3.SocketOption Envoy源代码或预编译二进制文件中可能不存在的其他套接字选项。
tcp_fast_open_queue_length UInt32Value 监听器是否应接受 TCP Fast Open(TFO)连接。 当此标志设置为大于0的值时,将在套接字上启用选项TCP_FASTOPEN,其队列长度为指定大小(请参阅RFC7413中的详细信息)。 当此标志设置为0时,套接字上禁用选项TCP_FASTOPEN。 如果未设置此标志(默认值),则不会修改套接字,即既未启用也未禁用该选项。
traffic_direction core.v3.TrafficDirection 指定流量相对于本地 envoy 的预期方向。对于使用原始目的地过滤器的 listener 来说,这个属性在Windows下是必需的,见原始目的地
udp_listener_config UdpListenerConfig 如果协议中的监听器套接字地址的协议是UDP,这个字段指定UDP监听器的具体配置。
api_listener ApiListener 用于表示一个API监听器,在非代理客户端中使用。暴露给非代理应用程序的API类型取决于API监听器的类型。当这个字段被设置时,除了名字之外,其他字段都不应该被设置。
connection_balance_config ConnectionBalanceConfig 监听器的连接平衡器配置,目前只适用于TCP监听器。如果没有指定配置,Envoy将不会尝试在工作线程之间平衡活动连接。

在这种情况下,监听器X通过在X中设置use_original_dst,在Y1和Y2中设置bind_to_port为false,将所有连接重定向到监听器Y1和Y2,建议禁用监听器X中的平衡配置,以避免平衡的成本,并启用Y1和Y2中的平衡配置,在工作者之间平衡连接。
reuse_port bool 弃用。请使用 enable_reuse_port
enable_reuse_port google.protobuf.BoolValue 当这个标志被设置为 “true” 时,监听器会设置SO_REUSEPORT套接字选项,为每个工作线程创建一个套接字。这使得在连接数较多的情况下,入站连接在工作线程之间大致均匀分布。当这个标志被设置为false时,所有工作线程共享一个套接字。这个字段的默认值是true。
access_log accesslog.v3.AccessLog[] 该监听器发出的访问日志的配置。
tcp_backlog_size google.protobuf.UInt32Value tcp 监听器的待处理连接队列可以增长的最大长度。如果没有提供net.core.somaxconn的值,在Linux上将会使用,否则将使用128。
bind_to_port google.protobuf.BoolValue 监听器是否应该与端口绑定。不绑定的监听器只能接收从其他设置use_original_dst为true的监听器重定向的连接。默认为true。

2.4.3 - LDS API中的Filter配置

LDS API中的Filter配置

Listener Filter的配置详细定义在 xDS API 中 Linsenter Components 的 proto 定义文件中,地址如下:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener_components.proto#L28

Json格式

Listerner Filter配置的JSON格式如下所示:

{
  "name": "...",
  "typed_config": "{...}"
}

proto格式

proto 文件定义如下:

message Filter {

  string name = 1 [(validate.rules).string = {min_len: 1}];

  oneof config_type {

    google.protobuf.Any typed_config = 4;

    core.v3.ExtensionConfigSource config_discovery = 5;
  }
}

Listener字段说明

具体字段的说明:

字段 格式 说明
name string 要实例化的过滤器的名称。该名称必须符合支持的过滤器
typed_config google.protobuf.Any 过滤器的具体配置,取决于正在实例化的过滤器。参见支持的过滤器的进一步的文档。
config_discovery core.v3.ExtensionConfigSource 扩展配置发现服务的配置源指定器。在失败和没有默认配置的情况下,监听器会关闭连接。

如果是通过 typed_config 来配置 filter,则 name 必须是已经被支持的 filter,具体目前支持的filter列表见:

https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/network_filters#config-network-filters

2.4.4 - LDS API中的Filter Chain配置

LDS API中的Filter Chain配置

Filter Chain

过滤器链包裹着一组匹配判据,一个选项TLS上下文,一组过滤器,以及其他各种参数。

Listener Filter Chain的配置详细定义在 xDS API 中 Linsenter Components 的 proto 定义文件中,地址如下:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener_components.proto#L201

Json格式

Listerner Filter Chain配置的JSON格式如下所示:

{
  "filter_chain_match": "{...}",
  "filters": [],
  "use_proxy_proto": "{...}",
  "transport_socket": "{...}",
  "transport_socket_connect_timeout": "{...}"
}

proto格式

proto 文件定义如下:

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

  FilterChainMatch filter_chain_match = 1;

  repeated Filter filters = 3;

  google.protobuf.BoolValue use_proxy_proto = 4
      [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"];

  core.v3.Metadata metadata = 5;

  core.v3.TransportSocket transport_socket = 6;

  google.protobuf.Duration transport_socket_connect_timeout = 9;

  string name = 7;

  OnDemandConfiguration on_demand_configuration = 8;
}

字段说明

具体字段的说明:

字段 格式 说明
filter_chain_match FilterChainMatch 匹配连接到该过滤链时使用的判据。
filters Filter 一个单独的网络过滤器的列表,它构成了与监听器建立的连接的过滤器链。顺序很重要,因为过滤器会随着连接事件的发生按顺序处理。注意:如果过滤器列表为空,连接将默认关闭。
use_proxy_proto google.protobuf.BoolValue 监听器是否应该在新连接上期待一个PROXY协议V1头。如果该选项被启用,监听器将假定连接的远程地址是头中指定的地址。包括AWS ELB在内的一些负载均衡器支持这个选项。如果该选项不存在或设置为false,Envoy将使用连接的物理对等地址作为远程地址。

这个字段已经废弃了。可以明确地添加PROXY协议监听器过滤器来代替。
metadata core.v3.Metadata 过滤链元数据
transport_socket core.v3.TransportSocket 可选的自定义传输套接字实现,用于下游连接。要设置TLS,在typed_config中设置一个名称为envoy.transport_sockets.tls的传输套接字和DownstreamTlsContext。如果没有指定传输套接字的配置,新的连接将用纯文本建立。
transport_socket_connect_timeout google.protobuf.Duration 如果存在并且不为零,允许传入的连接完成任何传输套接字协商的时间量。如果这个时间在传输报告连接建立之前过期,连接将被立即关闭。

FilterChainMatch

指定为 Listener 选择特定过滤链的匹配判据。

为了选择一个过滤链,它的所有判据必须被传入的连接满足,其属性由网络堆栈和/或监听器过滤器设置。

以下顺序适用:

  • 目的地端口。
  • 目的地IP地址。
  • 服务器名称(例如:TLS协议的SNI)。
  • 传输协议。
  • 应用协议(例如:TLS协议的ALPN)。
  • 直接连接的源IP地址(这只有在使用覆盖源地址的监听器过滤器时才会与源IP地址不同,如代理协议监听器过滤器)。
  • 源类型(例如,任何,本地或外部网络)。
  • 源IP地址。
  • 源端口

对于允许范围或通配符的标准,将使用任何配置的过滤链中与传入连接相匹配的最具体的值(例如,对于SNI www.example.com,最具体的匹配将是www.example.com,然后是*.example.com,然后是*.com,然后是任何没有server_names要求的过滤链)。

用另一种方式来推理过滤链的匹配。假设存在N个过滤链。使用上述8个步骤对过滤链集进行修剪。在每个步骤中,与属性最具体匹配的过滤链继续进入下一个步骤。监听器保证在所有的步骤之后最多剩下1个过滤链。

例如:

对于目的端口,指定传入流量的目的端口的过滤链是最具体的匹配。如果没有一个过滤链指定确切的目的端口,那么不指定端口的过滤链是最具体的匹配。指定错误端口的过滤链永远不可能成为最具体的匹配。

Listener Filter Chain Match的配置详细定义在 xDS API 中 Linsenter Components 的 proto 定义文件中,地址如下:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener_components.proto#L97

Json格式

Listerner Filter Chain Match配置的JSON格式如下所示:

{
  "destination_port": "{...}",
  "prefix_ranges": [],
  "direct_source_prefix_ranges": [],
  "source_type": "...",
  "source_prefix_ranges": [],
  "source_ports": [],
  "server_names": [],
  "transport_protocol": "...",
  "application_protocols": []
}

proto格式

proto 文件定义如下:

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

  google.protobuf.UInt32Value destination_port = 8 [(validate.rules).uint32 = {lte: 65535 gte: 1}];

  repeated core.v3.CidrRange prefix_ranges = 3;

  string address_suffix = 4;

  google.protobuf.UInt32Value suffix_len = 5;

  repeated core.v3.CidrRange direct_source_prefix_ranges = 13;

  ConnectionSourceType source_type = 12 [(validate.rules).enum = {defined_only: true}];

  repeated core.v3.CidrRange source_prefix_ranges = 6;

  repeated uint32 source_ports = 7
      [(validate.rules).repeated = {items {uint32 {lte: 65535 gte: 1}}}];

  repeated string server_names = 11;

  string transport_protocol = 9;

  repeated string application_protocols = 10;
}

字段说明

具体字段的说明:

字段 格式 说明
destination_port google.protobuf.UInt32Value 当监听器上设置use_original_dst时,在确定过滤链匹配时要考虑的可选目标端口。
prefix_ranges core.v3.CidrRange[] 如果非空,则是一个IP地址和前缀长度,以便在监听器被绑定到0.0.0.0/::或指定use_original_dst时匹配地址。
direct_source_prefix_ranges core.v3.CidrRange[] 如果下游连接的直接连接源IP地址包含在至少一个指定的子网中,则满足该条件。如果没有指定参数或者列表为空,直接连接的源IP地址将被忽略。
source_type ConnectionSourceType 指定连接源IP匹配类型。可以是任何,本地或外部网络。
source_prefix_ranges core.v3.CidrRange[] 如果下游连接的源IP地址包含在至少一个指定的子网中,则满足该条件。如果没有指定参数或列表为空,则源IP地址被忽略。
source_ports uint32[] 如果下游连接的源端口包含在至少一个指定的端口中,则满足该条件。如果没有指定该参数,源端口将被忽略。
server_names string[] 如果非空,则是一个服务器名称的列表(例如TLS协议的SNI),在确定过滤器链匹配时要考虑。这些值将与新连接的服务器名称进行比较,当被一个监听器过滤器检测到时。

服务器名称将与所有通配符域名进行匹配,即www.example.com,然后是*.example.com,然后是*.com。

注意,不支持部分通配符,像*w.example.com这样的值是无效的。
transport_protocol string 如果非空,在确定过滤器链匹配时要考虑的传输协议。这个值将与新连接的传输协议进行比较,当它被一个监听器过滤器检测到时。

建议的值包括:

- raw_buffer - 默认值,在没有检测到传输协议时使用。

- tls - 当检测到TLS协议时由envoy.filters.listener.tls_inspector设置。
application_protocols string[] 如果非空,则是一个应用协议的列表(例如,TLS协议的ALPN),在确定过滤器链的匹配时要考虑。这些值将与新连接的应用协议进行比较,当被一个监听器过滤器检测到时。

建议的值包括。

- http/1.1 - 由 envoy. filters.listener.tls_inspector 设置。

- h2 - 由 envoy.filters.listener.tls_inspector 设置。

2.4.5 - LDS API中的Listener Filter配置

LDS API中的Listener Filter配置

Listener Filter的配置详细定义在 xDS API 中 Linsenter Components 的 proto 定义文件中,地址如下:

https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/listener/v3/listener_components.proto#L336

Json格式

Listerner Filter Chain配置的JSON格式如下所示:

{
  "name": "...",
  "typed_config": "{...}",
  "filter_disabled": "{...}"
}

proto格式

proto 文件定义如下:

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

  string name = 1 [(validate.rules).string = {min_len: 1}];

  oneof config_type {
    google.protobuf.Any typed_config = 3;
  }

  ListenerFilterChainMatchPredicate filter_disabled = 4;
}

字段说明

具体字段的说明:

字段 格式 说明
name string 要实例化的过滤器的名称。该名称必须符合支持的过滤器。
typed_config google.protobuf.Any 过滤器的具体配置,取决于正在实例化的过滤器。参见支持的过滤器的进一步文件。
filter_disabled ListenerFilterChainMatchPredicate 用于禁用过滤器的可选匹配谓词。当这个字段为空时,过滤器被启用。更多例子见ListenerFilterChainMatchPredicate。

如果是通过 typed_config 来配置 filter,则 name 必须是已经被支持的 filter,具体目前支持的filter列表见:

https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/listener_filters/listener_filters#config-listener-filters

2.5 - xDS中LDS的版本演进

xDS中LDS API的版本演进

2.5.1 - LDS版本演进

介绍LDS在各个版本中的演进

LDS定义

LDS v1

TODO:LDS v1的定义在哪里?

LDS v2

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/lds.proto

// Envoy实例在启动时发起一个RPC,以发现监听器列表。
// 更新是通过LDS服务器的流式传输的,包括所有监听器的完整更新。
// 监听器不再存在时,现有的连接将被允许剔除。
service ListenerDiscoveryService {
  option (envoy.annotations.resource).type = "envoy.api.v2.Listener";

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

  rpc StreamListeners(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

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

LDS v3

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

// Envoy实例在启动时发起一个RPC,以发现监听器列表。
// 更新是通过LDS服务器的流式传输的,包括所有监听器的完整更新。
// 监听器不再存在时,现有的连接将被允许剔除。
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 = "*";
  }
}

和 LDS v2 API相比:

  1. option type 从 envoy.api.v2.Listener 修改为 envoy.config.listener.v3.Listener
  2. 各种message定义都增加了 discovery.v3 的前缀

相关消息体的定义

DeltaDiscoveryRequest

3 - xDS API中的RDS

xDS API中的RDS

3.1 - xDS API中的RDS概述

xDS API中的RDS

RDS API定义在 api/envoy/api/v2/rds.proto:

service RouteDiscoveryService {
  rpc StreamRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

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

  rpc FetchRoutes(DiscoveryRequest) returns (DiscoveryResponse) {
  }
}

3.2 - xDS API中的Route API

Envoy的Route配置参考手册

备注:内容来自 https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/rds.proto

Route配置

配置详细信息实际的源头是来自xDS API 中 Linsenter 的 proto 定义文件,地址如下:

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/rds.proto#L44

Listerner配置的JSON格式如下所示:

{
  "name": "...",
  "virtual_hosts": [],
  "internal_only_headers": [],
  "response_headers_to_add": [],
  "response_headers_to_remove": [],
  "request_headers_to_add": [],
  "request_headers_to_remove": [],
  "validate_clusters": "{...}"
}

具体字段的说明:

字段 格式 说明
name string 路由配置的名称。 例如,它可能与 config.filter.network.http_connection_manager.v2.Rds中的 route_config_name 匹配。
virtual_hosts route.VirtualHost 组成路由表的一组虚拟主机(virtual host)。
internal_only_headers string 可选地指定 HTTP header 列表,连接管理器将仅视为内部的。如果在外部请求中找到它们,则会在过滤器调用之前清除它们。 有关更多信息,请参阅 x-envoy-internal
response_headers_to_add core.HeaderValueOption 指定HTTP header列表,以添加到连接管理器编码的每个响应的。 在此级别指定的 header 将应用于来自任何封闭 route.VirtualHostroute.RouteAction 的header之后。 有关更多信息(包括header值语法的详细信息),请参阅有关自定义请求header的文档。
response_headers_to_remove string 指定应从连接管理器编码的每个响应中删除的HTTP header 列表。
request_headers_to_add core.HeaderValueOption 指定应添加到HTTP连接管理器路由的每个请求的HTTP header 列表。 在此级别指定的 header 将应用于来自任何封闭route.VirtualHostroute.RouteAction 的标头之后。 有关更多信息(包括 header 值语法的详细信息),请参阅有关自定义请求 header 的文档。
request_headers_to_remove string 指定应从HTTP连接管理器路由的每个请求中删除的HTTP header 列表。
validate_clusters BoolValue 一个可选的布尔值,指定路由表引用的集群是否将由集群管理器验证。如果设置为true且路由引用的群集不存在,则不会加载路由表。如果设置为false并且路由引用的集群不存在,则路由表将加载,如果在运行时选择了路由,路由器过滤器将返回404。如果通过route_config选项静态定义路由表,则此设置默认为true。如果通过rds选项动态加载路由表,则此设置默认为false。 在某些情况下,用户可能会覆盖默认行为(例如,将CDS与静态路由表一起使用时)。

4 - XDS API中的CDS

介绍Envoy的XDS API中的CDS

4.1 - XDS API中的CDS概述

介绍Envoy的XDS API中的CDS

CDS API定义在 api/envoy/api/v2/cds.proto:

service ClusterDiscoveryService {
  rpc StreamClusters(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

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

  rpc FetchClusters(DiscoveryRequest) returns (DiscoveryResponse) {
  }
}

4.2 - Cluster API

Envoy的Cluster配置参考手册

备注:内容来自 https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/cds.proto

Cluster配置

配置详细信息实际的源头是来自xDS API 中 Cluster 的 proto 定义文件,地址如下:

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/cds.proto#L50

Cluster配置的JSON格式如下所示:

{
  "name": "...",
  "alt_stat_name": "...",
  "type": "...",
  "eds_cluster_config": "{...}",
  "connect_timeout": "{...}",
  "per_connection_buffer_limit_bytes": "{...}",
  "lb_policy": "...",
  "hosts": [],
  "load_assignment": "{...}",
  "health_checks": [],
  "max_requests_per_connection": "{...}",
  "circuit_breakers": "{...}",
  "tls_context": "{...}",
  "common_http_protocol_options": "{...}",
  "http_protocol_options": "{...}",
  "http2_protocol_options": "{...}",
  "extension_protocol_options": "{...}",
  "dns_refresh_rate": "{...}",
  "dns_lookup_family": "...",
  "dns_resolvers": [],
  "outlier_detection": "{...}",
  "cleanup_interval": "{...}",
  "upstream_bind_config": "{...}",
  "lb_subset_config": "{...}",
  "ring_hash_lb_config": "{...}",
  "original_dst_lb_config": "{...}",
  "least_request_lb_config": "{...}",
  "common_lb_config": "{...}",
  "transport_socket": "{...}",
  "metadata": "{...}",
  "protocol_selection": "...",
  "upstream_connection_options": "{...}",
  "close_connections_on_host_health_failure": "...",
  "drain_connections_on_host_removal": "..."
}

具体字段的说明:

字段 格式 说明
name (string, REQUIRED) 提供群集的名称,该群集的名称在所有群集中必须是唯一的。 如果未提供 alt_stat_name,则在发出统计信息时使用群集名称。 任何:在发出统计信息时,群集名称中的:将转换为_。 默认情况下,群集名称的最大长度限制为60个字符。 通过将 --max-obj-name-len 命令行参数设置为所需的值,可以增加此限制。
alt_stat_name string 发出统计数据时要使用的群集名称的可选替代项。 任何:在发布统计信息时,名称中的:将转换为_。。 这不应与路由器过滤器头混淆。
type Cluster.DiscoveryType 用于解析集群的服务发现类型。
eds_cluster_config Cluster.EdsClusterConfig 用于群集EDS更新的配置。
connect_timeout Duration 新建到群集中主机的网络连接的超时。
per_connection_buffer_limit_bytes UInt32Value 集群连接读写缓冲区大小的软件限制。 如果未指定,则使用实现定义的默认值(1MiB)。
lb_policy Cluster.LbPolicy 负载均衡器类型,用于在群集中选择主机。
hosts core.Address 如果服务发现类型是STATIC,STRICT_DNS或LOGICAL_DNS,则需要设置主机。

这个字段已经被废弃,请设置 load_assignment 字段
load_assignment ClusterLoadAssignment 设置此选项是指定STATIC,STRICT_DNS或LOGICAL_DNS集群的成员所必需的。 此字段取代hosts字段。
health_checks core.HealthCheck 群集的可选活动运行状况检查配置。 如果未指定任何配置,则不会进行运行状况检查,并且所有集群成员始终被视为运行状况良好。
max_requests_per_connection UInt32Value 对单个上游连接的最大请求,可选。 HTTP/1.1和HTTP/2连接池实现都遵循此参数。如果没有指定,则没有限制。将此参数设置为1将有效禁用keep alive。
circuit_breakers cluster.CircuitBreakers 集群的熔断,可选。
tls_context auth.UpstreamTlsContext 用于连接到上游群集的TLS配置。 如果未指定TLS配置,则新连接不会用TLS。
common_http_protocol_options core.HttpProtocolOptions 处理HTTP请求时的其他选项。 这些选项适用于HTTP1和HTTP2请求。
http_protocol_options core.Http1ProtocolOptions 处理HTTP1请求时的其他选项。
http2_protocol_options core.Http2ProtocolOptions 即使需要默认的HTTP2协议选项,也必须设置此字段,以便Envoy在进行新的HTTP连接池连接时假定上游支持HTTP/2。 目前,Envoy仅支持上游连接的先验知识。 即使TLS与ALPN一起使用,也必须指定http2_protocol_options。 除此之外,这允许HTTP/2连接发生在纯文本上。
extension_protocol_options map<string, Struct> extension_protocol_options字段用于为上游连接提供特定于扩展的协议选项。 密钥应与扩展过滤器名称匹配,例如“envoy.filters.network.thrift_proxy”。 有关特定选项的详细信息,请参阅扩展的文档。
dns_refresh_rate Duration 如果指定了DNS刷新率且群集类型为STRICT_DNS或LOGICAL_DNS,则此值将用作群集的DNS刷新率。 如果未指定此设置,则默认值为5000毫秒。 对于除STRICT_DNS和LOGICAL_DNS之外的集群类型,将忽略此设置。
dns_lookup_family Cluster.DnsLookupFamily DNS IP地址解析策略。 如果未指定此设置,则默认值为AUTO。
dns_resolvers core.Address 如果指定了DNS解析器且集群类型为STRICT_DNS或LOGICAL_DNS,则此值用于指定集群的dns解析器。 如果未指定此设置,则该值默认为默认解析程序,该解析程序使用/etc/resolv.conf进行配置。 对于除STRICT_DNS和LOGICAL_DNS之外的集群类型,将忽略此设置。
outlier_detection cluster.OutlierDetection 如果指定,将为此上游群集启用异常值检测。 可以通过运行时值覆盖每个配置值。
cleanup_interval Duration 从群集类型 ORIGINAL_DST 中删除过时主机的时间间隔。 如果在此间隔期间未将主机用作上游目标,则认为主机已过时。 当新连接重定向到Envoy时,新主机会根据需要添加到原始目标群集,从而导致群集中的主机数量随时间增长。 非陈旧的主机(它们被主动用作目的地)保留在群集中,这允许与它们的连接保持打开状态,从而节省了在打开新连接时可能花费的延迟。 如果未指定此设置,则默认值为5000毫秒。 对于ORIGINAL_DST以外的群集类型,将忽略此设置。
upstream_bind_config core.BindConfig 用于绑定新建立的上游连接的可选配置。这将覆盖bootstrap proto中指定的任何bind_config。如果地址和端口为空,则不执行绑定。
lb_subset_config Cluster.LbSubsetConfig 负载均衡子集的配置。
ring_hash_lb_config Cluster.RingHashLbConfig Ring Hash负载均衡策略的可选配置。

LbPolicy选择的负载平衡算法的可选配置。 目前只有RING_HASH和LEAST_REQUEST具有其他配置选项。 指定ring_hash_lb_config或least_request_lb_config而不设置相应的LbPolicy将在运行时生成错误。

只能设置ring_hash_lb_config,original_dst_lb_config,least_request_lb_config中的一个。
original_dst_lb_config Cluster.OriginalDstLbConfig 原始目标负载平衡策略的可选配置。

LbPolicy选择的负载平衡算法的可选配置。 目前只有RING_HASH和LEAST_REQUEST具有其他配置选项。 指定ring_hash_lb_config或least_request_lb_config而不设置相应的LbPolicy将在运行时生成错误。

只能设置ring_hash_lb_config,original_dst_lb_config,least_request_lb_config中的一个。
least_request_lb_config Cluster.LeastRequestLbConfig LeastRequest负载平衡策略的可选配置。

LbPolicy选择的负载平衡算法的可选配置。 目前只有RING_HASH和LEAST_REQUEST具有其他配置选项。 指定ring_hash_lb_config或least_request_lb_config而不设置相应的LbPolicy将在运行时生成错误。

只能设置ring_hash_lb_config,original_dst_lb_config,least_request_lb_config中的一个。
common_lb_config Cluster.CommonLbConfig 所有负载均衡器实现的通用配置。
transport_socket core.TransportSocket 用于上游连接的自定义传输套接字实现,可选。
metadata core.Metadata 元数据字段可用于提供有关群集的其他信息。 它可用于统计信息,日志记录和不同的过滤器行为。 字段应使用反向DNS表示法来表示Envoy中哪个实体需要该信息。 例如,如果元数据用于路由器过滤器,则应将过滤器名称指定为envoy.router。
protocol_selection Cluster.ClusterProtocolSelection 确定Envoy如何选择用于与上游主机通信的协议。
upstream_connection_options UpstreamConnectionOptions 上游连接的可选选项。
close_connections_on_host_health_failure bool 如果上游主机变得不健康(由配置的运行状况检查或异常检测确定),立即关闭与故障主机的所有连接。

目前仅支持tcp_proxy创建的连接。

当检测到不健康状态时,此功能的当前实现会立即关闭所有连接。 如果向上游主机开放的大量连接变得不健康,Envoy可能会花费大量时间专门关闭这些连接,而不会处理任何其他流量。
drain_connections_on_host_removal bool 如果此群集使用EDS或STRICT_DNS配置其主机,请立即从已从服务发现中删除的任何主机中排除连接。

这仅影响正在进行健康检查的主机的行为。 如果此标志未设置为true,Envoy将等待,直到主机无法进行活动运行状况检查,然后才能将其从群集中删除。

5 - EDS

介绍Envoy的XDS API中的EDS

5.1 - EDS概述

介绍Envoy的XDS API中的EDS

EDS API定义在 api/envoy/api/v2/eds.proto:

service EndpointDiscoveryService {
  rpc StreamEndpoints(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
  }

  rpc FetchEndpoints(DiscoveryRequest) returns (DiscoveryResponse) {
  }
}

5.2 - Endpoint API

Envoy的Endpoint配置参考手册

备注:内容来自 https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/cds.proto

ClusterLoadAssignment

配置详细信息实际的源头是来自xDS API 中 Endpoint 的 proto 定义文件,地址如下:

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/eds.proto#L43

来自RDS的每个路由将使用 RDS WeightedCluster 中表示的权重映射到单个群集或跨群集的流量分割。

使用EDS,从LB视角看,每个群集都是独立进行处理,LB在群集内的位置之间以及位置内主机之间的更精细粒度之间进行。 对于给定的群集,主机的有效权重是其 load_balancing_weight 乘以其 Locality 的 load_balancing_weight

{
  "cluster_name": "...",
  "endpoints": [],
  "policy": "{...}"
}

具体字段的说明:

字段 格式 说明
cluster_name (string, REQUIRED) 集群的名称。 如果在群集EdsClusterConfig中指定,这将是service_name值。
endpoints endpoint.LocalityLbEndpoints 要负载均衡的端点列表。
policy ClusterLoadAssignment.Policy 负载均衡策略设置。

ClusterLoadAssignment.Policy

配置详细信息实际的源头是来自xDS API 的 proto 定义文件,地址如下:

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/eds.proto#L54

负载均衡策略设置。

{
  "drop_overloads": [],
  "overprovisioning_factor": "{...}"
}

具体字段的说明:

字段 格式 说明
drop_overloads ClusterLoadAssignment.Policy.DropOverload 裁剪整体传入流量以保护上游主机的操作。 如果主机无法从中断中恢复,或者由于任何原因无法自动调整或无法处理传入流量,则此操作可以提供保护。

在客户端,每个类别一个接一个地应用,以生成所有传出流量的“实际”丢弃百分比。
overprovisioning_factor UInt32Value 优先级和地点被认为是过度设置的因素(百分比)。 这意味着我们不认为优先级或地点不健康,直到健康主机的百分比乘以过度配置因子降至100以下。默认值140(1.4),Envoy不认为优先级或地点不健康 直到它们的健康宿主比例降至72%以下。 阅读更多优先级和地区。

ClusterLoadAssignment.Policy.DropOverload

配置详细信息实际的源头是来自xDS API 的 proto 定义文件,地址如下:

https://github.com/envoyproxy/envoy/blob/master/api/envoy/api/v2/eds.proto#L57

{
  "category": "...",
  "drop_percentage": "{...}"
}

具体字段的说明:

字段 格式 说明
category (string, REQUIRED) 指定丢弃策略的标识符。
drop_percentage type.FractionalPercent 应该为该类别丢弃的流量百分比。

6 - ADS

介绍Envoy的XDS API中的ADS

6.1 - ADS概述

介绍Envoy的XDS API中的ADS

ADS请求与其单个 xDS 对应项具有相同的结构,但可以在单个stream上复用多种资源类型。

DiscoveryRequest / DiscoveryResponse 中的 type_url 提供了足够的信息来恢复 Envoy 实例和管理服务器上的多路复用的单个API。

ADS 的定义在 api/envoy/service/discovery/v2/ads.proto

注意:ADS 是只能用于 gRPC 的API

service AggregatedDiscoveryService {
  rpc StreamAggregatedResources(stream envoy.api.v2.DiscoveryRequest)
      returns (stream envoy.api.v2.DiscoveryResponse) {
  }

  rpc DeltaAggregatedResources(stream envoy.api.v2.DeltaDiscoveryRequest)
      returns (stream envoy.api.v2.DeltaDiscoveryResponse) {
  }
}