xDS REST and gRPC protocol
原文地址:xDS REST and gRPC protocol.
envoy可通过文件系统、一个或多个管理服务器来发现各种动态资源.这些服务发现和他们相对应的API统称为xDS.通过定阅方式获取资源,如监控指定的文件路径、gRPC流或轮询REST-JSON URL.后两种使用DiscoveryRequest来发送请求消息.所有的资源包含在DiscoveryResponse响应消息中.下面,我们将讨论每种订阅类型.
文件订阅
动态配置最简单的方法是将配置写入一个文件,文件路径通过ConfigSource配置.Envoy会使用inotify
(Mac OS X上用kqueue
)去监控文件的变化,文件更新时用DiscoveryResponse
进行解析.DiscoveryResponse
支持的格式包括: 二进制protobuf,JSON,YAML和协议文本.
文件订阅支持统计数据和日志,不支持ACK/NACK的更新方式.如果配置更新被拒绝,xDS API将使用最近一次的有效配置.
gRPC流订阅
单资源类型发现
gRPC ApiConfigSource对于xDS API都可单独配置,指向对应的上游管理服务器的集群地址.每种xDS资源类型都会各自启动一个双向的gRPC流,来对应可能会向不同的管理服务器发起.API的交付方式是最终一致性的.明确控制的顺序可看下面的ADS.
URL类型
每个xDS API都与特定的资源类型相关.xDS API和资源类型之间是1:1的.对应关系如下:
- LDS: envoy.api.v2.Listener
- RDS: envoy.api.v2.RouteConfiguration
- CDS: envoy.api.v2.Cluster
- EDS: envoy.api.v2.ClusterLoadAssignment
- SDS: envoy.api.v2.Auth.Secret
URL类型的概念如下所示,其采用type.googleapis.com/<resource type>
形式,例如: CDS对应于type.googleapis.com/envoy.api.v2.Cluster
.在 Envoy 的请求和管理服务器的响应中,都包括了资源类型 URL.
ACK/NACK和版本控制
每个Enovy以流DiscoveryRequest
开始,包括指定订阅资源列表,订阅资源对应的URL类型,结点标志和空version_info
.EDS的请求例子如下:
version_info:
node: { id: envoy }
resource_names:
- foo
- bar
type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
response_nonce:
管理服务器会立即或者等待请求可用时以DiscoveryResponse
作为应答.应答例子如下:
version_info: X
resources:
- foo ClusterLoadAssignment proto encoding
- bar ClusterLoadAssignment proto encoding
type_url: type.googleapis.com/envoy.api.v2.ClusterLoadAssignment
nonce: A
处理完DiscoveryResponse
应答之后,将通过流发送一个新请求,请求包括应用成功的最后一个版本号和管理服务器提供的nonce.如果这次更新成功应用,version_info
将被设置为X,时序图如下:
在此序列图及后续中,将统一使用以下缩写格式:
DiscoveryRequest
: (V=version_info
,R=version_info
,N=response_nonce
,T=type_url
)DiscoveryResponse
: (v=version_info
,R=resources
,N=nonce
,T=type_url
)
版本为Envoy和管理服务器提供了共享当前应用配置的概念以及通过ACK/NACK来进行配置更新.如果Envoy拒绝配置更新X,它将返回error_detail和上一个版本号,在当前情况下为空的初始版本号.error_detail
包含了更多的错误详细信息:
后续,API可能会在版本Y上更新成功:
每个流都有自己的版本,跨资源类型没有共享版本.当不使用ADS时,甚至每个资源类型都可能有不同的版本,因为Envoy API允许不同的EDS/RDS资源配置对应不同的ConfigSource
.
何时发送更新
管理服务器仅在DiscoveryResponse
更改资源时才向Envoy客户端发送更新.Envoy会根据接受和拒绝的情况,会立即回复包含ACK/NACK的请求.如果管理服务器每次发送相同的资源集而不是等到有改变时再发送,这会导致Envoy和管理服务器的效率大打折扣.
对于同个流,新的DiscoveryRequest
将取代之前具有相同的资源类型请求.这意味着管理服务器只响应最新DiscoveryRequest
的请求,在相同的资源请求下.
资源提示
DiscoveryRequest
中resource_names
作为资源提示出现.一些资源类型,例如:cluster
和listener
将使用一个空的resource_names
列表.因为Envoy需要获取管理服务器对应于节点标识的所有Cluster
s(CDS)和Listener
s(LDS).对于其它的资源类型,例如:RouteConfiguration
s(RDS)和ClusterLoadAssignment
s(EDS),遵循早期的CDS/LDS更新,Envoy能够明确地列举这些资源.
LDS和CDS资源信息始终为空,并且期望管理服务器将在每个响应中提供LDS / CDS资源的完整状态.缺席的Listener
或Cluster
将被删除.resource_names
只是一个提示.
对于EDS/RDS,管理服务器不需要提供每个资源的请求,也可能提供额外的未请求资源.Envoy会忽略多余的资源信息.当一个RDS或EDS的更新缺少请求资源时,Envoy将会保留对应资源最后的有效值.管理服务器可能能够从node标识中推断出所有所需的EDS/ RDS资源DiscoveryRequest
,在这种情况下,提示信息可能会被丢弃.从特定角度看,一个空的EDS/RDS DiscoveryResponse
响应说明Envoy是一个空的资源.
当Listener
或Cluster
被删除,Envoy上相应EDS和RDS资源也会被删除.为了让Envoy知道或跟踪EDS资源,必须存在应用过的Cluster
定义(例如:通过CDS获得).RDS和Listeners
之间也存在相似的关系(通过LDS获得).
对于EDS/RDS,Envoy可以为每个给定类型的资源生成不同的流(如每个ConfigSource
都有自己的上游管理服务集群)或当指定资源类型的请求发送到同一个管理服务器的时候,允许将多个资源请求组合在一起发送.虽然可以单个实现,但管理服务器应该能够为resource_names
每个请求中的给定资源类型处理一个或多个.下面的两个时序图都可用于获取两个EDS资源{foo, bar}
:
资源更新
如上所述,Envoy可能会更新resource_names
列表在每个DiscoveryRequest
,其中DiscoveryResponse
是用来ACK/NACK管理服务器的特定的.此外,Envoy 后续可能会发送额外的DiscoveryRequest
,用于在特定version_info
上使用新的资源提示来更新管理服务器.举个例子: 如果Envoy在EDS版本X时仅知道集群foo
,但随后收到一个CDS更新时额外获取了集群bar
,它可能会为版本X发出额外的DiscoveryRequest
请求,并将{foo,bar}
作为请求的resource_names
.
这里可能会出现竞争情况;如果Envoy在版本X上发布了资源提示更新请求,但在管理服务器处理该请求之前发送了新的版本号为Y的响应,对于version_info
为X的版本,资源提示更新可能会被解释为拒绝Y.为了避免这种情况,管理服务器提供nonce
,Envoy可以用来保证DiscoveryResponse
对应每个DiscoveryRequest
.
管理服务器不应发送DiscoveryResponse
响应给任何过期DiscoveryRequest
请求.Envoy在DiscoveryResponse
响应中包含了新的nonce
,而旧的nonce
将过期做废.在新的版本就绪前,管理服务器不需要发送更新.同版本的早期请求也会过期.在新版本就绪时,管理服务器可能会处理同版本的多个DiscoveryRequests
请求.
上述资源更新时序图表明Envoy 并不能期待其发出的每个DiscoveryRequest
请求都能得到DiscoveryResponse
响应.
最终一致性考虑
由于Envoy xDS API采用最终一致性,在更新期会导致流量丢失.举个例子: 如果通过CDS/EDS仅获取到了集群X,而且RouteConfiguration
引用了集群X,在CDS/EDS更新集群Y配置之前,如果将集群调整为Y,那么流量将被丢弃,直到集群Y被Envoy实例获取.
对某些应用程序,暂时的流量丢失是可接受的,客户端重试或其它的Envoy sidecar会掩盖这这些丢失.对于对流量丢失不能容忍的场景,可以通过以下方式避免流量丢失.CDS/EDS更新同时携带X和Y,然后发送RDS更新从X切换到Y,最后发送丢弃X的CDS/EDS更新.
通常情况下,为了避免流量丢失,更新的顺序应该遵循make before break
模型,其中
- 必须始终先推送CDS更新(如果有).
- EDS更新(如果有)必须在相应群集的CDS更新后到达.
- LDS更新必须在相应的CDS/EDS更新后到达.
- 与新添加的监听器相关的RDS更新必须最终到达.
- 删除过期的CDS群集和相关的EDS端点(不再被引用的端点).
如果没有新的集群/路由/监听器或者允许更新时临时流量丢失的情况下,可以单独推送xDS更新.请注意,在LDS更新的情况下,监听器须在接收流量之前被预热,例如: 如果配置了依赖的路由,则需先从RDS获取规则.添加/删除/更新集群信息时,集群也要进行预热.另一方面,路由可以不用预热,例如: 管理平面确保在更新路由时,集群中的路由已经就绪.
聚合服务发现(ADS)
当管理服务器进行分发时,通过上述保证交互顺序的方式来避免流量丢失是一项很有挑战的工作.ADS允许单一管理服务器,通过gRPC流的方式来分发所有的API更新.配合仔细规划的更新顺序,ADS可规避更新过程中流量丢失.使用ADS,在单个流上可通过URL类型来进行复用多个独立的DiscoveryRequest
/DiscoveryResponse
序列.对于任何给定类型的URL,以上的DiscoveryRequest
和DiscoveryResponse
消息序列都适合.一个更新的时序图如下:
对于每个Envoy实例,ADS流都是可用的.
最小化ADS配置的bootstrap.yaml
片段示例如下:
node:
id: <node identifier>
dynamic_resources:
cds_config: {ads: {}}
lds_config: {ads: {}}
ads_config:
api_type: GRPC
grpc_services:
envoy_grpc:
cluster_name: ads_cluster
static_resources:
clusters:
- name: ads_cluster
connect_timeout: { seconds: 5 }
type: STATIC
hosts:
- socket_address:
address: <ADS management server IP address>
port_value: <ADS management server port>
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
admin:
...
增量xDS
增量xDS是可用于允许的ADS、CDS和RDS的单独xDS端点:
- xDS客户端对跟踪资源列表进行增量更新.这支持Envoy按需/惰性地请求额外资源.举个例子: 当与未知集群相对应的请求到达时,这是有可能发生的.
- xDS服务端可以增量更新客户端上的资源.这支持xDS资源可伸缩性的目标.管理服务器只需发送给更改的单个集群,而不是在修改单个集群时发送给所有100k集群.
xDS增量会话始终位于gRPC双向流的上下文中.这允许xDS服务器能够跟踪到连接的xDS客户端的状态.xDS REST版本不支持增量.
在增量xDS中,nonce字段是必须的,用于匹配IncrementalDiscoveryResponse与IncrementalDiscoveryRequest关联的ACK或NACK.可选地,响应级别的消息system_version_info仅用来调试.
IncrementalDiscoveryRequest
在以下3种情况发送:
- 在一个双向gRPC流初始化消息.
- 作为对先前的
IncrementalDiscoveryResponse
的ACK或NACK响应.这种情况下,response_nonce
将在响应中被设为nonce值.ACK或NACK存不存在由error_detail确定. - 由客户自发.可以动态地添加或删除被跟踪
resource_names
集.在这种情况下,response_nonce
必须被忽略.
在第一个示例中,客户端连接并接收它的第一个更新并ACK,第二次更新失败,客户发送NACK拒绝更新,然后xDS客户端会自发的请求"wc"资源.
在重新连接时,增量的xDS客户端可能会告诉服务器其已有资源从而避免通过网络重新发送它们.
轮询REST-JSOM URL订阅
通过REST端点进行同步(长)轮询,也可用于xDS单例API.和上面的消息序列类似,除了在管理服务器没有保持一个永久的流.在任何时间点只有一个未完成的请求,因此响应现时在REST-JSON中是可选的.proto3的Json编码规范用于编码DiscoveryRequest
和DiscoveryResponse
消息.ADS不适用REST-JSON轮询.
当轮询周期设置为较小的值时,为了进行长轮询,则还需要避免发送DiscoveryResponse
,除非对底层资源进行了更改.