xDS协议

xDS协议-CSDN博客

xDS协议介绍

xDS协议是X Disvocery Service 这里的X表示它不是指某个具体的协议,是一组基于不同数据源服务发现协议的总称,包括CDS,LDS,EDS,SDS等。客户端可以使用多种方式获取数据源,比如监听指定文件,订阅gRPCstream以及轮RESTAPI等。

这个协议是Envoy规定的,Envoy是一个数据面,控制面实现了xDS的都可以叫做服务网格体系。Istio就是一个典型的例子,我们后续的讲解都围绕istio来进行。

再istio架构中基于xDS协议提供了标准的控制平面规范,并以此向数据面传递服务信息和治理规则。再Envoy中,xDS被称为数据平面API,并且担任控制平面Pilot和数据平面Envoy的通信协议,同事这些API再特定场景也可以被其他代理使用。

利用xDS协议,Envoy可以实现配置的完全动态化,配置实时更新而无需重启Envoy或者影响业务。此外,利用其L3/L4/L7 Filter机制,Envoy可以完全无侵入的扩展各种强大的功能。利用其内置的Tracing机制和Stats模块,可以很方便的实现对流量的跟踪以及监控,保证Envoy中流量的可观察性。无论是Envoy Filter或者其Stats,都包含大量的内容,此处不会详述。本文只会概括性介绍Envoy中一些关键概念以及xDS相关内容。


再Pilot和Envoy的通信场景中,xDS协议是基于gRPC实现的传输协议,即Envoy通过Grpc streaming订阅pilot的资源配置。Pilot接住ADS对API更新推送排序的能力,按照CDS-EDS-LDS-RDS的顺序串行分发配置。

MCP是istio中调用一个组件,用于在istio控制平面中传递和同步配置信息,包括路由规则,策略配置和其他的网络配置

MCP主要用于控制平面的组件之间进行配置信息的交互和同步。它通过gRPC接口提供了一种标准化的协议,用于配置更新的传输和处理。MCP的主要作用是实现控制平面组件之间的配置一致性和同步,确保所有组件都有相同的配置视图。

ADS可以将xDS所有的协议都聚合到一起,即上文提到的CDS、EDS、LDS和RDS等,Envoy通过这些API可以动态从Pilot获取对Cluster、Endpoint、Litener、和Route等资源的配置。下标整理了主要的xDS API。

  • CDS:集群发现服务,Envoy使用它进行路由的时候发现上游Cluster。Envoy通常会优雅的添加更新删除Cluster。有了CDS协议,Envoy在初次启动的时候不一定要感知拓扑上游的Cluster。在做路由HTTP请求的时候通过HTTP请求里添加Cluster信息实现转发请求.
  • EDS:EDS即Endpoint Discovery Service的缩写。再Envoy的属于中,Endpoint即Cluster的成员。Envoy通过EDS API可以更加智能动态获取上游API。
  • LDS:基于此,Envoy可以发现所有的Litener包括L3和L4 filter等所有的filter栈,并由此执行各种代理工作,如认证、TCP代理和HTTP代理等。添加LDS使得Envoy的任何配置都可以动态执行,只有发生一些非常罕见的变更(管理员追踪、启动等)、证书轮转或二进制更新时才会使用热更新。
  • RDS:RDS即Router Discovery Service缩写,用于Envoy在运行时为HTTP链接管理filter获取完整的路由配置,比如HTTP头部修改等。并且路由配置会被优雅的写入而无需影响已有的请求。当RDS和EDS、CDS共同使用时,可以帮助构建一个复杂的路由拓扑蓝绿发布等。
  • ADS:EDS,CDS等每个独立的服务对应了不同的gRPC服务名称,需要控制不同资源抵达Envoy的顺序的需求,可以使用聚合发现服务,即Aggregated xDS,它可以通过单一的gRPC服务流支持所有的资源类型,借助于有序的配置分发,从而解决资源更新顺序问题

xDS协议的基本流程

作为Pilot和Envoy之间通信协议的xDS,可以通过俩种方式实现:gRPC和REST,无论那种方法都是通过xDS API发送DiscoveryRequest请求,然后解析响应DiscoveryResponse中包含的配置信息并动态加载

DiscoveryRequest

DiscoveryRequest 是结构化的请求,它为某个 Envoy 请求包含了某些 xDS API 的一组版本化配置资源。相关字段展示如下表:

属性名类型作用
VersionInfostring成功加载的资源版本号,首次为空
Node*core.Node发起请求的节点信息,如位置信息等元数据
ResourceNames[]string请求的资源名称列表,为空表示订阅所有的资源
TypeUrlstring资源类型
ResponseNoncestringACK/NACK 特定的 response
ErrorDetail*rpc.Status代理加载配置失败,ACK 为空

DiscoveryResponse

类似于 DiscoveryRequest,DiscoveryResponse 的相关字段如下表:

属性名类型作用
VersionInfostringPilot 响应版本号
Resources[]types.Any序列化资源,可表示任意类型的资源
TypeUrlstring资源类型
Noncestring基于 gRPC 的订阅使用,nonce 提供了一种在随后的 DiscoveryRequest 中明确 ACK 特定 DiscoveryResponse 的方法

ACK、NAC

当Envoy使用DiscoveryRequest和DiscoveryResponse进行通信的时候,除了可以在类型级别指定版本,还有一种资源实例版本,它不属于API属性。例如如下的EDS请求。

version_info:
node: {id: envoy}
resource_names:
- foo
- bar
type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
response_nonce:

管理服务端可能会立即返回响应,也可能在请求资源可用时通过DisvoceryResponse返回

version_info: X
resources:
- foo ClusterLoadAssignment proto encoding
- bar ClusterLoadAssignment proto encoding
type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
nonce: A

当Envoy解析完DiscoverResponse以后,将通过流发送一个新的请求,指明最近成功应用的版本以及服务提供的Nonce(注:Nonce是加密通信中用于一次 一密的随机数,以免重放攻击)。借助于这个版本给Envoy管理服务端同时指明当前所使用的配置版本。这种ACK、NACK的机制分别实现对应新API配置版本或先前的API配置版本进行标识。

ACK

如果更新被成功应用version_info将如图所示置为X

NACK

如果Envoy拒绝了配置更新X,那么会返回具体的error_detail以及之前的版本号,下图中为空。

对于xDS客户端来说,每当收到DiscoveryResponse 时都用过进行ACK或NACK。ACK标识成功的配置更新,并且包含来自DisvoceryRespone的version_info,而NACK标识失败的配置更新,并包含之前的version_info。只有NACK应该有error_detail字段。

基于xDS的推和拉

Envoy在启动时会和pilot建立全双工的长链接,这就为实现双向配置分发提供了条件。具体来说Pilot与Envoy进行通信的时候有主动和被动俩种方式,它们分别对应推和拉俩个动作。在主动分发模式里,由Pilot监听到时间变化后发送给Envoy。在被动分发模式里,由Envoy订阅特定资源事件,当资源更新时生成配置并下发。

XDS协议的特点

对于通过gRPC streaming传输的xDS协议有四个变种,它们覆盖了俩个维度。

第一个维度是全量(State of the World:SotW)传输对比增量(Incremental)传输。早期的 xDS 使用了全量传输,客户端必须在每个请求里指定所有的资源名,服务端返回所有资源。这种方式的扩展性受限。所以后来引入了增量传输,在这种方式里允许客户端和服务端指定相对之前状态变化的部分,这样服务端就只需返回那些发生了变化的资源。同时增量传输还提供了对于资源的 “慢加载”。

第二个维度是每种资源的gRPC stream对比所有资源聚合gRPC stream。同样欠着是早起xDs早起使用的方式,它提供了最终一致性的模型。后者对应于那些需要显式控制传输流的场景。

所以这四个变脸分别为

  • state of the world(Basic xDS):全量传输独立gRPC stream
  • Incremental xDS:增量传输独立gRPC stream
  • Aggregated Discovery Service (ADS)全量传输聚合gRPCstream
  • Incremental ADS:增量传输聚合xDS

对于所有的全量方法,请求和响应类型分别为 DiscoveryRequest 和 DiscoverResponse;对于所有的增量方法,请求和响应类型分别为 DeltaDiscoveryRequest 和 DeltaDiscoveryResposne。

增量xDS

每个xDS协议都拥有俩种Grpc服务,一种是Stream,另一种是Delta。在Envoy设计早期采用全量更新策略,即以Stream的方式来提供强一致的配置同步。如此一来,任何的变更都会触发全量配置下发,显然这种全量更新的方式会为整个网格带来很高的负担。所以Envoy社区提出了Delta的xDS方案,当配置发生变化时,仅下发和更新发生变化的配置部分。

增量xDS利用gRPC全双工流,支持xDS服务追踪xDS客户端的状态。在增量xDS协议中,nonce域来指明DeltaDiscoveryResponse 和 DeltaDiscoveryRequest ACK 或 NACK。

对于DeltaDisvoceryRequest可以在如下场景里发送

  • xDS全双工gRPC stream中的初始化消息
  • 作为前序DeltaDisvoceryResponse的ACK或NACK
  • 再动态添加或溢出资源时客户端自动发来的DeltaDisveryRequest,此场景中必须忽略response_nonce字段

在下面第一个例子中,客户端收到第一个更新并且返回 ACK,而第二次更新失败返回了 NACK,之后 xDS 客户端自发请求 ‘wc’ 资源:

在网络重连以后,因为并没有对之前的状态进行保存,增量 xDS 客户端需要向服务器告知它已拥有的资源从而避免重复发送:

最终一致性

对于分布式系统而言,在设计之初选择强一致性还是最终一致性是很关键的一步,它直接关系到未来的应用场景。比如 ZooKeeper 就是强一致性服务发现的代表。但是对于服务网格的场景来说,可能同时存在成百上千个节点,这些节点间进行如此庞大的数据复制是相当困难的,并且很有可能会耗尽资源。也就是说对于分布式系统来说,为了提供强一致性需要付出巨大的代价。Envoy 在设计之初就选择了最终一致性,并且从底层线程模型到上层配置发现都进行了相应的实现。这样一来不仅简化了系统,提供了更好的性能,也更方便运维。

因为Envoy xDS API是满足最终一致性,不分流量可能在更新时被丢弃。比如只有集群X可以通过CDS/EDS发现,那么当引用集群X的路由配置更新时,并且在CDS/EDS更新前将配置指向集群Y,那么在Envoy实例获取配置前的部分流量会被丢弃。

对于一些应用来说可以接受暂时的流量丢弃,再客户端或者其他Envoy Sidecar的重试会掩盖这次丢弃。对于其他无法忍受的数据丢失场景来说,流量丢弃可以通过更新对集群X和Y的CDS/EDS来避免,然后RDS更新将X指向Y,并且CDS/EDS更新中丢弃集群X。

通常为了避免丢弃,更新的顺序应该遵循 make before break 规则,即:

  • CDS 更新应该被最先推送;
  • 对相应集群的 EDS 更新必须在 CDS 更新后到达;
  • LDS 更新必须在对应的 CDS/EDS 更新后到达;
  • 对新增的相关监听器的 RDS 更新必须在 CDS/EDS/LDS 更新后到达;
  • 对任何新增路由配置相关的 VHDS 更新必须在 RDS 更新后到达;
  • 过期的 CDS 集群和相关的 EDS 端点此刻被移除;
  • 如果没有新的集群、路由或监听器添加,或者应用可以接受短期的流量丢弃,那么 xDS 更新可以被独立推送。在 LDS 更新的场景里,监听器要在收到流量前被预热。当添加、移除或更新集群时要对集群进行预热。另一方面,路由不需要被预热。
Last modification:April 19, 2024
如果觉得我的文章对你有用,请随意赞赏