Envoy 中的 xDS REST 和 gRPC 協議詳解

ServiceMesher發表於2018-10-12

Envoy 通過查詢檔案或管理伺服器來動態發現資源。概括地講,對應的發現服務及其相應的 API 被稱作 xDS。Envoy 通過訂閱(subscription)方式來獲取資源,如監控指定路徑下的檔案、啟動 gRPC 流或輪詢 REST-JSON URL。後兩種方式會傳送 DiscoveryRequest 請求訊息,發現的對應資源則包含在響應訊息 DiscoveryResponse 中。下面,我們將具體討論每種訂閱型別。

檔案訂閱

發現動態資源的最簡單方式就是將其儲存於檔案,並將路徑配置在 ConfigSource 中的 path 引數中。Envoy 使用 inotify(Mac OS X 上為 kqueue)來監控檔案的變化,在檔案被更新時,Envoy 讀取儲存的 DiscoveryResponse 資料進行解析,資料格式可以為二進位制 protobuf、JSON、YAML 和協議文字等。

譯者注:core.ConfigSource 配置格式如下:

{
  "path": "...",
  "api_config_source": "{...}",
  "ads": "{...}"
}複製程式碼

檔案訂閱方式可提供統計資料和日誌資訊,但是缺少 ACK/NACK 更新的機制。如果更新的配置被拒絕,xDS API 則繼續使用最後一個的有效配置。

gRPC 流式訂閱

單資源型別發現

每個 xDS API 可以單獨配置 ApiConfigSource,指向對應的上游管理伺服器的叢集地址。每個 xDS 資源型別會啟動一個獨立的雙向 gRPC 流,可能對應不同的管理伺服器。API 交付方式採用最終一致性。可以參考後續聚合服務發現(ADS) 章節來了解必要的顯式控制序列。

譯者注:core.ApiConfigSource 配置格式如下:

{
  "api_type": "...",
  "cluster_names": [],
  "grpc_services": [],
  "refresh_delay": "{...}",
  "request_timeout": "{...}"
}複製程式碼

型別 URL

每個 xDS API 都與給定的資源的型別存在 1:1 對應。關係如下:

的概念如下所示,其採用 type.googleapis.com/<resource type> 的形式,例如 CDS 對應於 type.googleapis.com/envoy.api.v2.Cluster。在 Envoy 的請求和管理伺服器的響應中,都包括了資源型別 URL。

ACK/NACK 和版本

每個 Envoy 流以 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複製程式碼

Envoy 在處理 DiscoveryResponse 響應後,將通過流傳送一個新的請求,請求包含應用成功的最後一個版本號和管理伺服器提供的 nonce。如果本次更新已成功應用,則 version_info 的值設定為 X,如下序列圖所示:

Envoy 中的 xDS REST 和 gRPC 協議詳解

在此序列圖及後續中,將統一使用以下縮寫格式:

  • DiscoveryRequest: (V=version_info,R=resource_names,N=response_nonce,T=type_url)

  • DiscoveryResponse: (V=version_info,R=resources,N=nonce,T=type_url)

譯者注:在資訊保安中,Nonce是一個在加密通訊只能使用一次的數字。在認證協議中,它往往是一個隨機偽隨機數,以避免重放攻擊。Nonce也用於流密碼以確保安全。如果需要使用相同的金鑰加密一個以上的訊息,就需要Nonce來確保不同的訊息與該金鑰加密的金鑰流不同。(引用自維基百科)在本文中nonce是每次更新的資料包的唯一標識。

版本為 Envoy 和管理伺服器提供了共享當前應用配置的概念和通過 ACK/NACK 來進行配置更新的機制。如果 Envoy 拒絕配置更新 X,則回覆 error_detail 及前一個的版本號,在當前情況下為空的初始版本號,error_detail 包含了有關錯誤的更加詳細的資訊:

Envoy 中的 xDS REST 和 gRPC 協議詳解

後續,API 更新可能會在新版本 Y 上成功:

Envoy 中的 xDS REST 和 gRPC 協議詳解

每個流都有自己的版本概念,但不存在跨資源型別的共享版本。在不使用 ADS 的情況下,每個資源型別可能具有不同的版本,因為 Envoy API 允許指向不同的 EDS/RDS 資源配置並對應不同的 ConfigSources

何時傳送更新

管理伺服器應該只向 Envoy 客戶端傳送上次 DiscoveryResponse 後更新過的資源。Envoy 則會根據接受或拒絕 DiscoveryResponse 的情況,立即回覆包含 ACK/NACK 的 DiscoveryRequest 請求。如果管理伺服器每次傳送相同的資源集結果,而不是根據其更新情況,則會導致 Envoy 和管理伺服器通訊效率大打折扣。

在同一個流中,新的 DiscoveryRequests 將取代此前具有相同的資源型別 DiscoveryRequest 請求。這意味著管理伺服器只需要響應給定資源型別最新的 DiscoveryRequest 請求即可。

資源提示

DiscoveryRequest 中的 resource_names 資訊作為資源提示出現。一些資源型別,例如 ClusterListener 將使用一個空的 resource_names,因為 Envoy 需要獲取管理伺服器對應於節點標識的所有 Cluster(CDS)和 Listener(LDS)。對於其他資源型別,如 RouteConfigurations(RDS)和 ClusterLoadAssignments(EDS),則遵循此前的 CDS/LDS 更新,Envoy 能夠明確地列舉這些資源。

LDS/CDS 資源提示資訊將始終為空,並且期望管理伺服器的每個響應都提供 LDS/CDS 資源的完整狀態。缺席的 ListenerCluster 將被刪除。

對於 EDS/RDS,管理伺服器並不需要為每個請求的資源進行響應,而且還可能提供額外未請求的資源。resource_names 只是一個提示。Envoy 將默默地忽略返回的多餘資源。如果請求的資源中缺少相應的 RDS 或 EDS 更新,Envoy 將保留對應資源的最後的值。管理伺服器可能會依據 DiscoveryRequestnode 標識推斷其所需的 EDS/RDS 資源,在這種情況下,提示資訊可能會被丟棄。從相應的角度來看,空的 EDS/RDS DiscoveryResponse 響應實際上是表明在 Envoy 中為一個空的資源。

ListenerCluster 被刪除時,其對應的 EDS 和 RDS 資源也需要在 Envoy 例項中刪除。為使 EDS 資源被 Envoy 已知或跟蹤,就必須存在應用過的 Cluster 定義(如通過 CDS 獲取)。RDS 和 Listeners 之間存在類似的關係(如通過 LDS 獲取)。

對於 EDS/RDS ,Envoy 可以為每個給定型別的資源生成不同的流(如每個 ConfigSource 都有自己的上游管理伺服器的叢集)或當指定資源型別的請求傳送到同一個管理伺服器的時候,允許將多個資源請求組合在一起傳送。雖然可以單個實現,但管理伺服器應具備處理每個給定資源型別中對單個或多個 resource_names 請求的能力。下面的兩個序列圖對於獲取兩個 EDS 資源都是有效的 {foo,bar}

Envoy 中的 xDS REST 和 gRPC 協議詳解 Envoy 中的 xDS REST 和 gRPC 協議詳解

資源更新

如上所述,Envoy 可能會更新 DiscoveryRequest 中出現的 resource_names 列表,其中 DiscoveryRequest 是用來 ACK/NACK 管理伺服器的特定的 DiscoveryResponse 。此外,Envoy 後續可能會傳送額外的 DiscoveryRequests ,用於在特定 version_info 上使用新的資源提示來更新管理伺服器。例如,如果 Envoy 在 EDS 版本 X 時僅知道叢集 foo,但在隨後收到的 CDS 更新時額外獲取了叢集 bar ,它可能會為版本 X 發出額外的 DiscoveryRequest 請求,並將 {foo,bar} 作為請求的 resource_names

Envoy 中的 xDS REST 和 gRPC 協議詳解

這裡可能會出現競爭狀況;如果 Envoy 在版本 X 上釋出了資源提示更新請求,但在管理伺服器處理該請求之前傳送了新的版本號為 Y 的響應,針對 version_infoX 的版本,資源提示更新可能會被解釋為拒絕 Y 。為避免這種情況,通過使用管理伺服器提供的 nonce,Envoy 可用來保證每個 DiscoveryRequest 對應到相應的 DiscoveryResponse

Envoy 中的 xDS REST 和 gRPC 協議詳解

管理伺服器不應該為含有過期 nonceDiscoveryRequest 傳送 DiscoveryResponse 響應。在向 Envoy 傳送的 DiscoveryResponse 中包含了的新 nonce ,則此前的 nonce 將過期。在資源新版本就緒之前,管理伺服器不需要向 Envoy 傳送更新。同版本的早期請求將會過期。在新版本就緒時,管理伺服器可能會處理同一個版本號的多個 DiscoveryRequests請求。

Envoy 中的 xDS REST 和 gRPC 協議詳解

上述資源更新序列表明 Envoy 並不能期待其發出的每個 DiscoveryRequest 都得到 DiscoveryResponse 響應。

最終一致性考慮

由於 Envoy 的 xDS API 採用最終一致性,因此在更新期間可能導致流量被丟棄。例如,如果通過 CDS/EDS 僅獲取到了叢集 X,而且 RouteConfiguration 引用了叢集 X;在 CDS/EDS 更新叢集 Y 配置之前,如果將 RouteConfiguration 將引用的叢集調整為 Y ,那麼流量將被吸入黑洞而丟棄,直至叢集 Y 被 Envoy 例項獲取。

對某些應用程式,可接受臨時的流量丟棄,客戶端重試或其他 Envoy sidecar 會掩蓋流量丟棄。那些對流量丟棄不能容忍的場景,可以通過以下方式避免流量丟失,CDS/EDS 更新同時攜帶 XY ,然後傳送 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,以上 DiscoveryRequestDiscoveryResponse 訊息序列都適用。 更新序列可能如下所示:

Envoy 中的 xDS REST 和 gRPC 協議詳解

每個 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 資源可伸縮性的目標。管理伺服器只需交付更改的單個叢集,而不是在修改單個叢集時交付所有上萬個叢集。

xDS 增量會話始終位於 gRPC 雙向流的上下文中。這允許 xDS 伺服器能夠跟蹤到連線的 xDS 客戶端的狀態。xDS REST 版本不支援增量。

在增量 xDS 中,nonce 欄位是必需的,用於匹配 IncrementalDiscoveryResponse 關聯的 ACK 或 NACK IncrementalDiscoveryRequest。可選地,存在響應訊息級別的 system_version_info,但僅用於除錯目的。

IncrementalDiscoveryRequest 可在以下 3 種情況下傳送:

  1. xDS 雙向 gRPC 流的初始訊息。

  2. 作為對先前的 IncrementalDiscoveryResponse 的 ACK 或 NACK 響應。在這種情況下,response_nonce 被設定為響應中的 nonce 值。ACK 或 NACK 由可由 error_detail 欄位是否出現來區分。

  3. 客戶端自發的 IncrementalDiscoveryRequest。此場景下可以採用動態新增或刪除被跟蹤的 resource_names 集。這種場景下,必須忽略 response_nonce

在第一個示例中,客戶端連線並接收它的第一個更新並 ACK。第二次更新失敗,客戶端傳送 NACK 拒絕更新。xDS客戶端後續會自發地請求 “wc” 相關資源。

Envoy 中的 xDS REST 和 gRPC 協議詳解

在重新連線時,支援增量的 xDS 客戶端可能會告訴伺服器其已知資源從而避免通過網路重新傳送它們。

Envoy 中的 xDS REST 和 gRPC 協議詳解

REST-JSON 輪詢訂閱

單個 xDS API 可對 REST 端點進行的同步(長)輪詢。除了無持久流與管理伺服器互動外,訊息順序與上述相似。在任何時間點,只存在一個未完成的請求,因此響應訊息中的 nonce 在 REST-JSON 中是可選的。DiscoveryRequestDiscoveryResponse 的訊息編碼遵循 JSON 變換 proto3 規範。ADS 不支援 REST-JSON 輪詢。

當輪詢期間設定為較小的值時,則可以等同於長輪詢,這時要求避免傳送 DiscoveryResponse除非對請求的資源發生了更改

本文轉自:www.servicemesher.com/blog/envoy-…


相關文章