Envoy xDS API 与协议全解析:v3 版本支持、四种传输变体、ACK/NACK 与客户端特性
2026/9/15 6:39:37 网站建设 项目流程

Envoy xDS API 与协议全解析:v3 版本支持、四种传输变体、ACK/NACK 与客户端特性

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

本篇技术指南以 Envoy 官方 API 文档区(api.rst 及其下 api_supported_versions.rst、v3 API 参考、xDS REST/gRPC 协议、客户端特性)为核心骨架,系统讲解 Envoy 动态配置体系 xDS 的版本演进、订阅方式、四种传输协议变体、ACK/NACK 确认机制、资源预热与最终一致性等关键机制,并结合仓库中的 proto 定义与官方配置示例(discovery.proto、ads.yaml)给出底层实现佐证。读完本文,你将掌握 xDS 协议在 Envoy 中的完整工作方式,能够正确选择 SotW / Delta / ADS 变体并理解管理面(control plane)需要遵循的交互约定。

API 区块导读

Envoy 的 API 文档区以docs/root/api/api.rst为入口,通过 toctree 组织四块内容:支持的 API 版本(Supported API versions)、v3 API 完整参考(v3 API reference)、xDS REST 与 gRPC 协议细节(xDS REST and gRPC protocol)、以及知名客户端特性(Well Known Client Features)。这四个子文档共同构成了理解 Envoy 动态配置(xDS)从"版本选型 → 消息定义 → 线上协议交互 → 客户端能力声明"全链条的权威资料。下文按这一脉络展开,并在关键节点引用仓库内源码与示例配置进行印证。

支持的 API 版本

Envoy 的 API 遵循仓库内的 API 版本化方案,当前只支持一个主版本:

  • v3 xDS API(活跃状态):根据 API_VERSIONING.md 的 API 生命周期章节,v3 xDS 是最终主版本,将永久获得支持。官方明确表示:由于 Envoy 与整个 xDS 生态(gRPC 等)使用面太广,主版本号升级已不再现实,因此 v3 即为终点;未来只会有字段级弃用(deprecation)提示,但任何字段都不会被移除,Envoy 也不会移除任何已弃用字段的实现

以下版本已不再被 Envoy 支持:

  • v1 xDS API:早于 Protobuf 时代的遗留 REST-JSON API,是当前 Protobuf 与 REST/gRPC 双通道 xDS API 的前身。
  • v2 xDS API:于 2021 年第一季度(Q1 2021)移除支持。

版本化机制要点(依据 api/API_VERSIONING.md)

理解"v3"的确切含义需要把握以下细节:

  • Envoy API 是一族独立版本化的包(package),例如envoy.admin.v3alphaenvoy.service.trace.v3,采用基于 protobuf 的语义化版本方案,主版本号写在包名与目录结构中(如 v3 的 trace 包为envoy.service.trace.v3,proto 位于api/envoy/service/trace/v3),且不允许出现envoy.service.trace.v3.somethingelse这类子包。
  • 所谓"vN xDS API"实际指的是根配置资源(bootstrap、xDS 资源如 Cluster)所处的版本 N。v3 API 的 bootstrap 配置即为envoy.config.bootstrap.v3.Bootstrap,整个 Envoy API 本质上是版本化包命名空间构成的有向无环图(DAG)。
  • 在包的主版本内不允许任何破坏性变更:字段不可重编号、不可改类型、不可重命名(字段重命名会破坏 YAML/JSON 与 text proto 的加载,而 YAML/JSON 被视为一等输入);单例字段升级为 repeated、将字段包进 oneof、收紧 protoc-gen-validate 注解等通常"proto 安全"的操作,在 Envoy 中被视为破坏性变更。
  • 例外情况:字段或消息引入 14 天内且未随 Envoy 发布、vNalpha版本内、以及标记了work_in_progress注解的 proto。
  • 新功能通常只加入当前稳定主版本vNalpha可由vN机械生成,不要求开发者双处维护。

v3 API 参考全貌

v3 API 参考文档(docs/root/api-v3/api.rst)是完整 API 的目录入口,覆盖十大类消息族:

  • bootstrap / listeners / clusters / http_routes / config / admin / data / service / common_messages(含 common_messages_xds)/ types

对应到仓库中,这些 proto 实际位于api/envoy/下的config/service/type/data/admin/等目录(如envoy.config.bootstrap.v3.Bootstrapenvoy.config.listener.v3.Listenerenvoy.config.cluster.v3.Clusterenvoy.config.route.v3.RouteConfigurationenvoy.config.endpoint.v3.ClusterLoadAssignment等),读者可结合 api/envoy/config 目录逐一定位具体消息定义。

xDS 协议核心概念

Envoy 通过文件系统或一个或多个管理服务器(management server)发现各类动态资源,这些发现服务及其对应 API 被统称为xDS。资源通过"订阅(subscriptions)"来请求,方式有三种:监视指定文件系统路径、发起 gRPC 流、轮询 REST-JSON URL。后两种方式需要携带 DiscoveryRequest 消息负载;所有方式的资源投递均使用 DiscoveryResponse 消息负载。

资源类型(Resource Types)

每种 xDS 配置资源都有其类型,资源类型独立于下文所述传输方式进行版本化。v3 支持的资源类型如下:

资源类型说明
envoy.config.listener.v3.Listener监听器
envoy.config.route.v3.RouteConfiguration路由配置
envoy.config.route.v3.ScopedRouteConfiguration带作用域的路由配置
envoy.config.route.v3.VirtualHost虚拟主机
envoy.config.cluster.v3.Cluster集群
envoy.config.endpoint.v3.ClusterLoadAssignment集群端点分配(EDS 资源)
envoy.extensions.transport_sockets.tls.v3.SecretTLS 密钥
envoy.service.runtime.v3.Runtime运行时配置

资源通过type URL在请求/响应中标识,形如type.googleapis.com/<resource type>,例如 Cluster 资源的 type URL 为type.googleapis.com/envoy.config.cluster.v3.Cluster

protoc-gen-validate(PGV)注解

各 xDS 资源类型的 protobuf 消息带有 protoc-gen-validate(PGV)注解,用于描述客户端收到资源时应执行的语义约束。需要特别注意的是:

  • 客户端并非必须使用 PGV 注解做校验(例如 Envoy 会做校验,但 gRPC 不做);PGV 注解也不是客户端应执行校验的穷尽清单。
  • 控制面(control plane)和 xDS 代理通常不应直接依赖 PGV 注解,尽管有些控制面会提前用其拦截非法配置。因为 PGV 注解会随 API 演进,且收紧 PGV 注解不视为 API 破坏性变更——控制面无法假定所有客户端编译时使用了与其相同版本的 xDS proto,从而可能出现"服务器拒绝而客户端本可接受"的配置。

三种订阅方式详解

1. 文件系统订阅(Filesystem subscriptions)

最简的动态配置投递方式:把配置放在 ConfigSource 指定的已知路径,Envoy 使用inotify(macOS 上为kqueue)监视文件变更,并在更新时解析文件中的 DiscoveryResponse。支持二进制 protobuf、JSON、YAML、proto text四种格式。

文件系统订阅没有 ACK/NACK 机制,只能依靠统计计数器和日志观察;若某次配置更新被拒绝,最后一个有效配置会继续生效。

2. 流式 gRPC 订阅(Streaming gRPC subscriptions)

API 流程

典型 HTTP 路由场景下,客户端配置的核心资源类型是ListenerRouteConfigurationClusterClusterLoadAssignment,依赖关系呈树状:每个Listener可指向一个RouteConfiguration,路由可指向一个或多个Cluster,每个Cluster可指向一个ClusterLoadAssignment

  • Envoy(代理):启动时抓取全部ListenerCluster资源,再按需抓取这些资源所引用的RouteConfigurationClusterLoadAssignment。换言之,每个ListenerCluster都是 Envoy 配置树的一个根。
  • gRPC 等非代理客户端:可以只抓取自己关心的Listener,再逐级抓取路由、集群、端点资源——最初的Listener是客户端配置树的根。
传输协议变体:四个维度组合出的四种形态

xDS 流式 gRPC 传输协议有两个独立维度:

维度一:State of the World(SotW)vs. 增量(incremental)

  • SotW 是 xDS 最初的机制:客户端每次请求必须列出全部感兴趣的资源名;对 LDS/CDS,服务器每次必须返回客户端订阅的全部资源。例如已订阅 99 个资源、想新增 1 个,就必须发送含全部 100 个资源名的请求,服务器也要重新返回全部 100 个。这一机制存在可扩展性瓶颈,因此引入了增量变体。
  • 增量变体允许双方只表达相对上一次状态的差值(delta):客户端只增删订阅的资源名,服务器只发送发生变化的资源,并提供资源懒加载(on-demand)机制。

维度二:每种资源类型独立 gRPC 流 vs. 全部资源类型聚合在单一 gRPC 流上

  • 独立流是原始机制,提供最终一致性模型;
  • 聚合流(ADS)为需要显式控制更新顺序的环境而生。

由此组合出四种传输变体:

  1. State of the World(Basic xDS):SotW + 每种资源类型独立流
  2. Incremental xDS(Delta xDS):增量 + 每种资源类型独立流
  3. Aggregated Discovery Service(ADS):SotW + 全资源类型聚合流
  4. Incremental ADS:增量 + 全资源类型聚合流
各变体的 RPC 服务与方法

非聚合变体下,每种资源类型有独立的 RPC 服务,且同时提供 SotW 与增量两种方法:

资源类型发现服务SotW 方法增量方法
ListenerLDS(Listener Discovery Service)ListenerDiscoveryService.StreamListenersListenerDiscoveryService.DeltaListeners
RouteConfigurationRDS(Route Discovery Service)RouteDiscoveryService.StreamRoutesRouteDiscoveryService.DeltaRoutes
ScopedRouteConfigurationSRDS(Scoped Route Discovery Service)ScopedRouteDiscoveryService.StreamScopedRoutesScopedRouteDiscoveryService.DeltaScopedRoutes
VirtualHostVHDS(Virtual Host Discovery Service)VirtualHostDiscoveryService.DeltaVirtualHosts
ClusterCDS(Cluster Discovery Service)ClusterDiscoveryService.StreamClustersClusterDiscoveryService.DeltaClusters
ClusterLoadAssignmentEDS(Endpoint Discovery Service)EndpointDiscoveryService.StreamEndpointsEndpointDiscoveryService.DeltaEndpoints
SecretSDS(Secret Discovery Service)SecretDiscoveryService.StreamSecretsSecretDiscoveryService.DeltaSecrets
RuntimeRTDS(Runtime Discovery Service)RuntimeDiscoveryService.StreamRuntimeRuntimeDiscoveryService.DeltaRuntime

聚合变体(ADS / Incremental ADS)将所有资源类型复用在同一 gRPC 流上,每种资源类型在聚合流内视为一个独立逻辑子流,服务与方法为:

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

所有 SotW 方法的请求/响应类型为 DiscoveryRequest / DiscoveryResponse;所有增量方法使用 DeltaDiscoveryRequest / DeltaDiscoveryResponse。

如何配置选用哪种变体

xDS API 中,ConfigSource 消息说明如何获取特定类型的资源:

  • 若 ConfigSource 内含 gRPC ApiConfigSource,则指向管理服务器的上游集群,每种 xDS 资源类型会建立一条独立的双向 gRPC 流(可指向不同的管理服务器)。
  • 若 ConfigSource 内含 AggregatedConfigSource,则指示客户端使用 ADS。

通常客户端需要一份本地配置告知如何获取 Listener 与 Cluster 资源:Listener 资源内部可携带 ConfigSource 说明 RouteConfiguration 如何获取,Cluster 资源内部可携带 ConfigSource 说明 ClusterLoadAssignment 如何获取。

Envoy 客户端配置:bootstrap 文件中含两个 ConfigSource(分别说明 Listener、Cluster 资源的获取方式),另含一个独立的 ApiConfigSource 说明如何连接 ADS 服务器——只要任何 ConfigSource(bootstrap 中的或管理服务器下发的 Listener/Cluster 中的)包含 AggregatedConfigSource 消息,就会走这个 ADS 配置。

一个已知限制:bootstrap 的static_resources中,任何被其他静态集群依赖的 xDS 集群必须排在最前面,否则 Envoy 初始化会变慢。例如某个集群依赖 xDS 集群做 SDS 来配置传输套接字密钥,则该 xDS 集群必须先于使用该密钥的集群声明。

gRPC 客户端:使用 xDS 的 gRPC 客户端只支持 ADS,bootstrap 中含 ADS 服务器名称,所有资源都通过它获取;Listener/Cluster 中的 ConfigSource 必须包含 AggregatedConfigSource。

xDS 传输协议(The xDS transport Protocol)

传输 API 版本:除资源类型版本外,xDS 线缆协议还有自己的传输版本,为 DiscoveryRequest / DiscoveryResponse 这类消息提供类型版本化,并且编码在 gRPC 方法名中——服务器仅凭客户端调用哪个方法即可判断其协议版本。

基本协议流程

  1. 每条 xDS 流以客户端发出的 DiscoveryRequest 开始,包含:订阅资源列表、订阅资源对应的 type URL、节点标识(node identifier)、可选的资源类型实例版本(客户端已见过的最新版本)。
  2. 服务器随后发送 DiscoveryResponse,内含自客户端上次指示的实例版本以来发生变化的已订阅资源;此后订阅资源发生变化时服务器可随时追加响应。
  3. 客户端每次收到新响应后,会再发一个请求,说明响应中的单个资源孤立地看是否有效(即 ACK/NACK)。
  4. 所有服务器响应都带nonce字段,客户端后续请求必须将response_nonce设为同一条流上最近收到的 nonce,用于让服务器把请求与特定响应关联,规避 SotW 变体中的竞态。nonce 只在单条 xDS 流上下文内有效,流重启即失效;聚合变体中 nonce 按资源类型分别跟踪。
  5. 只有流上的第一个请求保证携带节点标识,后续请求可带空 node(若携带则必须与首次一致),因此检查第一个消息的 node 即可。

在 discovery.proto 中可以看到这些字段的实际定义:DiscoveryRequestversion_info(L68)、resource_names(L79)、type_url(L97)、response_nonce(L107)、error_detail(L113);DiscoveryResponseversion_info(L121)、type_url(L148)、nonce(L158)。

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

每种 xDS 资源类型都有一个版本字符串,只要该类型的任一资源发生变化,版本就更新。服务器在 DiscoveryResponse 的version_info中给出该类型当前版本;客户端随后在请求的version_info中回传自己看到的最近有效版本,服务器据此判断何时发送了客户端认为无效的版本。(增量变体中,该信息由服务器在 DeltaDiscoveryResponse 的system_version_info字段发送,但客户端并不用其表达资源有效性——增量 API 有独立机制。)

资源类型实例版本按资源类型独立(聚合变体下同一条流上的每种类型各有版本),也按 xDS 服务器独立(以唯一 ConfigSource 标识)。版本属于资源本身而非某条流:流断开重连后,新流上的首个请求应携带旧流上见过的最新版本;服务器可据此不重发客户端已见过的资源(仅在能确定客户端订阅集合不变的场景下安全,例如 LDS/CDS 的通配订阅)。

一个 EDS 请求示例(YAML 形式,即 DiscoveryRequest 的字段结构):

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

处理完该响应后,Envoy 会在流上发送新请求,携带最后一次成功应用的版本与服务器给出的 nonce——版本为 Envoy 和管理服务器提供了"当前已应用配置"的共同认知,也即 ACK/NACK 机制。

ACK:如果更新中所有资源均有效,则version_infoX。注意 ACK 只表示客户端认为响应中每个资源孤立地看是有效的,不代表配置已成功应用——客户端发出 ACK 后仍可能应用失败。

NACK:如果发现更新X中有资源无效,客户端会回复填充了error_detail且携带上一个版本的请求(上例中为空初始版本)。NACK 并不表示所有资源都被拒绝——error_detail的 message 字段含具体错误信息。时序图中消息简写约定为: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字段;较老的服务器会同时比对版本与 nonce(版本与 nonce 对应服务器响应不一致即视为拒绝),但该方法对 LDS/CDS 之外的 API 不适用(客户端可能动态改变订阅集合),除非服务器保证每次任一客户端新订阅资源时都递增资源类型实例版本。

ACK/NACK 语义总结

  • xDS 客户端应对管理服务器发来的每个 DiscoveryResponse 做出 ACK 或 NACK;response_nonce告诉服务器该 ACK/NACK 对应哪个响应。
  • ACK 表示单个资源有效且客户端意图应用它们,但不代表应用成功;包含来自 DiscoveryResponse 的version_info
  • NACK 表示响应中至少一个资源被认为无效;以error_detail字段存在为标志;version_info表示客户端正在使用的最新版本(在客户端从既有版本新订阅了一个无效资源的场景下,该版本可能并不比新版本更旧)。
何时发送更新

管理服务器只在 DiscoveryResponse 中的资源发生变化时才应发送更新。Envoy 对每个 DiscoveryResponse 都会在接收(接受或拒绝)后立即回 ACK/NACK 请求;若服务器不等待变化而重复下发相同资源集,会造成双方无谓工作,甚至带来严重性能影响。

流内同资源类型的新 DiscoveryRequest 会取代先前所有同类型请求——服务器只需响应每条流上每个资源类型的最新请求,且 Envoy 并不期待每个请求都有对应响应。

客户端如何指定需要返回的资源(资源提示)

SotW 变体通过 DiscoveryRequest 的resource_names字段给出感兴趣的资源名集合;增量变体通过 DeltaDiscoveryRequest 的resource_names_subscribe/resource_names_unsubscribe字段。常规情况下请求必须指明资源名集合,服务器必须提供存在且被请求的资源;客户端会静默忽略未被显式请求的多余资源。当请求改变资源集合时,服务器必须重发新请求的资源(即使此前未请求时就发过且未变化)。资源名列表为空表示客户端对该类型不再感兴趣。

通配订阅(wildcard):Listener 与 Cluster 资源类型支持订阅特殊名称*,此时服务器应依据站点业务逻辑(通常基于客户端的 node 标识)决定客户端的完整资源集合。历史遗留语义:若客户端对某资源类型从未显式订阅过任何资源名(SotW 中该类型所有请求的resource_names均为空;增量中从未用非空resource_names_subscribe发过请求),服务器应将其视同订阅*;但一旦客户端显式订阅过某个名字(*或其它),该遗留语义即失效,此后清空订阅列表被解释为退订。

SotW 下举例:

  • resource_names未设置 → 服务器解释为订阅*
  • resource_names*A→ 继续订阅*并新增订阅A
  • resource_namesA→ 退订*,继续订阅A
  • resource_names未设置(第二次) → 退订A(已全部退订)。该请求与第一个相同,但因该流上该类型此前设置过resource_names,不再被解释为通配订阅。

增量下对应:resource_names_subscribe未设置 → 订阅*;置A→ 继续*并新增Aresource_names_unsubscribe*→ 退订*继续Aresource_names_unsubscribeA→ 退订A(集合再次为空,但同样不再解释为通配订阅)。

客户端行为:Envoy 对 Listener 与 Cluster 资源始终使用通配订阅;而 gRPC 等其它 xDS 客户端可能显式订阅具体资源名(例如它只有单一监听器且通过带外配置已知其名字)。

资源如何分组进响应
  • 增量变体中,服务器每个资源单独一个响应:已发过 100 个资源、只有 1 个变化时,只需发送那 1 个,客户端不得删除未变化的资源。
  • SotW 变体中,除 Listener/Cluster 外的资源类型与增量一致;但Listener 与 Cluster 必须是完整的"世界状态"——即使只有 1 个资源变化也必须重发全部 100 个。
  • 所有变体都以"整条命名资源"为单位操作,不存在对命名资源内 repeated 字段做增量更新的机制,最典型的是目前无法对 EDS 响应中的单个端点做增量更新。
重复资源名

服务器在单个响应中包含同一资源名两次是错误行为,客户端应 NACK 这类响应。

删除资源
  • 增量变体:服务器通过 DeltaDiscoveryResponse 的removed_resources字段(对应 proto 中 L311,另有removed_resource_names于 L316)指示客户端删除资源。
  • SotW 变体:Listener/Cluster 类型中,新响应里缺失的既有资源即表示已被删除,客户端必须删除;空响应表示删除该类型全部资源。其它资源类型没有显式删除机制——删除通过父资源不再引用子资源来隐式表达(例如 LDS 更新移除某 Listener 后,若没有其它 Listener 再指向 RouteConfiguration A,客户端可删除 A)。对这些类型而言,空 DiscoveryResponse 从客户端视角是 no-op。
判断请求的资源不存在

SotW 变体没有任何显式机制判断请求的资源不存在:LDS/CDS 响应必须包含全部请求资源,但由于更新是最终一致的,客户端无法仅凭资源缺席就断定其不存在(响应可能是基于先前的旧请求生成的)。其它资源类型由于每个资源独立成包,下个响应可能是无关的更新,同样无从判断。

因此客户端应在发送新资源请求后使用超时(推荐 15 秒)判定资源不存在;Envoy 在 RouteConfiguration 与 ClusterLoadAssignment 的资源预热 阶段就是这么做的。同时,即使请求时资源不存在,它也可能在任意时刻被创建——管理服务器必须记住客户端的请求集合,并在资源诞生后主动推送更新。

退订资源
  • 增量变体:通过resource_names_unsubscribe退订。
  • SotW 变体:每次请求必须携带完整订阅列表,退订某资源即发送不包含它的新请求(例如从订阅AB退订B,需发送仅含A的请求)。
  • 使用通配订阅的 Listener/Cluster:订阅集合由服务器决定,客户端无法单独退订其中某个资源,只能整体退订通配符。
单条流上请求多个资源

对 EDS/RDS,Envoy 既可以为每个资源生成独立流(例如每个 ConfigSource 都有独立的管理服务器上游集群),也可以在目标为同一管理服务器时把多个资源请求合并到一条流。两种方式对服务器都合法——服务器应能处理每个请求中一个或多个resource_names。下图分别为同一流上请求两个 EDS 资源{foo, bar}、以及两条独立流各请求一个资源。

资源更新与 nonce 竞态

Envoy 除了在 ACK/NACK 每个 DiscoveryResponse 时更新resource_names外,还可能在某个version_info下额外发出 DiscoveryRequest 以更新资源提示。例如 Envoy 处于 EDS 版本X且只知道集群foo,随后收到 CDS 更新得知bar,它可能会发一个X版本、resource_names{foo,bar}的额外请求。

这里存在竞态:若 Envoy 在X发出资源提示更新、管理服务器在处理前已回复新版本Y,则该提示更新可能因携带X版本而被误判为对Y的拒绝。为此服务器用 nonce 标明每个 DiscoveryResponse 对应的请求。服务器不应对携带过期 nonce 的 DiscoveryRequest 发送响应(nonce 在服务器向 Envoy 呈现更新 nonce 后即过期);服务器无需在确定新版本就绪前发送更新,先前版本的请求也随之过期,同一版本上可处理多个 DiscoveryRequest。

资源预热(Resource warming)

Cluster 与 Listener 在能提供服务前要经历预热(warming),该过程既发生在 Envoy 初始化期间,也发生在 Cluster/Listener 被更新时:

  • Cluster 的预热只有在管理服务器提供了 ClusterLoadAssignment 响应后才算完成;即使端点没有变化,也要求提供新的 ClusterLoadAssignment 响应(预热超时后 Envoy 会使用缓存的 ClusterLoadAssignment)。
  • Listener 的预热在其引用了 RDS 配置时,只有拿到 RouteConfiguration 后才完成;但若管理服务器不发送 RouteConfiguration 响应,Listener 仍可完成预热——Envoy 会使用之前发送过的 RouteConfiguration(管理服务器只需在路由发生变化或从未发送过时下发)。

管理服务器被期望在预热期间提供 EDS/RDS 更新。若预热阶段拿不到 EDS/RDS 响应,Envoy 在初始化阶段将无法完成初始化,通过 CDS/LDS 发送的更新也不会生效,直到 EDS/RDS 响应到达。

最终一致性考量

xDS API 是最终一致的,更新期间可能出现短暂流量中断。例如:仅通过 CDS/EDS 知道集群X,一条 RouteConfiguration 引用X并在 CDS/EDS 提供Y之前被调整为引用Y,则流量会被黑洞,直到 Envoy 得知Y

  • 若应用能容忍短暂流量中断(客户端重试或其它 Envoy sidecar 可掩盖),可直接推送更新。
  • 若不能容忍,应先提供同时包含XY的 CDS/EDS 更新,再做 RDS 更新把路由从X指向Y,最后做移除X的 CDS/EDS 更新。

一般原则是遵循make before break(先建后断)的更新顺序:

  1. CDS 更新(若有)必须最先推送;
  2. EDS 更新必须晚于对应集群的 CDS 更新到达;
  3. LDS 更新必须晚于对应 CDS/EDS 更新;
  4. 与新增监听器相关的 RDS 更新必须晚于 CDS/EDS/LDS 更新;
  5. 与新增 RouteConfiguration 相关的 VHDS 更新(若有)必须晚于 RDS 更新;
  6. 之后才可移除陈旧的 CDS 集群及其 EDS 端点(不再被引用的)。

注意:LDS 更新后监听器在接收流量前会先预热(若配置了 RDS 会先拉取路由);Cluster 在增删改时会预热;但路由不预热——管理面必须在推送路由更新前确保其引用的集群已就位。

TTL(资源过期)

默认情况下,管理服务器不可达时 Envoy 会保留最后已知配置直到连接恢复;但某些场景(如故障注入服务)不希望如此。TTL 设置允许 Envoy 在失去与管理服务器联系一段时间后移除一组资源(例如管理服务器不可达时终止故障注入测试)。

支持xds.config.supports-resource-ttl客户端特性的客户端,可在每个 Resource(proto 中Resource消息的ttl字段位于 L434)上指定 TTL 字段;每个资源有各自的过期时间。更新 TTL 的方式是服务器用新 TTL 重发该资源;移除 TTL 则是重发资源但 TTL 字段不设置。

心跳(heartbeat):为支持轻量级 TTL 刷新,服务器可发送一个resource未设置、版本与最近发送版本一致的 Resource 来仅更新 TTL——这类资源不被视为资源更新。

SotW 下的 TTL:需要把相关资源包装进 Resource 消息(从而复用 Delta xDS 的 TTL 字段而不改变 SotW API);心跳同样支持。该能力由xds.config.resource-in-sotw客户端特性门控。

ADS(Aggregated Discovery Service)

当管理服务器分布式部署时,要满足上述防流量中断的顺序保证很困难。ADS 允许单个管理服务器、通过单条 gRPC 流投递所有 API 更新,从而精心编排更新顺序。ADS 中一条流内通过 type URL 多路复用多条独立的 DiscoveryRequest/DiscoveryResponse 序列;对任意给定 type URL,上述请求/响应顺序语义仍然适用。每个 Envoy 实例只有一条 ADS 流

官方最小 ADS 配置(bootstrap.yaml 片段,来源 docs/root/_include/ads.yaml):

node: # set <cluster identifier> cluster: envoy_cluster # set <node identifier> id: envoy_node dynamic_resources: ads_config: api_type: GRPC grpc_services: - envoy_grpc: cluster_name: ads_cluster cds_config: ads: {} lds_config: 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: {}

要点:dynamic_resources.ads_config指定 ADS 服务器(这里指向静态集群ads_cluster),cds_config/lds_config均声明ads: {}ads_cluster本身作为静态集群预先定义,并建议配置 HTTP/2 或 TCP keepalive 以便及时发现连接问题并让 Envoy 重连。

增量 xDS(Incremental xDS / Delta xDS)

增量 xDS 是独立的 xDS 端点,价值有二:

  1. 线上以资源/资源名差值通信(Delta xDS):支持 xDS 资源的可扩展性目标——100k 个集群中只修改 1 个时,服务器只需投递被修改的那 1 个集群,而非全部。
  2. 允许 Envoy 按需/懒加载资源:例如只在请求到达时才请求相应集群。

增量 xDS 会话总在 gRPC 双向流上下文中进行(服务器可跟踪连接客户端的状态),目前没有 REST 版本。Delta 线缆协议中 nonce 字段是必需的,用于将 DeltaDiscoveryResponse 与 DeltaDiscoveryRequest 的 ACK/NACK 配对;响应级system_version_info仅用于调试。

DeltaDiscoveryRequest 可发送的场景

  • xDS 双向流中的首条消息;
  • 对先前 DeltaDiscoveryResponse 的 ACK/NACK(response_nonce设为响应中的 nonce;以error_detail是否存在区分 ACK/NACK);
  • 客户端自发请求(动态增删跟踪的资源名集合,此时必须省略response_nonce)。

即使请求上设置了response_nonce,服务器必须尊重订阅状态的变化(nonce 可能过期);nonce 仅用于关联 ACK/NACK,不应用来拒绝过期请求。此外,即使服务器认为客户端已订阅且持有最新版本,客户端resource_names_subscribe中的名字服务器仍必须提供资源(客户端可能因实现细节"忘记"了资源)。

重连:重连时增量客户端可把已知资源版本填入initial_resource_versions(对应 proto 中 L277)告知服务器,避免重新传输;因为旧流状态不保留,重连客户端必须提供全部感兴趣的资源名。通配订阅的请求必须在resource_names_subscribe中显式给出*(或按遗留行为,subscribe/unsubscribe 均为空)。

资源名与别名:资源由资源名或别名标识;DeltaDiscoveryResponse 的资源中 alias 字段标识别名,name 字段返回资源名。订阅时resource_names_subscribe可填别名或名字,服务器需同时检查名字与别名判断是否已订阅。

退订:通过resource_names_unsubscribe(可填别名或名字)。字段中可能包含服务器认为客户端本就没订阅的多余名字——服务器应干净地忽略这些"幻影退订"。一般情况下,仅退订不改变其它内容的请求无需响应;例外:当客户端同时有通配订阅(*)和某个具体资源名订阅时,由于该具体名字可能同时被通配覆盖,客户端无法自行判断是否继续缓存——服务器必须响应,将该资源放入removed_resources(不在通配内)或resources(在通配内)。

判断资源不存在:增量变体中,客户端订阅的资源不存在时,服务器会在removed_resources字段中发送该资源名——客户端可据此快速判断,而无需像 SotW 那样等超时;不过仍建议客户端保留超时保护,以防管理服务器迟迟不响应。

3. REST-JSON 轮询订阅

xDS 单例 API 也支持通过 REST 端点的同步(长)轮询。消息时序与上述类似,但不维持到管理服务器的持久流:任何时刻只允许一个未完成的请求,因此 REST-JSON 中响应 nonce 是可选的。DiscoveryRequest/DiscoveryResponse 使用proto3 的 JSON 规范转换(JSON canonical transform)编码。REST-JSON 轮询不支持 ADS。当轮询周期设得很小(意图做长轮询)时,还必须避免在底层资源未发生变化时发送 DiscoveryResponse。

知名客户端特性(Well Known Client Features)

客户端通过 node 的client_features字段(对应envoy.config.core.v3.Node.client_features)声明支持的特性列表,使用反向 DNS 命名(如com.acme.feature)。目前定义的特性如下:

  • envoy.config.require-any-fields-contain-struct:要求google.protobuf.Any类型的配置项只包含xds.type.v3.TypedStruct(或历史原因下的udpa.type.v1.TypedStruct)消息。
  • envoy.lb.does_not_support_overprovisioning:客户端不支持按 ClusterLoadAssignment.Policy.overprovisioning_factor 字段配置的优先级故障转移与 locality 加权的超量供应(overprovisioning);如需优雅故障转移功能,须由管理服务器提供。
  • envoy.lrs.supports_send_all_clusters:客户端支持 LRS(Load Reporting Service)响应中的LoadStatsResponse.send_all_clusters字段。
  • xds.config.supports-resource-ttl:客户端支持按资源或按 SotW 的 TTL。
  • xds.config.resource-in-sotw:客户端能够在 SotW DiscoveryResponse 中解包 Resource 包装器(即支持把资源包装进 Resource 消息以复用 TTL 字段)。

之所以需要这些显式声明,是因为存在无法仅靠 protobuf 语义表达的客户端能力差异(依据 api/API_VERSIONING.md):例如路由匹配器的合取条件不能被静默忽略(可能导致路由策略绕过),或客户端期望服务器以特定格式/编码(如Struct-in-Any的 JSON 编码)返回不透明扩展配置。这些场景下,client_features字段就是控制面判断客户端能力边界的权威依据。

关键消息定义速查(源码佐证)

上述协议消息在仓库 api/envoy/service/discovery/v3/discovery.proto 中有完整定义,核心字段位置如下:

消息关键字段(行号)
DiscoveryRequest(L58)version_info(L68)、resource_names(L79)、type_url(L97)、response_nonce(L107)、error_detail(L113)
DiscoveryResponse(L117)version_info(L121)、type_url(L148)、nonce(L158)
DeltaDiscoveryRequest(L207)type_url(L217)、initial_resource_versions(L277)、response_nonce(L283)、error_detail(L288)
DeltaDiscoveryResponse(L292)system_version_info(L297)、type_url(L307)、removed_resources(L311)、removed_resource_names(L316)、nonce(L320)
Resource(L386)ttl(L434)

同时,ConfigSource / ApiConfigSource / AggregatedConfigSource 是决定"走独立流还是 ADS"的配置载体,Node 的client_features字段承载能力声明,二者与本文协议描述一一对应,是深入阅读源码时的最佳起点。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询