前面我們和大家學習了 Envoy 的基礎知識,使用靜態配置來認識了 Envoy,但實際上 Envoy 的閃光點在于其動態配置,動態配置主要有基于文件和 API 兩種方式。
基于文件的動態配置
Envoy 除了支持靜態配置之外,還支持動態配置,而且動態配置也是 Envoy 重點關注的功能,本節我們將學習如何將 Envoy 靜態配置轉換為動態配置,從而允許 Envoy 自動更新。
Envoy 動態配置
前面的章節中,我們都是直接使用的靜態配置,但是當我們需要更改配置的時候就比較麻煩了,需要重啟 Envoy 代理才會生效。要解決這個問題,我們可以將靜態配置更改成動態配置,當我們使用動態配置的時候,更改了配置,Envoy 將會自動去重新加載配置。
Envoy 支持不同的模塊進行動態配置,可配置的有如下幾個 API(統稱為 xDS):
- EDS:端點發現服務(EDS)可以讓 Envoy 自動發現上游集群的成員,這使得我們可以動態添加或者刪除處理流量請求的服務。
- CDS:集群發現服務(CDS)可以讓 Envoy 通過該機制自動發現在路由過程中使用的上游集群。
- RDS:路由發現服務(RDS)可以讓 Envoy 在運行時自動發現 HTTP 連接管理過濾器的整個路由配置,這可以讓我們來完成諸如動態更改流量分配或者藍綠發布之類的功能。
- VHDS:虛擬主機發現服務(VHDS)允許根據需要與路由配置本身分開請求屬于路由配置的虛擬主機。該 API 通常用于路由配置中有大量虛擬主機的部署中。
- SRDS:作用域路由發現服務(SRDS)允許將路由表分解為多個部分。該 API 通常用于具有大量路由表的 HTTP 路由部署中。
- LDS:監聽器發現服務(LDS)可以讓 Envoy 在運行時自動發現整個監聽器。
- SDS:密鑰發現服務(SDS)可以讓 Envoy 自動發現監聽器的加密密鑰(證書、私鑰等)以及證書校驗邏輯(受信任的根證書、吊銷等)。
- RTDS:運行時發現服務 (RTDS) API 允許通過 xDS API 獲取運行時層。這可以通過文件系統層進行補充或改善。
- ECDS:擴展配置發現服務 (ECDS) API 允許獨立于偵聽器提供擴展配置(例如 HTTP 過濾器配置)。當構建更適合與主控制平面分離的系統(例如 WAF、故障測試等)時,這非常有用。
- ADS:EDS、CDS 等都是單獨的服務,具有不同的 REST/gRPC 服務名稱,例如 StreamListeners、StreamSecrets。對于希望強制資源按照不同類型的順序到達 Envoy 的用戶來說,有聚合的 xDS,這是一個單獨的 gRPC 服務,在一個 gRPC 流中攜帶所有資源類型。(ADS 只支持 gRPC)。
動態資源,是指由 Envoy 通過 xDS 協議發現所需要的各項配置的機制,相關的配置信息保存于稱之為管理服務器(Management Server )的主機上,經由 xDS API 向外暴露;下面是一個純動態資源的基礎配置框架。
{
"lds_config": "{...}",
"cds_config": "{...}",
"ads_config": "{...}"
}
xDS API 為 Envoy 提供了資源的動態配置機制,它也被稱為 Data Plane API。
xDS API
Envoy 支持三種類型的配置信息的動態發現機制,相關的發現服務及其相應的 API 聯合起來 稱為 xDS API。
- 基于文件系統發現:指定要監視的文件系統路徑
-
gRPC 服務:啟動 gRPC 流。
- REST 服務:輪詢 REST-JSON URL。
- 通過查詢一到多個管理服務器發現:通過 DiscoveryRequest 協議報文發送請求,并要求服務方以 DiscoveryResponse 協議報文進行響應。
v3 版本的 xDS 支持如下幾種資源類型:
- 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
- envoy.extensions.transport_sockets.tls.v3.Secret
- envoy.service.runtime.v3.Runtime
Envoy 對 xDS API 的管理由后端服務器實現,包括 LDS、CDS、RDS、SRDS(Scoped Route)、VHDS (Virtual Host)、EDS、SDS、RTDS(Runtime )等。
- 所有這些 API 都提供了最終的一致性,并且彼此間不存在相互影響;
- 部分更高級別的操作(例如執行服務的 A/B 部署)需要進行排序以防止流量被丟棄,因此,基于一個管理服務器提供多類 API 時還需要使用聚合發現服務(ADS )API。
- ADS API 允許所有其他 API 通過來自單個管理服務器的單個 gRPC 雙向流進行編組,從而允許對操作進行確定性排序
接下來我們先更改配置來使用 EDS,讓 Envoy 根據配置文件的數據來動態添加節點。
Cluster ID
首先我們這里定義了一個基本的 Envoy 配置文件,如下所示:
# envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0 # 監聽器的名稱
address:
socket_address:
address: 0.0.0.0 # 監聽器的地址
port_value: 10000 # 監聽器的端口
filter_chAIns:
- filters:
- name: envoy.filters.NETwork.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters: # 定義http過濾器鏈
- name: envoy.filters.http.router # 調用7層的路由過濾器
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: targetCluster
現在我們還沒有配置 clusters 集群部分,這是因為我們要通過使用 EDS 來進行自動發現。
首先我們需要添加一個 node 節點讓 Envoy 來識別并應用這一個唯一的配置,動態配置中 Envoy 實例需要有唯一的 id 標識。將下面的配置放置在配置文件的頂部區域:
node:
id: envoy_eds_id
cluster: youdianzhishi_cluster
除了 id 和 cluster 之外,我們還可以配置基于區域的一些位置信息來進行聲明,比如 region、zone、sub_zone 等。
EDS 配置
端點發現服務 EDS 是基于 gRPC 或 REST-JSON API 服務器的 xDS 管理服務器,Envoy 使用它來獲取集群成員。集群成員在 Envoy 術語中稱為“端點”。對于每個集群,Envoy 從發現服務獲取端點,EDS 是首選的服務發現機制:
- Envoy 明確了解每個上游主機(相對于通過 DNS 解析的負載均衡器進行路由),并且可以做出更智能的負載均衡決策。
- 每個主機的發現 API 響應中攜帶的額外屬性告知 Envoy 主機的負載均衡權重、金絲雀狀態、區域等。這些附加屬性由 Envoy 網格在負載均衡、統計收集等過程中全局使用。
接下來我們就可以來定義 EDS 配置了,可以來動態控制上游集群數據。在前面這部分的靜態配置是這樣的:
clusters:
- name: targetCluster
connect_timeout: 0.25s
type: STRICT_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: targetCluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.215.3
port_value: 80
- endpoint:
address:
socket_address:
address: 192.168.215.4
port_value: 80
現在我們將上面的靜態配置轉換成動態配置,首先需要轉換為基于 EDS 的 eds_cluster_config 屬性,并將類型更改為 EDS,將下面的集群配置添加到 Envoy 配置的末尾:
clusters:
- name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices # 可選,代替集群的名稱,提供給 EDS 服務
eds_config: # 集群的 EDS 更新源配置
path_config_source: # 本地文件配置源
path: "/etc/envoy/eds.yaml"
# watched_directory: # 可選,監視目錄中的文件更改
# path: "/etc/envoy"
在上面的集群配置中我們設置了 type: EDS,表示這是一個基于 EDS 的集群配置,然后使用 eds_cluster_config 屬性來定義 EDS 的配置信息,其中 service_name 屬性是可選的,如果沒有設置則使用集群的名稱,這個屬性是提供給 EDS 服務的,eds_config 屬性定義了 EDS 更新源的配置,這里我們使用的是本地文件配置源,所以使用 path_config_source 屬性來指定本地配置文件的路徑,這里我們使用的是 /etc/envoy/eds.yaml 這個文件,這個文件將會被 Envoy 代理監視,當文件內容發生變化的時候,Envoy 將會自動更新配置。
此外還可以配置一個 watched_directory 屬性來監視目錄中的文件更改,當該目錄中的文件被移動到時,該路徑將被重新加載。這在某些部署場景中是必需的。比如如果我們使用 Kubernetes ConfigMap 來加載 xDS 資源,則可能會使用以下配置:
- 將 xds.yaml 存儲在 ConfigMap 內。
- 將 ConfigMap 掛載到 /config_map/xds
- 配置路徑 /config_map/xds/xds.yaml
- 配置監視目錄 /config_map/xds
上述配置將確保 Envoy 監視所屬目錄的移動,這是由于 Kubernetes 在原子更新期間管理 ConfigMap 符號鏈接的方式而必需的。
上游的服務器 192.168.215.3 和 192.168.215.3 就將來自于 /etc/envoy/eds.yaml 這個文件,我們創建一個如下所示的 eds.yaml 文件,內容如下所示:
resources:
- "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
cluster_name: localservices # 集群的名稱,如果在集群 eds_cluster_config 中指定,這將是 service_name 值。
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.215.3
port_value: 80
上面我們暫時只定義了 192.168.215.3 這一個端點。該配置文件是以 DiscoveryResponse 的格式提供響應實例的。
現在配置完成后,我們可以啟動 Envoy 代理來進行測試。執行下面的命令啟動 Envoy 容器:
$ Docker run --name=proxy-eds -d
-p 9901:9901
-p 80:10000
-v $(pwd)/manifests/3.Envoy:/etc/envoy
envoyproxy/envoy:v1.28.0
然后同樣和前面一樣運行兩個 HTTP 服務來作為上游服務器:
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4ee790db09db cnych/docker-http-server "/App" 3 seconds ago Up 3 seconds 80/tcp fervent_khorana
f9456b56f1ff cnych/docker-http-server "/app" 4 seconds ago Up 3 seconds 80/tcp wonderful_carson
f9ce95dcc434 envoyproxy/envoy:v1.28.0 "/docker-entrypoint.…" About a minute ago Up 44 seconds 0.0.0.0:9901->9901/tcp, :::9901->9901/tcp, 0.0.0.0:80->10000/tcp, :::80->10000/tcp proxy-eds
根據上面的 EDS 配置,Envoy 將把所有的流量都發送到 192.168.215.3 這一個節點上去,我們可以使用 curl localhost 來測試下:
$ curl localhost
<h1>This request was processed by host: f9456b56f1ff</h1>
$ curl localhost
<h1>This request was processed by host: f9456b56f1ff</h1>
可以看到可以正常得到響應,而且都是由 f9456b56f1ff 這個容器來處理的請求?,F在我們來嘗試更新上面的 EDS 配置添加上另外的一個節點,觀察 Envoy 代理是否會自動生效。
由于我們這里使用的是 EDS 動態配置,所以當我們要擴展上游服務的時候,只需要將新的端點添加到上面我們指定的 eds.yaml 配置文件中即可,然后 Envoy 就會自動將新添加的端點包含進來。用上面同樣的方式添加 192.168.215.4 這個端點,eds.yaml 內容如下所示:
resources:
- "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
cluster_name: localservices # 集群的名稱,如果在集群 eds_cluster_config 中指定,這將是 service_name 值。
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.215.3
port_value: 80
- endpoint:
address:
socket_address:
address: 192.168.215.4
port_value: 80
更新后,正常情況下 Envoy 就會自動重新加載配置并將新的端點添加到負載均衡中去,這個時候我們再來訪問代理:
$ curl localhost
<h1>This request was processed by host: 2135ba4e10c9</h1>
$ curl localhost
<h1>This request was processed by host: f9456b56f1ff</h1>
可以看到已經可以自動訪問到另外的端點去了。
我在測試階段發現在 mac 系統下面并沒有自動熱加載,在 linux 系統下面是可以正常重新加載的。
CDS 配置
現在已經配置好了 EDS,接下來我們就可以去擴大上游集群的規模了,如果我們想要能夠動態添加新的域名和集群,就需要實現集群發現服務(CDS)API,在下面的示例中,我們將配置集群發現服務(CDS)和監聽器發現服務(LDS)來進行動態配置。
創建一個名為 cds.yaml 的文件來配置集群服務發現的數據,文件內容如下所示:
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds.yaml
此外,還需要創建一個名為 lds.yaml 的文件來放置監聽器的配置,文件內容如下所示:
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
name: listener_0 # 監聽器的名稱
address:
socket_address:
address: 0.0.0.0 # 監聽器的地址
port_value: 10000 # 監聽器的端口
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters: # 定義http過濾器鏈
- name: envoy.filters.http.router # 調用7層的路由過濾器
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: targetCluster
仔細觀察可以發現 cds.yaml 和 lds.yaml 配置文件的內容基本上和之前的靜態配置文件一致的。我們這里只是將集群和監聽器拆分到外部文件中去,這個時候我們需要修改 Envoy 的配置來引用這些文件,我們可以通過將 static_resources 更改為 dynamic_resources 來進行配置。
重新新建一個 Envoy 配置文件,命名為 envoy-dynamic.yaml,內容如下所示:
# envoy-dynamic.yaml
node:
id: envoy_eds_id
cluster: youdianzhishi_cluster
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 9901
dynamic_resources: # 動態配置
lds_config:
path: "/etc/envoy/lds.yaml"
cds_config:
path: "/etc/envoy/cds.yaml"
然后使用上面的配置文件重新啟動一個新的 Envoy 代理,命令如下所示:
$ docker run --name=proxy-xds -d
-p 9901:9901
-p 80:10000
-v $(pwd)/manifests/3.Envoy:/etc/envoy
-v $(pwd)/manifests/3.Envoy/envoy-dynamic.yaml:/etc/envoy/envoy.yaml
envoyproxy/envoy:v1.28.0
啟動完成后,同樣可以訪問 Envoy 代理來測試是否生效了:
$ curl localhostcurl localhost
<h1>This request was processed by host: 4ee790db09db</h1>
$ curl localhost
<h1>This request was processed by host: f9456b56f1ff</h1>
現在我們再基于上面配置的 CDS、LDS、EDS 的配置來動態添加一個新的集群。比如添加一個名為 newTargetCluster 的集群,內容如下所示:
# cds.yaml
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: targetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds.yaml
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
name: newTargetCluster
connect_timeout: 0.25s
lb_policy: ROUND_ROBIN
type: EDS
eds_cluster_config:
service_name: localservices
eds_config:
path: /etc/envoy/eds-1.yaml
上面我們新增了一個新的集群,對應的 eds_config 配置文件是 eds-1.yaml,所以我們同樣需要去創建該文件去配置新的端點服務數據,內容如下所示:
resources:
- "@type": type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment
cluster_name: localservices # 集群的名稱,如果在集群 eds_cluster_config 中指定,這將是 service_name 值。
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.215.5
port_value: 80
- endpoint:
address:
socket_address:
address: 192.168.215.6
port_value: 80
這個時候新的集群添加上了,但是還沒有任何路由來使用這個新集群,我們可以在 lds.yaml 中去配置,將之前配置的 targetCluster 替換成 newTargetCluster。
當然同樣我們這里還需要運行兩個簡單的 HTTP 服務來作為上游服務提供服務,執行如下所示的命令:
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;
這個時候 Envoy 應該就會自動重新加載并添加新的集群,我們同樣可以執行 curl localhost 命令來驗證:
$ curl localhost
<h1>This request was processed by host: 5f43efcb9432</h1>
$ curl localhost
<h1>This request was processed by host: 4986b39d716f</h1>
可以看到已經變成了新的兩個端點數據了,證明我們這里基于文件的 xDS 動態配置已經生效了。
基于 API 的動態配置
當在 Envoy 配置中定義了上游集群后,Envoy 需要知道如何解析集群成員,這就是服務發現。端點發現服務(EDS)是 Envoy 基于 gRPC 或者用來獲取集群成員的 REST-JSON API 服務的 xDS 管理服務。在本節我們將學習如何使用 REST-JSON API 來配置端點的自動發現。
在前面的章節中,我們使用文件來定義了靜態和動態配置,在這里我們將介紹另外一種方式來進行動態配置:API 動態配置。
Envoy 項目在 JAVA 和 Golang 中都提供了 EDS 和其他服務發現的 gRPC 實現參考。
REST-JSON 服務
接下來我們將更改配置來使用 EDS,從而允許基于來自 REST-JSON API 服務的數據進行動態添加節點。
EDS 配置
下面是提供的一個 Envoy 配置的初始配置 envoy.yaml,文件內容如下所示:
# envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0 # 監聽器的名稱
address:
socket_address:
address: 0.0.0.0 # 監聽器的地址
port_value: 10000 # 監聽器的端口
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
http_filters: # 定義http過濾器鏈
- name: envoy.filters.http.router # 調用7層的路由過濾器
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["*"]
routes:
- match:
prefix: "/"
route:
cluster: targetCluster
接下來需要添加一個 EDS 類型的集群配置,并在 eds_config 中配置使用 REST API:
clusters:
- name: targetCluster
type: EDS
connect_timeout: 0.25s
eds_cluster_config:
service_name: myservice
eds_config:
resource_api_version: V3 # xDS資源的API版本,支持 AUTO、V2、V3,如果未指定,默認為 v2。
api_config_source: # api_config_source的數據來自于 xDS API Server,即 Management Server。
api_type: REST
cluster_names: [xds_cluster] # 該字段只用于REST,cluster_names 的集群必須是靜態定義的,其類型不能是EDS。
transport_api_version: V3
refresh_delay: 5s
上面配置中我們使用 api_config_source 來使用 REST API 的配置,其中 api_type 屬性指定了使用 REST API,cluster_names 屬性指定了使用 xds_cluster 這個集群來獲取數據,refresh_delay 屬性指定了刷新間隔時間,這里我們設置為 5 秒。
然后需要定義 xds_cluster 的解析方式,這里我們可以使用靜態配置:
- name: xds_cluster
type: STATIC
connect_timeout: 0.25s
load_assignment:
cluster_name: xds_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 192.168.0.112
port_value: 8080
然后同樣啟動一個 Envoy 代理實例來進行測試:
$ docker run --name=api-eds -d
-p 9901:9901
-p 80:10000
-v $(pwd)/manifests/4.Envoy:/etc/envoy
envoyproxy/envoy:v1.28.0
然后啟動一個如下所示的上游端點服務:
$ docker run -p 8081:8081 -d -e EDS_SERVER_PORT='8081' cnych/docker-http-server:v4
啟動完成后我們可以使用如下命令來測試上游的端點服務:
$ curl http://localhost:8081 -i
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 36
Server: Werkzeug/0.15.4 Python/ target=_blank class=infotextkey>Python/2.7.16
Date: Fri, 27 Oct 2023 06:58:29 GMT
4caf19d5-6765-470b-a95c-a3615aea9796
現在我們啟動了 Envoy 代理和上游的服務集群,但是由于我們這里啟動的服務并不是 xds_cluster 中配置的服務,所以還沒有連接它們。這個時候我們去查看 Envoy 代理得日志,可以看到如下所示的一些錯誤:
$ docker logs -f api-eds
[2023-10-27 08:43:48.964][1][warning][config] [source/extensions/config_subscription/rest/http_subscription_impl.cc:120] REST update for /v3/discovery:endpoints failed
......
啟動 EDS 服務
為了讓 Envoy 獲取端點服務,我們需要啟動 xds_cluster,我們這里將使用 python 實現的一個 REST-JSON 的管理服務,代碼如下所示:
# server.py
from flask import Flask, request, jsonify
import uuid
app = Flask(__name__)
# 存儲 endpoints 的數據庫
endpoints_db = {}
@app.route('/endpoints', methods=['POST'])
def add_endpoint():
data = request.get_json()
endpoint_id = str(uuid.uuid4())
endpoints_db[endpoint_id] = data
return jsonify({"id": endpoint_id}), 201
@app.route('/v3/discovery:endpoints', methods=['POST'])
def discovery_endpoints():
xds_response = { # 構造 xDS v3 EDS 響應格式
"version_info": "0",
"resources": [
{"@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"cluster_name": "myservice",
"endpoints": [{"lb_endpoints": [{"endpoint": {"address": {"socket_address": endpoint}}}
for endpoint in endpoints_db.values()]}]
}
],
"type_url": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
"nonce": "0"
}
return jsonify(xds_response)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=True)
上面的代碼中我們使用 Flask 實現了一個 /v3/discovery:endpoints 的 POST 接口,這個是 Envoy 用來請求發現 Endpoints 端點的接口,需要注意的是該接口必須返回 DiscoveryResponse 協議格式的數據,我們這里就是組裝一個 ClusterLoadAssignment。
我們這里直接在本地啟動(當然也可以打包成 Docker 鏡像在容器中啟動):
$ python server.py
* Serving Flask app 'server'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:8080
* Running on http://192.168.0.112:8080
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 131-555-179
要注意前面配置的 xds_cluster 的地址和端口要和上面的服務一致,并要能夠訪問到。
然后我們就可以將上游的服務配置添加到 EDS 服務中去了,這樣可以讓 Envoy 來自動發現上游服務。上面的管理服務中我們定義了一個 /endpoints 的添加端點的接口,我們只需要將要添加的端點提交給這個接口即可,然后在發現接口里面會自動獲取添加的端點,然后 Envoy 就可以動態感知到了。
$ curl --location --request POST 'http://localhost:8080/endpoints'
--header 'Content-Type: application/json'
--data-raw '{
"address": "192.168.215.7",
"port_value": 8081
}'
由于我們已經啟動了上面注冊的上游服務,所以現在我們可以通過 Envoy 代理訪問到它了:
$ curl -i http://localhost
HTTP/1.1 200 OK
content-type: text/html; charset=utf-8
content-length: 36
server: envoy
date: Fri, 27 Oct 2023 09:02:07 GMT
x-envoy-upstream-service-time: 8
4caf19d5-6765-470b-a95c-a3615aea9796
接下來我們在上游集群中運行更多的節點,并調用 API 來進行動態注冊,使用如下所示的命令來向上游集群再添加 4 個節點:
for i in 8082 8083 8084 8085
do
docker run -d -e EDS_SERVER_PORT=$i cnych/docker-http-server:v4;
sleep .5
done
然后將上面的 4 個節點注冊到 EDS 服務上面去,同樣使用如下所示的 API 接口調用:
$ curl --location --request POST 'http://localhost:8080/endpoints'
--header 'Content-Type: application/json'
--data-raw '{
"address": "192.168.215.8",
"port_value": 8082
}'
$ curl --location --request POST 'http://localhost:8080/endpoints'
--header 'Content-Type: application/json'
--data-raw '{
"address": "192.168.215.9",
"port_value": 8083
}'
$ curl --location --request POST 'http://localhost:8080/endpoints'
--header 'Content-Type: application/json'
--data-raw '{
"address": "192.168.215.10",
"port_value": 8084
}'
$ curl --location --request POST 'http://localhost:8080/endpoints'
--header 'Content-Type: application/json'
--data-raw '{
"address": "192.168.215.11",
"port_value": 8085
}'
注冊成功后,我們可以通過如下所示的命令來驗證網絡請求是否與注冊的節點之間是均衡的:
$ while true; do curl http://localhost; sleep .5; printf 'n'; done
2a73139d-5929-4224-a227-a1aa560162df
4caf19d5-6765-470b-a95c-a3615aea9796
4395d85c-a216-46e4-bed7-04cc122c1903
9a22d774-62aa-47cc-bc35-c592015e5580
4caf19d5-6765-470b-a95c-a3615aea9796
2a73139d-5929-4224-a227-a1aa560162df
......
根據上面的輸出結果可以看到每次請求的服務是不同的響應,我們一共注冊了 5 個端點服務。
到這里我們就實現了基于 REST-JSON 方式的 EDS 動態配置了,當然在實際使用的時候,更多的時候會使用 gRPC 的方式來實現管理服務,這樣可以實現流式的數據傳輸,更加高效,可以查看官方提供的 go-control-plane 示例了解如何實現(https://Github.com/envoyproxy/go-control-plane/blob/main/internal/example/server.go)。
gRPC xDS 服務相比 REST-JSON xDS 服務,通常更為復雜一些,因為 gRPC 是基于 HTTP/2 的,并且使用 Protocol Buffers 作為序列化協議。
首先我們需要定義一個 gRPC 服務來實現 xDS API??梢詮?Envoy 的 data-plane-api(https://github.com/envoyproxy/data-plane-api/blob/main/envoy/service/endpoint/v3/eds.proto) 獲取 .proto 文件,然后可以使用 protoc 編譯器生成 Python 代碼,然后就可以去實現具體的業務邏輯了,比如 istio 就類似這種方式。