xDS REST 和 gRPC 協定

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。

Protoc-Gen-Validate 註釋

個別 xDS 資源類型的 protobuf 訊息都使用了 protoc-gen-validate (PGV) 的註釋,這些註釋指示了在用戶端接收資源時用於驗證資源內容的語義約束。

並非要求用戶端必須使用這些 PGV 註釋來驗證資源(例如,Envoy 會執行此驗證,但 gRPC 不會)。此外,PGV 註釋並非旨在列出用戶端要執行的所有驗證檢查;用戶端可能會因與 PGV 註釋無關的原因而拒絕資源。

一般來說,控制平面或 xDS 代理並不打算直接使用 PGV 註釋。在某些情況下,控制平面可能會希望使用 PGV 註釋進行驗證,以便在配置管道中更早地發現問題(例如,在資源新增到控制平面時,在將資源發送到任何用戶端之前,拒絕無效的輸入)。但是,PGV 註釋會隨著 xDS API 的發展而發展,並且降低 PGV 註釋的嚴格度並不被視為 API 中的重大變更。因此,在一般情況下,控制平面無法假設其所有用戶端都使用與控制平面相同的 xDS proto 檔案版本進行編譯,這表示它無法知道用戶端是否真的會使用與伺服器相同的驗證。這可能會導致伺服器拒絕用戶端會接受的資源的問題。

檔案系統訂閱

傳遞動態配置最簡單的方法是將其放置在 ConfigSource 中指定的已知路徑中。Envoy 將使用 inotify(在 macOS 上為 kqueue)來監控檔案的變更,並在更新時剖析檔案中的 DiscoveryResponse proto。支援二進位 protobuf、JSON、YAML 和 proto 文字作為 DiscoveryResponse 的格式。

除了統計計數器和記錄檔之外,沒有任何可用的機制可供檔案系統訂閱 ACK/NACK 更新。如果發生配置更新拒絕,則 xDS API 的最後一個有效配置將繼續套用。

串流 gRPC 訂閱

API 流程

對於典型的 HTTP 路由案例,用戶端配置的核心資源類型為 ListenerRouteConfigurationClusterClusterLoadAssignment。每個 Listener 資源都可能會指向一個 RouteConfiguration 資源,該資源可能會指向一個或多個 Cluster 資源,而每個 Cluster 資源都可能會指向一個 ClusterLoadAssignment 資源。

Envoy 在啟動時會擷取所有 ListenerCluster 資源。然後,它會擷取 ListenerCluster 資源所需的任何 RouteConfigurationClusterLoadAssignment 資源。實際上,每個 ListenerCluster 資源都是 Envoy 配置樹的一部分的根。

像 gRPC 這樣的非代理用戶端可能會從僅擷取它感興趣的特定 Listener 資源開始。然後,它會擷取這些 Listener 資源所需的 RouteConfiguration 資源,然後擷取這些 RouteConfiguration 資源所需的 Cluster 資源,最後擷取 Cluster 資源所需的 ClusterLoadAssignment 資源。實際上,原始的 Listener 資源是用戶端配置樹的根。

xDS 傳輸協定的變體

四種變體

通過串流 gRPC 使用的 xDS 傳輸協定有四種變體,涵蓋了兩個維度的所有組合。

第一個維度是世界狀態 (SotW) 與增量。SotW 方法是 xDS 最初使用的機制,其中用戶端必須在每個請求中指定它感興趣的所有資源名稱,對於 LDS 和 CDS 資源,伺服器必須在每個請求中傳回用戶端已訂閱的所有資源。這表示如果用戶端已經訂閱了 99 個資源,並且想要新增一個額外的資源,則必須發送一個包含所有 100 個資源名稱的請求,而不僅僅是新增的資源名稱。對於 LDS 和 CDS 資源,伺服器然後必須傳回所有 100 個資源,即使已經訂閱的 99 個資源沒有變更。此機制可能會成為擴展性限制,這就是為什麼引入增量協定變體的原因。增量方法允許用戶端和伺服器都僅指示相對於其先前狀態的增量 – 即,用戶端可以說它想要新增或移除對特定資源名稱的訂閱,而無需重新發送那些沒有變更的資源,並且伺服器可以僅發送已變更的資源的更新。增量協定還提供了資源延遲載入的機制。有關增量協定的詳細資訊,請參閱下面的 增量 xDS

第二個維度是為每個資源類型使用單獨的 gRPC 串流,還是將所有資源類型聚合到單個 gRPC 串流上。前一種方法是 xDS 最初使用的機制,它提供最終一致性模型。後一種方法是為需要顯式控制排序的環境而新增的。有關詳細資訊,請參閱下面的 最終一致性考量

因此,xDS 傳輸協定的四種變體為

  1. 世界狀態 (基本 xDS):SotW,每個資源類型使用單獨的 gRPC 串流

  2. 增量 xDS:增量,每個資源類型使用單獨的 gRPC 串流

  3. 聚合探索服務 (ADS):SotW,所有資源類型使用聚合串流

  4. 增量 ADS:增量,所有資源類型使用聚合串流

每個變體的 RPC 服務和方法

對於非聚合協定變體,每個資源類型都有一個單獨的 RPC 服務。這些 RPC 服務中的每一個都可以為 SotW 和增量協定變體提供一種方法。以下是每個資源類型的 RPC 服務和方法

  • 接聽器:接聽器探索服務 (LDS)

    • SotW:ListenerDiscoveryService.StreamListeners

    • 增量:ListenerDiscoveryService.DeltaListeners

  • 路由配置:路由探索服務 (RDS)

    • SotW:RouteDiscoveryService.StreamRoutes

    • 增量:RouteDiscoveryService.DeltaRoutes

  • 範圍路由配置:範圍路由探索服務 (SRDS)

    • SotW:ScopedRouteDiscoveryService.StreamScopedRoutes

    • 增量:ScopedRouteDiscoveryService.DeltaScopedRoutes

  • 虛擬主機:虛擬主機探索服務 (VHDS)

    • SotW:不適用

    • 增量:VirtualHostDiscoveryService.DeltaVirtualHosts

  • 叢集:叢集探索服務 (CDS)

    • SotW:ClusterDiscoveryService.StreamClusters

    • 增量:ClusterDiscoveryService.DeltaClusters

  • 叢集負載分配:端點探索服務 (EDS)

    • SotW:EndpointDiscoveryService.StreamEndpoints

    • 增量:EndpointDiscoveryService.DeltaEndpoints

  • 機密資訊:機密資訊探索服務 (SDS)

    • SotW:SecretDiscoveryService.StreamSecrets

    • 增量:SecretDiscoveryService.DeltaSecrets

  • 執行階段:執行階段探索服務 (RTDS)

    • SotW:RuntimeDiscoveryService.StreamRuntime

    • 增量:RuntimeDiscoveryService.DeltaRuntime

在彙總協定變體中,所有資源類型都會在單一 gRPC 串流上進行多工處理,其中每個資源類型都會被視為彙總串流中的獨立邏輯串流。實際上,它只是將上述所有獨立 API 合併為單一串流,方法是將每個資源類型的請求和回應視為單一彙總串流上的獨立子串流。彙總協定變體的 RPC 服務和方法如下:

  • SotW:AggregatedDiscoveryService.StreamAggregatedResources

  • 增量:AggregatedDiscoveryService.DeltaAggregatedResources

對於所有 SotW 方法,請求類型為 DiscoveryRequest,而回應類型為 DiscoveryResponse

對於所有增量方法,請求類型為 DeltaDiscoveryRequest,而回應類型為 DeltaDiscoveryResponse

設定要使用的變體

在 xDS API 中,ConfigSource 訊息指示如何取得特定類型的資源。如果 ConfigSource 包含 gRPC ApiConfigSource,則它會指向管理伺服器的上游叢集;這將為每個 xDS 資源類型啟動一個獨立的雙向 gRPC 串流,並且可能指向不同的管理伺服器。如果 ConfigSource 包含 AggregatedConfigSource,則會告知用戶端使用 ADS

目前,預期用戶端會被賦予一些本機設定,以告知其如何取得 ListenerCluster 資源。Listener 資源可以包含 ConfigSource,指示如何取得 RouteConfiguration 資源,而 Cluster 資源可以包含 ConfigSource,指示如何取得 ClusterLoadAssignment 資源。

用戶端設定

在 Envoy 中,啟動檔案包含兩個 ConfigSource 訊息,一個指示如何取得 Listener 資源,另一個指示如何取得 Cluster 資源。它還包含一個獨立的 ApiConfigSource 訊息,指示如何連線到 ADS 伺服器,只要 ConfigSource 訊息(無論是在啟動檔案中還是從管理伺服器取得的 ListenerCluster 資源中)包含 AggregatedConfigSource 訊息,就會使用該伺服器。

Envoy 目前的一個限制是,任何 xDS Cluster 資源都應在啟動設定的 static_resources 欄位中,指定於任何依賴 xDS 叢集的靜態 Cluster 資源之前。若未遵循此做法,會導致 Envoy 初始化速度較慢(詳情請參閱 GitHub 問題)。例如,如果叢集依賴 xDS Cluster 來設定傳輸通訊端上的機密資訊,則 xDS Cluster 應在 static_resources 欄位中,指定於具有傳輸通訊端機密資訊的叢集之前。

在使用 xDS 的 gRPC 用戶端中,僅支援 ADS,而且啟動檔案包含 ADS 伺服器的名稱,該名稱將用於所有資源。ListenerCluster 資源中的 ConfigSource 訊息必須包含 AggregatedConfigSource 訊息。

xDS 傳輸協定

傳輸 API 版本

除了上述資源類型版本之外,xDS 線路協定還具有與其關聯的傳輸版本。這為諸如 DiscoveryRequestDiscoveryResponse 等訊息提供類型版本控制。它也會在 gRPC 方法名稱中進行編碼,因此伺服器可以根據用戶端呼叫的方法來判斷用戶端所使用的版本。

基本協定概觀

每個 xDS 串流都以來自用戶端的 DiscoveryRequest 開始,該請求指定要訂閱的資源清單、對應於訂閱資源的類型 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,如序列圖中所示

Version update after ACK

NACK

如果 Envoy 反而拒絕了組態更新 X,它將回覆並填入 error_detail 及其先前版本,在此案例中為空的初始版本。error_detail 在訊息欄位中填入了有關確切錯誤訊息的更多詳細資訊

No version update after NACK

在序列圖中,以下格式用於縮寫訊息

  • 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 成功

ACK after NACK

伺服器偵測 NACK 的首選機制是查找用戶端傳送的請求中是否存在 error_detail 欄位。一些較舊的伺服器可能會透過查看請求中的版本和 nonce 來偵測 NACK:如果請求中的版本不等於伺服器使用該 nonce 傳送的版本,則表示用戶端拒絕了最新版本。但是,除非伺服器設法在每次有任何一個用戶端訂閱新資源時都遞增資源類型實例版本,否則此方法不適用於 LDS 和 CDS 以外的 API,這些 API 的用戶端可能會動態變更它們正在訂閱的資源集。具體來說,請考慮以下範例

detecting NACK from error_detail instead of version and nonce

ACK 和 NACK 語意摘要

  • xDS 用戶端應 ACKNACK 從管理伺服器接收的每個 DiscoveryResponseresponse_nonce 欄位會告訴伺服器 ACKNACK 與其哪個回應相關聯。

  • ACK 表示組態更新成功,並包含 version_info (來自 DiscoveryResponse)。

  • NACK 表示組態失敗,並由 error_detail 欄位的存在表示。version_info 表示用戶端正在使用的最新版本,儘管在用戶端從現有版本訂閱新資源且該新資源無效的情況下,可能不是舊版本(請參閱上面的範例)。

何時傳送更新

僅當 DiscoveryResponse 中的資源已變更時,管理伺服器才應向 Envoy 用戶端傳送更新。Envoy 在接受或拒絕任何 DiscoveryResponse 後,會立即以包含 ACK/NACKDiscoveryRequest 回應。如果管理伺服器提供相同的資源集,而不是等待發生變更,這將導致用戶端和管理伺服器產生不必要的工作,這可能會對效能造成嚴重影響。

在串流中,新的 DiscoveryRequest 會取代具有相同資源類型的任何先前 DiscoveryRequest。這表示管理伺服器僅需回應該串流上針對任何給定資源類型的最新 DiscoveryRequest

用戶端如何指定要傳回哪些資源

xDS 請求允許用戶端指定一組資源名稱,以作為伺服器關於用戶端感興趣哪些資源的提示。在 SotW 協定變體中,這是透過 resource_names 指定在 DiscoveryRequest 中完成;在增量協定變體中,這是透過 resource_names_subscriberesource_names_unsubscribe 欄位在 DeltaDiscoveryRequest 中完成。

通常(例外情況請參閱下文),請求必須指定用戶端感興趣的資源名稱集。管理伺服器必須在資源存在時提供請求的資源。用戶端將會靜默忽略任何未明確請求提供的資源。當用戶端傳送變更所請求資源集的新請求時,伺服器必須重新傳送任何新請求的資源,即使它先前傳送過這些資源而沒有被要求並且資源自那時以來沒有變更。如果資源名稱清單變為空,則表示用戶端不再對任何指定類型的資源感興趣。

對於 ListenerCluster 資源類型,也存在「萬用字元」訂閱,當訂閱特殊名稱 * 時會觸發。在這種情況下,伺服器應使用特定站點的業務邏輯來決定客戶端感興趣的完整資源集,通常基於客戶端的 節點 識別。

由於歷史原因,如果客戶端針對給定的資源類型發送請求,但從未明確訂閱任何資源名稱(即,在 SotW 中,該資源類型的所有請求在流上都具有空的 resource_names 欄位,或者在增量模式下,從未在流上發送具有非空 resource_names_subscribe 欄位的該資源類型請求),伺服器應將其視為與客戶端明確訂閱 * 的情況相同。然而,一旦客戶端明確訂閱了資源名稱(無論是 * 還是任何其他名稱),則此舊有語意不再適用;此時,清除已訂閱的資源列表將被解釋為取消訂閱(請參閱 取消訂閱資源),而不是訂閱 *

例如,在 SotW 中:

  • 客戶端發送一個 resource_names 未設定的請求。伺服器將此解釋為訂閱 *

  • 客戶端發送一個 resource_names 設定為 *A 的請求。伺服器將此解釋為繼續現有對 * 的訂閱,並新增對 A 的訂閱。

  • 客戶端發送一個 resource_names 設定為 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 將始終為 ListenerCluster 資源使用萬用字元訂閱。然而,其他 xDS 客戶端(例如使用 xDS 的 gRPC 客戶端)可能會針對這些資源類型明確訂閱特定的資源名稱,例如,如果它們只有一個單一的偵聽器,並且已經從某些帶外設定中知道其名稱。

將資源分組到回應中

在增量協定變體中,伺服器會在自己的回應中傳送每個資源。這表示,如果伺服器先前傳送了 100 個資源,而只有其中一個已變更,則它可能會傳送僅包含變更資源的回應;它不需要重新傳送未變更的 99 個資源,而客戶端不得刪除未變更的資源。

在 SotW 協定變體中,除了 ListenerCluster 之外的所有資源類型都以與增量協定變體相同的方式分組到回應中。然而,ListenerCluster 資源類型的處理方式不同:伺服器必須包含完整的世界狀態,這表示必須包含客戶端所需的所有相關類型資源,即使它們自上次回應以來沒有變更也是如此。這表示,如果伺服器先前傳送了 100 個資源,而只有其中一個已變更,則它必須重新傳送所有 100 個資源,即使有 99 個資源未修改也是如此。

請注意,所有協定變體都以完整命名資源的單位運作。沒有任何機制可以提供命名資源內重複欄位的增量更新。最值得注意的是,目前沒有任何機制可以增量更新 EDS 回應中的個別端點。

重複資源名稱

伺服器在單一回應中傳送相同的資源名稱兩次是錯誤的。客戶端應 NACK 包含同一資源名稱多個執行個體的回應。

刪除資源

在增量協定變體中,伺服器會透過回應的 removed_resources 欄位向客戶端發出應刪除資源的訊號。這會告知客戶端從其本機快取中移除資源。

在 SotW 協定變體中,刪除資源的條件更為複雜。對於 ListenerCluster 資源類型,如果先前看到的資源未出現在新的回應中,則表示該資源已移除,客戶端必須將其刪除;包含零資源的回應表示要刪除該類型的所有資源。然而,對於其他資源類型,API 未提供伺服器告知客戶端資源已刪除的機制;相反地,刪除會由變更父資源以不再參照子資源來隱式指示。例如,當客戶端收到 LDS 更新而移除先前指向 RouteConfiguration A 的 Listener 時,如果沒有其他 Listener 指向 RouteConfiguration A,則客戶端可能會刪除 A。對於這些資源類型,從客戶端的角度來看,空的 DiscoveryResponse 實際上是空操作。

了解請求的資源何時不存在

SotW 協定變體不提供任何明確的機制來判斷請求的資源何時不存在。

對於 ListenerCluster 資源類型的回應,必須包含用戶端請求的所有資源。然而,用戶端可能無法僅根據回應中不存在某資源來得知該資源不存在,因為更新的傳遞最終是一致的:如果用戶端最初發送資源 A 的請求,然後發送資源 AB 的請求,然後看到一個僅包含資源 A 的回應,用戶端無法斷定資源 B 不存在,因為該回應可能是根據第一個請求發送的,在伺服器看到第二個請求之前。

對於其他資源類型,由於每個資源都可以在自己的回應中發送,因此無法從下一個回應中得知新請求的資源是否存在,因為下一個回應可能是先前已訂閱的其他資源的不相關更新。

因此,用戶端應在發送新資源請求後使用逾時(建議時長為 15 秒),如果在此期間未收到資源,則將視為請求的資源不存在。在 Envoy 中,這是在 RouteConfigurationClusterLoadAssignment 資源於資源暖機期間完成的。

請注意,即使請求的資源在用戶端請求時不存在,該資源也可能隨時被建立。管理伺服器必須記住用戶端正在請求的資源集合,如果其中一個資源稍後出現,伺服器必須向用戶端發送更新,告知用戶端新的資源。最初看到不存在資源的用戶端必須準備好該資源隨時被建立。

取消訂閱資源

在增量協定變體中,可以透過 resource_names_unsubscribe 欄位來取消訂閱資源。

在 SotW 協定變體中,每個請求都必須包含 resource_names 欄位中正在訂閱的資源名稱的完整列表,因此取消訂閱一組資源是透過發送一個新的請求來完成的,該請求包含所有仍在訂閱的資源名稱,但不包含要取消訂閱的資源名稱。例如,如果用戶端先前已訂閱資源 AB,但希望取消訂閱 B,則必須發送一個僅包含資源 A 的新請求。

請注意,對於 ListenerCluster 資源類型,如果用戶端使用「萬用字元」訂閱(詳細資訊請參閱 用戶端如何指定要傳回的資源),則正在訂閱的資源集合是由伺服器而不是用戶端決定的,因此用戶端無法單獨取消訂閱這些資源;它只能取消訂閱整個萬用字元。

在單一串流上請求多個資源

對於 EDS/RDS,Envoy 可以為給定類型的每個資源產生不同的串流(例如,如果每個 ConfigSource 都為管理伺服器使用其自己獨特的上游叢集),或者可以在它們目的地是同一個管理伺服器時,將給定資源類型的多個資源請求合併在一起。雖然這留給實作細節,但管理伺服器應能夠處理每個請求中給定資源類型的一個或多個 resource_names。以下兩個序列圖對於擷取兩個 EDS 資源 {foo, bar} 都是有效的。

Multiple EDS requests on the same stream Multiple EDS requests on distinct streams

資源更新

如上所述,Envoy 可以在每個 DiscoveryRequest 中更新它呈現給管理伺服器的 resource_names 列表,該請求確認/否定特定的 DiscoveryResponse。此外,Envoy 可能稍後在給定的 version_info 發出額外的 DiscoveryRequests,以使用新的資源提示更新管理伺服器。例如,如果 Envoy 處於 EDS 版本 X 並且只知道叢集 foo,但隨後收到 CDS 更新並了解 bar,它可能會針對 X 發出額外的 DiscoveryRequest,其中 {foo,bar} 作為 resource_names

CDS response leads to EDS resource hint update

這裡可能會出現一個競爭條件;如果在 Envoy 在 X 發出資源提示更新後,但在管理伺服器處理更新之前,它回覆一個新的版本 Y,則資源提示更新可能會被解釋為拒絕 Y,因為它呈現了 X version_info。為了避免這種情況,管理伺服器提供了一個 nonce,Envoy 使用它來指示每個 DiscoveryResponse 對應的特定 DiscoveryRequest

EDS update race motivates nonces

管理伺服器不應為任何具有過時 nonce 的 DiscoveryRequest 發送 DiscoveryResponse。在 DiscoveryResponse 中向 Envoy 呈現較新的 nonce 後,nonce 會過時。管理伺服器不需要發送更新,直到它確定有新版本可用。較早版本的請求也會過時。它可以處理同版本的多個 DiscoveryRequests,直到準備好新版本為止。

Requests become stale

上述資源更新順序的一個含義是,Envoy 不期望為它發出的每個 DiscoveryRequests 收到 DiscoveryResponse

資源暖機

叢集 (Clusters)監聽器 (Listeners) 可以處理請求之前,它們會先經歷暖機 (warming) 的過程。這個過程會在 Envoy 初始化期間以及 ClusterListener 更新時發生。Cluster 的暖機只有在管理伺服器提供 ClusterLoadAssignment 回應時才會完成。同樣地,Listener 的暖機只有在監聽器參考 RDS 配置時,管理伺服器提供 RouteConfiguration 時才會完成。管理伺服器應在暖機期間提供 EDS/RDS 更新。如果管理伺服器未提供 EDS/RDS 回應,Envoy 在初始化階段將不會初始化自身,且透過 CDS/LDS 發送的更新將不會生效,直到提供 EDS/RDS 回應為止。

注意

Envoy 特定實作注意事項

  • Cluster 的暖機只有在管理伺服器提供新的 ClusterLoadAssignment 回應時才會完成,即使端點沒有變更。如果運行時標誌 envoy.restart_features.use_eds_cache_for_ads 設定為 true,Envoy 將在資源暖機逾時後,使用快取的 ClusterLoadAssignment (如果存在) 來處理叢集。

  • 即使管理伺服器沒有傳送 Listener 所參考的 RouteConfiguration 回應,Listener 的暖機也會完成。Envoy 將使用先前傳送的 RouteConfiguration 來完成 Listener 的暖機。只有當 RouteConfiguration 已變更或過去從未傳送時,管理伺服器才必須傳送 RouteConfiguration 回應。

最終一致性考量

由於 Envoy 的 xDS API 最終會達到一致性,流量在更新期間可能會短暫下降。例如,如果只有透過 CDS/EDS 知道叢集 X,而 RouteConfiguration 參考叢集 X,然後在提供 Y 的 CDS/EDS 更新之前,調整為叢集 Y,則流量將會被黑洞化,直到 Envoy 實例知道 Y 為止。

對於某些應用程式來說,暫時的流量下降是可以接受的,客戶端或其他的 Envoy sidecar 的重試會隱藏這種下降。對於無法容忍下降的其他情況,可以透過提供同時包含 XY 的 CDS/EDS 更新來避免流量下降,然後進行將 X 重新指向 Y 的 RDS 更新,然後再進行移除 X 的 CDS/EDS 更新。

一般而言,為了避免流量下降,更新的順序應遵循「先建立後中斷」模式,其中

  • CDS 更新 (如果有的話) 必須始終先推送。

  • EDS 更新 (如果有的話) 必須在各自叢集的 CDS 更新之後到達。

  • LDS 更新必須在對應的 CDS/EDS 更新之後到達。

  • 與新加入的監聽器相關的 RDS 更新必須在 CDS/EDS/LDS 更新之後到達。

  • 與新加入的 RouteConfigurations 相關的 VHDS 更新 (如果有的話) 必須在 RDS 更新之後到達。

  • 然後可以移除過時的 CDS 叢集和相關的 EDS 端點 (不再被參考的那些)。

如果未加入新的叢集/路由/監聽器,或者如果在更新期間可以接受暫時的流量下降,則可以獨立推送 xDS 更新。請注意,在 LDS 更新的情況下,監聽器會在接收流量之前進行暖機,也就是說,如果已配置,則會透過 RDS 提取相關的路由。在新增/移除/更新叢集時,會暖機叢集。另一方面,路由不會暖機,也就是說,管理平面必須確保路由參考的叢集已就緒,然後再推送路由的更新。

TTL

如果管理伺服器變得無法連線,Envoy 會保留最後已知的配置,直到重新建立連線為止。對於某些服務而言,這可能不理想。例如,在錯誤注入服務的情況下,管理伺服器在錯誤的時間當機可能會使 Envoy 處於不良狀態。TTL 設定允許 Envoy 在與管理伺服器失去連線後,在指定的期間後移除一組資源。例如,這可以用於在無法再連線到管理伺服器時,終止錯誤注入測試。

對於支援 xds.config.supports-resource-ttl 用戶端功能的用戶端,可以在每個 資源 (Resource) 上指定 TTL 欄位。每個資源都會有自己的 TTL 到期時間,到期時資源將會過期。每種 xDS 類型都可能有不同的方式來處理此類過期。

若要更新與 Resource 關聯的 TTL,管理伺服器會重新傳送具有新 TTL 的資源。若要移除 TTL,管理伺服器會重新傳送未設定 TTL 欄位的資源。

為了允許輕量級的 TTL 更新 (「心跳」),可以傳送一個回應,該回應提供一個 資源 (Resource),該回應的 resource 未設定,且版本符合最近傳送的版本,可用於更新 TTL。這些資源將不會被視為資源更新,而僅視為 TTL 更新。

SotW TTL

為了將 TTL 與 SotW xDS 搭配使用,相關的資源必須包裝在 資源 (Resource) 中。這允許在 SotW 中設定與 Delta xDS 相同的 TTL 欄位,而無需變更 SotW API。SotW 也支援心跳:回應中任何看起來像心跳資源的資源都只會用於更新 TTL。

此功能受 xds.config.supports-resource-in-sotw 用戶端功能控制。

彙總探索服務

當管理伺服器分散時,要提供上述排序保證以避免流量下降是一項挑戰。ADS 允許單一管理伺服器透過單一 gRPC 串流傳遞所有 API 更新。這提供了仔細排序更新以避免流量下降的能力。使用 ADS,單一串流會與多個獨立的 DiscoveryRequest/DiscoveryResponse 序列 (透過 type URL 多工處理) 搭配使用。對於任何給定的 type URL,上述 DiscoveryRequestDiscoveryResponse 訊息的順序適用。範例更新順序可能如下所示

EDS/CDS multiplexed on an ADS stream

每個 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
    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: {}

增量 xDS

增量 xDS 是一個單獨的 xDS 端點,它

  • 允許協議以資源/資源名稱差異 (「Delta xDS」) 的形式在網路上進行通訊。這支援擴展 xDS 資源的目標。管理伺服器只需要傳遞已變更的單一叢集,而不是在修改單一叢集時傳遞所有 10 萬個叢集。

  • 允許 Envoy 隨選/延遲請求其他資源。例如,僅在收到該叢集的請求時才請求叢集。

增量 xDS 會話始終處於 gRPC 雙向串流的內容中。這允許 xDS 伺服器追蹤連線到它的 xDS 用戶端的狀態。目前尚無增量 xDS 的 REST 版本。

在 delta xDS 網路協議中,nonce 欄位是必要欄位,用於將 DeltaDiscoveryResponse 配對至 DeltaDiscoveryRequest ACK 或 NACK。選擇性地,回應訊息層級 system_version_info 僅用於偵錯目的。

可以在以下情況下傳送 DeltaDiscoveryRequest

請注意,雖然可以在請求中設定 response_nonce,但即使 nonce 過時,伺服器也必須遵守訂閱狀態的變更。nonce 可用於將 ack/nack 與伺服器回應關聯起來,但不應用於拒絕過時的請求。

在第一個範例中,客戶端連線並接收第一個更新,然後它會 ACK。 第二個更新失敗,客戶端會 NACK 該更新。 稍後,xDS 客戶端會自發請求「wc」資源。

Incremental session example

重新連線時,Incremental xDS 客戶端可以透過在 initial_resource_versions 中傳送其已知的資源來告知伺服器,以避免透過網路重新傳送這些資源。由於不假設從先前的串流保留任何狀態,因此重新連線的客戶端必須向伺服器提供它感興趣的所有資源名稱。

請注意,對於「萬用字元」訂閱(詳情請參閱 客戶端如何指定要傳回的資源),請求必須在 resource_names_subscribe 欄位中指定 *,或者(舊版行為)請求的 resource_names_subscriberesource_names_unsubscribe 中都不能有任何資源。

Incremental reconnect example

資源名稱

資源由資源名稱或別名識別。 如果存在資源的別名,則可以在 DeltaDiscoveryResponse 的資源中的 alias 欄位中識別。 資源名稱將在 DeltaDiscoveryResponse 的資源中的 name 欄位中傳回。

訂閱資源

客戶端可以在 resource_names_subscribeDeltaDiscoveryRequest 欄位中傳送資源的別名或名稱,以便訂閱資源。 應檢查資源的名稱和別名,以確定是否已訂閱相關實體。

resource_names_subscribe 欄位可能包含伺服器認為客戶端已訂閱的資源名稱,並且具有最新版本。 但是,伺服器必須還是在回應中提供這些資源;由於伺服器隱藏的實作細節,客戶端可能「忘記」這些資源,儘管它顯然仍然處於訂閱狀態。

取消訂閱資源

當客戶端對某些資源失去興趣時,它將在 resource_names_unsubscribeDeltaDiscoveryRequest 欄位中指示。 與 resource_names_subscribe 一樣,這些可能是資源名稱或別名。

resource_names_unsubscribe 欄位可能包含多餘的資源名稱,伺服器認為客戶端已經取消訂閱這些名稱。 伺服器必須乾淨地處理此類請求;它可以簡單地忽略這些幽靈取消訂閱。

在大多數情況下(例外情況請參閱下文),如果請求除了取消訂閱資源之外沒有執行任何操作,則伺服器不需要傳送任何回應;特別是,通常不要求伺服器在 removed_resources 欄位中傳送包含已取消訂閱資源名稱的回應。

但是,上述情況有一個例外:當客戶端具有萬用字元訂閱 (*) 訂閱另一個特定資源名稱時,該特定資源名稱也可能包含在萬用字元訂閱中,因此如果客戶端取消訂閱該特定資源名稱,它不知道是否要繼續快取該資源。 為了處理此問題,伺服器必須傳送一個回應,該回應在 removed_resources 欄位(如果未包含在萬用字元中)或在 resources 欄位(如果包含在萬用字元中)中包含該特定資源。

了解何時請求的資源不存在

當客戶端訂閱的資源不存在時,伺服器將傳送 DeltaDiscoveryResponse 訊息,該訊息在 removed_resources 欄位中包含該資源的名稱。 這讓客戶端可以快速確定資源何時不存在,而無需像在 SotW 通訊協定變體中那樣等待逾時。 但是,仍然鼓勵客戶端使用逾時來防止管理伺服器未能及時傳送回應的情況。

REST-JSON 輪詢訂閱

REST 端點也提供同步(長)輪詢用於 xDS 單例 API。 上述訊息排序類似,只是不維護與管理伺服器的持久串流。 預期在任何時間點都只有一個未完成的請求,因此在 REST-JSON 中,回應 nonce 是選填的。 proto3 的 JSON 標準轉換用於編碼 DiscoveryRequestDiscoveryResponse 訊息。 ADS 不適用於 REST-JSON 輪詢。

當輪詢週期設定為較小的值時,目的是進行長輪詢,那麼還需要避免傳送 DiscoveryResponse,除非透過 資源更新對基礎資源進行了變更。