Envoy 是一個(gè)用 C++ 開(kāi)發(fā)的高性能代理,Envoy 是一種 L7 代理和通信總線(xiàn),專(zhuān)為大型的現(xiàn)代面向服務(wù)的架構(gòu)而設(shè)計(jì)。
Envoy 的誕生源于以下理念:
網(wǎng)絡(luò)對(duì)于應(yīng)用程序來(lái)說(shuō)應(yīng)該是透明的,當(dāng)網(wǎng)絡(luò)和應(yīng)用程序出現(xiàn)問(wèn)題時(shí),應(yīng)該很容易確定問(wèn)題的源頭。
當(dāng)然要實(shí)現(xiàn)上述目標(biāo)是非常困難的。Envoy 試圖通過(guò)提供以下高級(jí)功能來(lái)實(shí)現(xiàn)這一目標(biāo):
非侵入架構(gòu): Envoy 是一個(gè)獨(dú)立的進(jìn)程,設(shè)計(jì)為伴隨每個(gè)應(yīng)用程序服務(wù)一起運(yùn)行。所有 Envoy 實(shí)例形成一個(gè)透明的通信網(wǎng)格,每個(gè)應(yīng)用程序通過(guò) localhost 發(fā)送和接收消息,不需要知道網(wǎng)絡(luò)拓?fù)洹?duì)服務(wù)的實(shí)現(xiàn)語(yǔ)言也完全無(wú)感知,這種模式也被稱(chēng)為 Sidecar 模式。
L3/L4 過(guò)濾器架構(gòu): Envoy 的核心是一個(gè) L3/L4 層的網(wǎng)絡(luò)代理。可插拔的過(guò)濾器鏈機(jī)制允許編寫(xiě)不同的 TCP/UDP 代理任務(wù)的過(guò)濾器,并將其插入到主服務(wù)器中。而且已經(jīng)內(nèi)置支持了各種任務(wù)的過(guò)濾器,例如原始 TCP 代理、UDP 代理、HTTP 代理、TLS 客戶(hù)端證書(shū)身份驗(yàn)證、Redis、MongoDB、Postgres 等。
HTTP L7 過(guò)濾器架構(gòu): HTTP 是現(xiàn)代應(yīng)用程序架構(gòu)的關(guān)鍵組件,因此 Envoy 支持了一個(gè)額外的 HTTP L7 過(guò)濾器層。HTTP 過(guò)濾器可以被插入到 HTTP 連接管理子系統(tǒng)中,執(zhí)行不同的任務(wù),如緩存、速率限制、路由/轉(zhuǎn)發(fā)、嗅探 Amazon 的 DynamoDB 等。
頂級(jí)的 HTTP/2 支持: 在 HTTP 模式下運(yùn)行時(shí),Envoy 同時(shí)支持 HTTP/1.1 和 HTTP/2。Envoy 可以作為透明的 HTTP/1.1 到 HTTP/2 雙向代理運(yùn)行。這意味著可以連接任何組合的 HTTP/1.1 和 HTTP/2 客戶(hù)端與目標(biāo)服務(wù)器。推薦的服務(wù)到服務(wù)配置在所有 Envoy 之間使用 HTTP/2 創(chuàng)建持久連接網(wǎng)格,請(qǐng)求和響應(yīng)可以在該連接上進(jìn)行多路復(fù)用。
HTTP/3 支持(目前處于 alpha 版): 從 Envoy 1.19.0 版本開(kāi)始,Envoy 現(xiàn)在支持上游和下游的 HTTP/3,而且可以在任何方向上進(jìn)行 HTTP/1.1、HTTP/2 和 HTTP/3 之間的轉(zhuǎn)換。
HTTP L7 路由: 在 HTTP 模式下運(yùn)行時(shí),Envoy 支持路由子系統(tǒng),該子系統(tǒng)能夠根據(jù)路徑、權(quán)限、內(nèi)容類(lèi)型、運(yùn)行時(shí)值等路由和重定向請(qǐng)求。在使用 Envoy 作為前端/邊緣代理時(shí),此功能非常有用,但在構(gòu)建服務(wù)到服務(wù)的網(wǎng)格時(shí)也可以利用它。
gRPC 支持: gRPC 是 Google 的一個(gè) RPC 框架,使用 HTTP/2 或更高版本作為底層多路復(fù)用傳輸。Envoy 支持用作 gRPC 請(qǐng)求和響應(yīng)的路由和負(fù)載均衡基礎(chǔ)所需的所有 HTTP/2 功能,這兩個(gè)系統(tǒng)非常互補(bǔ)。
服務(wù)發(fā)現(xiàn)和動(dòng)態(tài)配置: Envoy 可以選擇使用一組分層的動(dòng)態(tài)配置 API 來(lái)進(jìn)行集中管理。這些層向 Envoy 提供了關(guān)于后端集群中的主機(jī)、后端集群自身、HTTP 路由、監(jiān)聽(tīng)套接字和加密材料的動(dòng)態(tài)更新。對(duì)于更簡(jiǎn)單的部署,可以通過(guò) DNS 解析(甚至完全跳過(guò))來(lái)完成后端主機(jī)發(fā)現(xiàn),并且進(jìn)一步的層可以由靜態(tài)配置文件替代。
健康檢查: 構(gòu)建 Envoy 網(wǎng)格的推薦方法是將服務(wù)發(fā)現(xiàn)視為最終一致的過(guò)程。Envoy 包含一個(gè)健康檢查子系統(tǒng),可以選擇對(duì)上游服務(wù)集群執(zhí)行主動(dòng)健康檢查。然后,Envoy 使用服務(wù)發(fā)現(xiàn)和健康檢查信息的結(jié)合來(lái)確定健康的負(fù)載均衡目標(biāo)。Envoy 還通過(guò)異常值檢測(cè)子系統(tǒng)支持被動(dòng)健康檢查。
高級(jí)負(fù)載均衡: 分布式系統(tǒng)中不同組件之間的負(fù)載均衡是一個(gè)復(fù)雜的問(wèn)題。由于 Envoy 是一個(gè)獨(dú)立的代理而不是庫(kù),因此可以獨(dú)立實(shí)現(xiàn)高級(jí)負(fù)載均衡以供任何應(yīng)用程序訪(fǎng)問(wèn)。目前 Envoy 支持自動(dòng)重試、熔斷、通過(guò)外部速率限制服務(wù)進(jìn)行全局速率限制、異常檢測(cè)等。
前端/邊緣代理支持: 在邊緣使用相同的軟件有很大的好處(可觀(guān)察性、管理、相同的服務(wù)發(fā)現(xiàn)和負(fù)載均衡算法等)。Envoy 的功能集使其非常適合作為大多數(shù)現(xiàn)代 Web 應(yīng)用程序用例的邊緣代理。這包括 TLS 終止、HTTP/1.1、HTTP/2 和 HTTP/3 支持以及 HTTP L7 路由。
最佳的可觀(guān)測(cè)性: 如上所述,Envoy 的主要目標(biāo)是使網(wǎng)絡(luò)透明化。但是,問(wèn)題在網(wǎng)絡(luò)層面和應(yīng)用層面都可能會(huì)出現(xiàn)。Envoy 為所有子系統(tǒng)提供了強(qiáng)大的統(tǒng)計(jì)支持。目前支持的統(tǒng)計(jì)數(shù)據(jù)輸出端是 statsd(以及兼容的提供程序),但是接入其他不同的統(tǒng)計(jì)數(shù)據(jù)輸出端并不困難。統(tǒng)計(jì)數(shù)據(jù)也可以通過(guò)管理端口進(jìn)行查看,Envoy 還支持通過(guò)第三方提供者進(jìn)行分布式跟蹤。
在我們介紹 Envoy 架構(gòu)之前,有必要先介紹一些常用的術(shù)語(yǔ)定義,因?yàn)檫@些術(shù)語(yǔ)貫穿整個(gè) Envoy 的架構(gòu)設(shè)計(jì)。
Envoy 采用單進(jìn)程多線(xiàn)程架構(gòu)。
一個(gè)獨(dú)立的 primary 線(xiàn)程負(fù)責(zé)控制各種零散的協(xié)調(diào)任務(wù),而一些 worker 線(xiàn)程則負(fù)責(zé)執(zhí)行監(jiān)聽(tīng)、過(guò)濾和轉(zhuǎn)發(fā)任務(wù)。
一旦偵聽(tīng)器接受連接,該連接就會(huì)將其生命周期綁定到一個(gè)單獨(dú)的 worker 線(xiàn)程。這使得 Envoy 的大部分工作基本上是單線(xiàn)程來(lái)處理的,只有少量更復(fù)雜的代碼處理工作線(xiàn)程之間的協(xié)調(diào)。
通常情況下 Envoy 實(shí)現(xiàn)了 100% 非阻塞。對(duì)于大多數(shù)工作負(fù)載,我們建議將 worker 線(xiàn)程的數(shù)量配置為機(jī)器上的硬件線(xiàn)程數(shù)量。
Envoy 整體架構(gòu)如下圖所示:
Envoy 架構(gòu)
Envoy 進(jìn)程中運(yùn)行著一系列 Inbound/Outbound 監(jiān)聽(tīng)器(Listener),Inbound 代理入站流量,Outbound 代理出站流量。Listener 的核心就是過(guò)濾器鏈(FilterChain),鏈中每個(gè)過(guò)濾器都能夠控制流量的處理流程。
Envoy 接收到請(qǐng)求后,會(huì)先走 FilterChain,通過(guò)各種 L3/L4/L7 Filter 對(duì)請(qǐng)求進(jìn)行處理,然后再路由到指定的集群,并通過(guò)負(fù)載均衡獲取一個(gè)目標(biāo)地址,最后再轉(zhuǎn)發(fā)出去。
其中每一個(gè)環(huán)節(jié)可以靜態(tài)配置,也可以動(dòng)態(tài)服務(wù)發(fā)現(xiàn),也就是所謂的 xDS,這里的 x 是一個(gè)代詞,是 lds、rds、cds、eds、sds 的總稱(chēng),即服務(wù)發(fā)現(xiàn),后 2 個(gè)字母 ds 就是 discovery service。
下面我們通過(guò)一個(gè)簡(jiǎn)單的示例來(lái)介紹 Envoy 的基本使用。
Envoy 使用 YAML 文件來(lái)控制代理的行為,整體配置結(jié)構(gòu)如下:
listen -- 監(jiān)聽(tīng)器 1.我監(jiān)聽(tīng)的地址 2.過(guò)濾鏈 filter1 路由: 轉(zhuǎn)發(fā)到哪里 virtual_hosts 只轉(zhuǎn)發(fā)什么 轉(zhuǎn)發(fā)到哪里 --> 由后面的 cluster 來(lái)定義 filter2 filter3 # envoyproxy.io/docs/envoy/v1.28.0/api-v3/config/filter/filtercluster 轉(zhuǎn)發(fā)規(guī)則 endpoints --指定了我的后端地址
接下來(lái)我們就來(lái)創(chuàng)建一個(gè)簡(jiǎn)單的 Envoy 代理,它監(jiān)聽(tīng) 10000 端口,將請(qǐng)求轉(zhuǎn)發(fā)到 www.baidu.com 的 80 端口。在下面的步驟中,我們將使用靜態(tài)配置接口來(lái)構(gòu)建配置,也意味著所有設(shè)置都是預(yù)定義在配置文件中的。此外 Envoy 也支持動(dòng)態(tài)配置,這樣可以通過(guò)外部一些源來(lái)自動(dòng)發(fā)現(xiàn)進(jìn)行設(shè)置。
Envoy 代理使用開(kāi)源 xDS API 來(lái)交換信息,目前 xDS v2 已被廢棄,最新版本的 Envoy 不再支持 xDS v2,建議使用 xDS v3。
創(chuàng)建一個(gè)名為 envoy-1.yaml 的文件,在 Envoy 配置的第一行定義正在使用的接口配置,在這里我們將配置靜態(tài) API,因此第一行應(yīng)為 static_resources:
static_resources:
然后需要在靜態(tài)配置下面定義 Envoy 的監(jiān)聽(tīng)器(Listener),監(jiān)聽(tīng)器是 Envoy 監(jiān)聽(tīng)請(qǐng)求的網(wǎng)絡(luò)配置,例如 IP 地址和端口。我們這里設(shè)置監(jiān)聽(tīng) IP 地址為 0.0.0.0,并在端口 10000 上進(jìn)行監(jiān)聽(tīng)。對(duì)應(yīng)的監(jiān)聽(tīng)器的配置為
static_resources: listeners: - name: listener_0 # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 10000 # 監(jiān)聽(tīng)器的端口
通過(guò) Envoy 監(jiān)聽(tīng)傳入的流量,下一步是定義如何處理這些請(qǐng)求。每個(gè)監(jiān)聽(tīng)器都有一組過(guò)濾器,并且不同的監(jiān)聽(tīng)器可以具有一組不同的過(guò)濾器。
在我們這個(gè)示例中,我們將所有流量代理到 baidu.com,配置完成后我們應(yīng)該能夠通過(guò)請(qǐng)求 Envoy 的端點(diǎn)就可以直接看到百度的主頁(yè)了,而無(wú)需更改 URL 地址。
過(guò)濾器是通過(guò) filter_chains 來(lái)定義的,每個(gè)過(guò)濾器的目的是找到傳入請(qǐng)求的匹配項(xiàng),以使其與目標(biāo)地址進(jìn)行匹配。Filter 過(guò)濾器的寫(xiě)法如下所示:
name: 指定使用哪個(gè)過(guò)濾器typed_config: "@type": type.googleapis.com/envoy.過(guò)濾器的具體值 參數(shù)1:值1 參數(shù)2:值2 。。。這里選擇什么參數(shù),要看name里選擇的什么參數(shù)要根據(jù)所選擇的過(guò)濾器來(lái)判定和 http 相關(guān)的,一般選擇 HTTP connection manager。在 https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/filter/filter 里找參數(shù)name 的位置應(yīng)該寫(xiě) envoy.filterswork.http_connection_manager@type 的值到文檔里找具體的值
比如我們這里的配置如下所示:
static_resources: listeners: - name: listener_0 # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 10000 # 監(jiān)聽(tīng)器的端口 filter_chains: # 配置過(guò)濾器鏈 # 在此地址收到的任何請(qǐng)求都會(huì)通過(guò)這一系列過(guò)濾鏈發(fā)送。 - filters: # 指定要使用哪個(gè)過(guò)濾器,下面是envoy內(nèi)置的網(wǎng)絡(luò)過(guò)濾器,如果請(qǐng)求是 HTTP 它將通過(guò)此 HTTP 過(guò)濾器 # 該過(guò)濾器將原始字節(jié)轉(zhuǎn)換為HTTP級(jí)別的消息和事件(例如接收到的header、接收到的正文數(shù)據(jù)等) # 它還處理所有HTTP連接和請(qǐng)求中常見(jiàn)的功能,例如訪(fǎng)問(wèn)日志記錄、請(qǐng)求ID生成和跟蹤、請(qǐng)求/響應(yīng)頭操作、路由表管理和統(tǒng)計(jì)信息。 - name: envoy.filterswork.http_connection_manager typed_config: # 需要配置下面的類(lèi)型,啟用 http_connection_manager "@type": type.googleapis.com/envoy.extensions.filterswork.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http access_log: # 連接管理器發(fā)出的 HTTP 訪(fǎng)問(wèn)日志的配置 - name: envoy.access_loggers.stdout # 輸出到stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog http_filters: # 定義http過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] # 要匹配的主機(jī)名列表,*表示匹配所有主機(jī) routes: - match: prefix: "/" # 要匹配的 URL 前綴 route: # 路由規(guī)則,發(fā)送請(qǐng)求到 service_baidu 集群 host_rewrite_literal: www.baidu.com # 更改 HTTP 請(qǐng)求的入站 Host 頭信息 cluster: service_baidu # 將要處理請(qǐng)求的集群名稱(chēng),下面會(huì)有相應(yīng)的實(shí)現(xiàn)
這里我們使用的過(guò)濾器使用了 envoy.filterswork.http_connection_manager,這是為 HTTP 連接設(shè)計(jì)的一個(gè)內(nèi)置過(guò)濾器,該過(guò)濾器將原始字節(jié)轉(zhuǎn)換為 HTTP 級(jí)別的消息和事件(例如接收到的 header、接收到的正文數(shù)據(jù)等),它還處理所有 HTTP 連接和請(qǐng)求中常見(jiàn)的功能,例如訪(fǎng)問(wèn)日志記錄、請(qǐng)求 ID 生成和跟蹤、請(qǐng)求/響應(yīng)頭操作、路由表管理和統(tǒng)計(jì)信息。
當(dāng)請(qǐng)求于過(guò)濾器匹配時(shí),該請(qǐng)求將會(huì)傳遞到集群。下面的配置就是將主機(jī)定義為訪(fǎng)問(wèn) HTTPS 的 baidu.com 域名,如果定義了多個(gè)主機(jī),則 Envoy 將執(zhí)行輪詢(xún)(Round Robin)策略。配置如下所示:
clusters: - name: service_baidu # 集群的名稱(chēng),與上面的 router 中的 cluster 對(duì)應(yīng) type: LOGICAL_DNS # 用于解析集群(生成集群端點(diǎn))時(shí)使用的服務(wù)發(fā)現(xiàn)類(lèi)型,可用值有STATIC、STRICT_DNS 、LOGICAL_DNS、ORIGINAL_DST和EDS等; connect_timeout: 0.25s dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN # 負(fù)載均衡算法,支持ROUND_ROBIN、LEAST_REQUEST、RING_HASH、RANDOM、MAGLEV和CLUSTER_PROVIDED; load_assignment: # 以前的 v2 版本的 hosts 字段廢棄了,現(xiàn)在使用 load_assignment 來(lái)定義集群的成員,指定 STATIC、STRICT_DNS 或 LOGICAL_DNS 集群的成員需要設(shè)置此項(xiàng)。 cluster_name: service_baidu # 集群的名稱(chēng) endpoints: # 需要進(jìn)行負(fù)載均衡的端點(diǎn)列表 - lb_endpoints: - endpoint: address: socket_address: address: www.baidu.com port_value: 443 transport_socket: # 用于與上游集群通信的傳輸層配置 name: envoy.transport_sockets.tls # tls 傳輸層 typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.baidu.com
最后,還需要配置一個(gè)管理模塊:
admin: access_log_path: /tmp/admin_access.log address: socket_address: address: 0.0.0.0 port_value: 9901
上面的配置定義了 Envoy 的靜態(tài)配置模板,監(jiān)聽(tīng)器定義了 Envoy 的端口和 IP 地址,監(jiān)聽(tīng)器具有一組過(guò)濾器來(lái)匹配傳入的請(qǐng)求,匹配請(qǐng)求后,將請(qǐng)求轉(zhuǎn)發(fā)到集群,完整的配置如下所示:
# envoy-1.yamladmin: access_log_path: /tmp/admin_access.log address: socket_address: address: 0.0.0.0 port_value: 9901static_resources: listeners: - name: listener_0 # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 10000 # 監(jiān)聽(tīng)器的端口 filter_chains: # 配置過(guò)濾器鏈 - filters: # 過(guò)濾器配置的名稱(chēng),要填寫(xiě) typed_config 配置的過(guò)濾器指定的名稱(chēng) - name: envoy.filterswork.http_connection_manager typed_config: # 啟用 http_connection_manager "@type": type.googleapis.com/envoy.extensions.filterswork.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過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: host_rewrite_literal: www.baidu.com cluster: service_baidu clusters: - name: service_baidu # 集群的名稱(chēng) type: LOGICAL_DNS # 用于解析集群(生成集群端點(diǎn))時(shí)使用的服務(wù)發(fā)現(xiàn)類(lèi)型,可用值有STATIC、STRICT_DNS 、LOGICAL_DNS、ORIGINAL_DST和EDS等; connect_timeout: 0.25s dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN # 負(fù)載均衡算法,支持ROUND_ROBIN、LEAST_REQUEST、RING_HASH、RANDOM、MAGLEV和CLUSTER_PROVIDED; load_assignment: cluster_name: service_baidu endpoints: - lb_endpoints: - endpoint: address: socket_address: address: www.baidu.com port_value: 443 transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext sni: www.baidu.com
第一次使用 Envoy,可能會(huì)覺(jué)得它的配置太復(fù)雜了,讓人眼花繚亂。其實(shí)只要我們理解了網(wǎng)絡(luò)代理程序的流程就不難配置了,比如作為一個(gè)代理,首先要能獲取請(qǐng)求流量,通常是采用監(jiān)聽(tīng)端口的方式實(shí)現(xiàn);其次拿到請(qǐng)求數(shù)據(jù)后需要對(duì)其做微處理,例如附加 Header 或校驗(yàn)?zāi)硞€(gè) Header 字段的內(nèi)容等,這里針對(duì)來(lái)源數(shù)據(jù)的層次不同,可以分為 L3/L4/L7,然后將請(qǐng)求轉(zhuǎn)發(fā)出去;轉(zhuǎn)發(fā)這里又可以衍生出如果后端是一個(gè)集群,需要從中挑選一臺(tái)機(jī)器,如何挑選又涉及到負(fù)載均衡等。
腦補(bǔ)完大致流程后,再來(lái)看 Envoy 是如何組織配置信息的,我們?cè)賮?lái)解釋一下其中的關(guān)鍵字段。
結(jié)合關(guān)鍵字段和上面的腦補(bǔ)流程,可以看出 Envoy 的大致處理流程如下:
Envoy 配置流程
Envoy 內(nèi)部對(duì)請(qǐng)求的處理流程其實(shí)跟我們上面腦補(bǔ)的流程大致相同,即對(duì)請(qǐng)求的處理流程基本是不變的,而對(duì)于變化的部分,即對(duì)請(qǐng)求數(shù)據(jù)的微處理,全部抽象為 Filter,例如對(duì)請(qǐng)求的讀寫(xiě)是 ReadFilter、WriteFilter,對(duì) HTTP 請(qǐng)求數(shù)據(jù)的編解碼是 StreamEncoderFilter、StreamDecoderFilter,對(duì) TCP 的處理是 TcpProxyFilter,其繼承自 ReadFilter,對(duì) HTTP 的處理是 ConnectionManager,其也是繼承自 ReadFilter 等等,各個(gè) Filter 最終會(huì)組織成一個(gè) FilterChain,在收到請(qǐng)求后首先走 FilterChain,其次路由到指定集群并做負(fù)載均衡獲取一個(gè)目標(biāo)地址,然后轉(zhuǎn)發(fā)出去。
配置完成后,我們就可以去啟動(dòng) Envoy 了,首先當(dāng)然需要去安裝 Envoy 了,因?yàn)?Envoy 是 C++ 開(kāi)發(fā)的,編譯起來(lái)非常麻煩,如果是 Mac 用戶(hù)可以使用 brew install envoy 來(lái)一鍵安裝,但是最簡(jiǎn)單的方式還是使用 Docker 來(lái)啟動(dòng) Envoy。
我們這里也通過(guò) Docker 容器來(lái)啟動(dòng) Envoy,將上面的配置文件通過(guò) Volume 掛載到容器中的 /etc/envoy/envoy.yaml 去。
然后使用以下命令啟動(dòng)綁定到端口 80 的 Envoy 容器:
$ docker run --name=envoy -d / -p 80:10000 / -v $(pwd)/manifests/2.Envoy/envoy-1.yaml:/etc/envoy/envoy.yaml / envoyproxy/envoy:v1.28.0
啟動(dòng)后,我們可以在本地的 80 端口上去訪(fǎng)問(wèn)應(yīng)用 curl localhost 來(lái)測(cè)試代理是否成功。同樣我們也可以通過(guò)在本地瀏覽器中訪(fǎng)問(wèn) localhost 來(lái)查看:
localhost
可以看到請(qǐng)求被代理到了 baidu.com,而且應(yīng)該也可以看到 URL 地址沒(méi)有變化,還是 localhost,查看 Envoy 日志可以看到如下信息:
[2023-10-25T06:53:50.003Z] "GET / HTTP/1.1" 200 - 0 103079 399 235 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" "e081fa5b-31a4-4285-92d9-b8a8c896f2d4" "www.baidu.com" "110.242.68.3:443"[2023-10-25T06:53:50.819Z] "GET /sugrec?&prod=pc_his&from=pc_web&jsnotallow=1&sid=&hisdata=%5B%7B%22time%22%3A1698206660%2C%22kw%22%3A%22envovy%20typed_config%22%2C%22fq%22%3A2%7D%5D&_t=1698216830777&req=2&csor=0 HTTP/1.1" 200 - 0 155 57 57 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" "9a9351e7-e7ef-4fa8-9aec-fba96600e4df" "www.baidu.com" "110.242.68.3:443"
此外 Envoy 還提供了一個(gè)管理視圖,可以讓我們?nèi)ゲ榭磁渲谩⒔y(tǒng)計(jì)信息、日志以及其他 Envoy 內(nèi)部的一些數(shù)據(jù)。上面我們定義的管理視圖的端口為 9901,當(dāng)然我們也可以通過(guò) Docker 容器將管理端口暴露給外部用戶(hù)。
docker run --name=envoy -d / -p 9901:9901 / -p 80:10000 / -v $(pwd)/manifests/2.Envoy/envoy-1.yaml:/etc/envoy/envoy.yaml / envoyproxy/envoy:v1.28.0
上面的配置就會(huì)將管理頁(yè)面暴露給外部用戶(hù),當(dāng)然我們這里僅僅用于演示是可以的,如果你是用于線(xiàn)上環(huán)境還需要做好一些安全保護(hù)措施。運(yùn)行成功后,現(xiàn)在我們可以在瀏覽器里面輸入 localhost:9901 來(lái)訪(fǎng)問(wèn) Envoy 的管理頁(yè)面:
envoy admin
需要注意的是當(dāng)前的管理頁(yè)面不僅允許執(zhí)行一些破壞性的操作(比如,關(guān)閉服務(wù)),而且還可能暴露一些私有信息(比如統(tǒng)計(jì)信息、集群名稱(chēng)、證書(shū)信息等)。所以應(yīng)該只允許通過(guò)安全網(wǎng)絡(luò)去訪(fǎng)問(wèn)管理頁(yè)面。
因?yàn)楝F(xiàn)階段大部分的應(yīng)用可能還是使用的比較傳統(tǒng)的 Nginx 來(lái)做服務(wù)代理,為了對(duì)比 Envoy 和 Nginx 的區(qū)別,我們這里將來(lái)嘗試將 Nginx 的配置遷移到 Envoy 上來(lái),這樣也有助于我們?nèi)チ私?Envoy 的配置。
首先我們使用 Nginx 官方 Wiki 的完整示例來(lái)進(jìn)行說(shuō)明,完整的 nginx.conf 配置如下所示:
user www www;pid /var/run/nginx.pid;worker_processes 2;events { worker_connections 2000;}http { gzip on; gzip_min_length 1100; gzip_buffers 4 8k; gzip_types text/plain; log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$gzip_ratio"'; log_format download '$remote_addr - $remote_user [$time_local] ' '"$request" $status $bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$http_range" "$sent_http_content_range"'; upstream targetCluster { 192.168.215.3:80; 192.168.215.4:80; } server { listen 8080; server_name one.example.com www.one.example.com; access_log /var/log/nginx.access_log main; error_log /var/log/nginx.error_log info; location / { proxy_pass http://targetCluster/; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }}
上面的 Nginx 配置有 3 個(gè)核心配置:
并不是所有的 Nginx 的配置都適用于 Envoy,有些方面的配置我們可以不用。Envoy 代理主要有 4 中主要的配置類(lèi)型,它們是支持 Nginx 提供的核心基礎(chǔ)結(jié)構(gòu)的:
我們將使用這 4 個(gè)組件來(lái)創(chuàng)建 Envoy 代理配置,去匹配 Nginx 中的配置。Envoy 的重點(diǎn)一直是在 API 和動(dòng)態(tài)配置上,但是我們這里仍然使用靜態(tài)配置。
Nginx 配置的核心是 HTTP 配置配置,里面包含了:
我們可以通過(guò) Envoy 代理中的過(guò)濾器來(lái)配置這些內(nèi)容。在 HTTP 配置部分,Nginx 配置指定了監(jiān)聽(tīng)的端口 8080,并響應(yīng)域名 one.example.com 和 www.one.example.com 的傳入請(qǐng)求:
server { listen 8080; server_name one.example.com www.one.example.com; ......}
在 Envoy 中,這部分就是監(jiān)聽(tīng)器來(lái)管理的。開(kāi)始一個(gè) Envoy 代理最重要的方面就是定義監(jiān)聽(tīng)器,我們需要?jiǎng)?chuàng)建一個(gè)配置文件來(lái)描述我們?nèi)绾稳ミ\(yùn)行 Envoy 實(shí)例。
這里我們定義一個(gè) static_resources 配置,它是 Envoy 配置的根節(jié)點(diǎn),它包含了所有的靜態(tài)配置,包括監(jiān)聽(tīng)器、集群、路由等。我們將創(chuàng)建一個(gè)新的監(jiān)聽(tīng)器并將其綁定到 8080 端口上,該配置指示了 Envoy 代理用于接收網(wǎng)絡(luò)請(qǐng)求的端口,如下所示:
static_resources: listeners: - name: listener_0 # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 8080 # 監(jiān)聽(tīng)器的端口
需要注意的是我們沒(méi)有在監(jiān)聽(tīng)器部分定義 server_name,這需要在過(guò)濾器部分進(jìn)行處理。
當(dāng)請(qǐng)求進(jìn)入 Nginx 時(shí),location 部分定義了如何處理流量以及在什么地方轉(zhuǎn)發(fā)流量。在下面的配置中,站點(diǎn)的所有傳入流量都將被代理到一個(gè)名為 targetCluster 的上游(upstream)集群,上游集群定義了處理請(qǐng)求的節(jié)點(diǎn)。
location / { proxy_pass http://targetCluster/; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;}
在 Envoy 中,這部分將由過(guò)濾器來(lái)進(jìn)行配置管理。在靜態(tài)配置中,過(guò)濾器定義了如何處理傳入的請(qǐng)求,在我們這里,將配置一個(gè)過(guò)濾器去匹配上一步中的 server_names,當(dāng)接收到與定義的域名和路由匹配的傳入請(qǐng)求時(shí),流量將轉(zhuǎn)發(fā)到集群,集群和 Nginx 配置中的 upstream 是一致的。
filter_chains: - filters: - name: envoy.filterswork.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filterswork.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http http_filters: # 定義http過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: backend domains: - "one.example.com" - "www.one.example.com" routes: - match: prefix: "/" route: cluster: targetCluster
其中 envoy.filterswork.http_connection_manager 是 Envoy 內(nèi)置的一個(gè)過(guò)濾器,用于處理 HTTP 連接的,除此之外,還有其他的一些內(nèi)置的過(guò)濾器,比如 Redis、Mongo、TCP。
在 Nginx 中,upstream(上游)配置定義了處理請(qǐng)求的目標(biāo)服務(wù)器集群,在我們這里的示例中,分配了兩個(gè)集群。
upstream targetCluster { 192.168.215.3:80; 192.168.215.4:80;}
在 Envoy 代理中,這部分是通過(guò) clusters 進(jìn)行配置管理的。upstream 等同與 Envoy 中的 clusters 定義,我們這里通過(guò)集群定義了主機(jī)被訪(fǎng)問(wèn)的方式,還可以配置超時(shí)和負(fù)載均衡等方面更精細(xì)的控制。
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
上面我們配置了 STRICT_DNS 類(lèi)型的服務(wù)發(fā)現(xiàn),Envoy 會(huì)持續(xù)異步地解析指定的 DNS 目標(biāo)。DNS 解析結(jié)果返回的每個(gè) IP 地址都將被視為上游集群的主機(jī)。所以如果返回兩個(gè) IP 地址,則 Envoy 將認(rèn)為集群有兩個(gè)主機(jī),并且兩個(gè)主機(jī)都應(yīng)進(jìn)行負(fù)載均衡,如果從結(jié)果中刪除了一個(gè)主機(jī),則 Envoy 會(huì)從現(xiàn)有的連接池中將其剔出掉。
最后需要配置的日志部分,Envoy 采用云原生的方式,將應(yīng)用程序日志都輸出到 stdout 和 stderr,而不是將錯(cuò)誤日志輸出到磁盤(pán)。
當(dāng)用戶(hù)發(fā)起一個(gè)請(qǐng)求時(shí),訪(fǎng)問(wèn)日志默認(rèn)是被禁用的,我們可以手動(dòng)開(kāi)啟。要為 HTTP 請(qǐng)求開(kāi)啟訪(fǎng)問(wèn)日志,需要在 HTTP 連接管理器中包含一個(gè) access_log 的配置,該路徑可以是設(shè)備,比如 stdout,也可以是磁盤(pán)上的某個(gè)文件,這依賴(lài)于我們自己的實(shí)際情況。
下面過(guò)濾器中的配置就會(huì)將所有訪(fǎng)問(wèn)日志通過(guò)管理傳輸?shù)?nbsp;stdout:
- name: envoy.filterswork.http_connection_manager typed_config: # 啟用 http_connection_manager "@type": type.googleapis.com/envoy.extensions.filterswork.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過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: # ......
默認(rèn)情況下,Envoy 訪(fǎng)問(wèn)日志格式包含整個(gè) HTTP 請(qǐng)求的詳細(xì)信息:
[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%"%RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION%%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% "%REQ(X-FORWARDED-FOR)%" "%REQ(USER-AGENT)%""%REQ(X-REQUEST-ID)%" "%REQ(:AUTHORITY)%" "%UPSTREAM_HOST%"/n
輸出結(jié)果格式化后如下所示:
[2023-10-25T07:25:09.826Z] "GET / HTTP/1.1" 200 - 0 102931 361 210 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36" "6e19ddda-e0a1-41f9-9355-ea5db8d23bcc" "one.example.com" "192.168.215.4:80"
我們也可以通過(guò)設(shè)置 format 字段來(lái)自定義輸出日志的格式,例如:
access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog log_format: text_format: "[%START_TIME%] %REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL% %RESPONSE_CODE% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% %REQ(X-REQUEST-ID)% %REQ(:AUTHORITY)% %UPSTREAM_HOST%/n"
此外我們也可以通過(guò)設(shè)置 json_format 字段來(lái)將日志作為 JSON 格式輸出,例如:
access_log: - name: envoy.access_loggers.stdout typed_config: "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog log_format: json_format: { "protocol": "%PROTOCOL%", "duration": "%DURATION%", "request_method": "%REQ(:METHOD)%", }
要注意的是,訪(fǎng)問(wèn)日志會(huì)在未設(shè)置、或者空值的位置加入一個(gè)字符:-。不同類(lèi)型的訪(fǎng)問(wèn)日志(例如 HTTP 和 TCP)共用同樣的格式字符串。不同類(lèi)型的日志中,某些字段可能會(huì)有不同的含義。有關(guān) Envoy 日志的更多信息,可以查看官方文檔對(duì)應(yīng)的說(shuō)明。當(dāng)然日志并不是 Envoy 代理獲得請(qǐng)求可見(jiàn)性的唯一方法,Envoy 還內(nèi)置了高級(jí)跟蹤和指標(biāo)功能。
最后我們完整的 Envoy 配置如下所示:
# envoy-2.yamlstatic_resources: listeners: - name: listener_0 # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 8080 # 監(jiān)聽(tīng)器的端口 filter_chains: - filters: - name: envoy.filterswork.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filterswork.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過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: backend domains: - "one.example.com" - "www.one.example.com" routes: - match: prefix: "/" route: cluster: targetCluster 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
現(xiàn)在我們已經(jīng)將 Nginx 配置轉(zhuǎn)換為了 Envoy 代理,接下來(lái)我們可以來(lái)啟動(dòng) Envoy 代理進(jìn)行測(cè)試驗(yàn)證。
在 Nginx 配置的頂部,有一行配置 user www www;,表示用非 root 用戶(hù)來(lái)運(yùn)行 Nginx 以提高安全性。而 Envoy 代理采用云原生的方法來(lái)管理使用這,我們通過(guò)容器啟動(dòng) Envoy 代理的時(shí)候,可以指定一個(gè)低特權(quán)的用戶(hù)。
下面的命令將通過(guò) Docker 容器來(lái)啟動(dòng)一個(gè) Envoy 實(shí)例,該命令使 Envoy 可以監(jiān)聽(tīng) 80 端口上的流量請(qǐng)求,但是我們?cè)?Envoy 的監(jiān)聽(tīng)器配置中指定的是 8080 端口,所以我們用一個(gè)低特權(quán)用戶(hù)身份來(lái)運(yùn)行:
$ docker run --name proxy1 -p 80:8080 --user 1000:1000 -v $(pwd)/manifests/2.Envoy/envoy-2.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy:v1.28.0
啟動(dòng)代理后,就可以開(kāi)始測(cè)試了,下面我們用 curl 命令使用代理配置的 host 頭發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求:
$ curl -H "Host: one.example.com" localhost -iHTTP/1.1 503 Service Unavailablecontent-length: 91content-type: text/plaindate: Wed, 25 Oct 2023 07:37:55 GMTserver: envoyupstream connect error or disconnect/reset before headers. reset reason: connection timeout%
我們可以看到會(huì)出現(xiàn) 503 錯(cuò)誤,這是因?yàn)槲覀兣渲玫纳嫌渭褐鳈C(jī)根本就沒(méi)有運(yùn)行,所以 Envoy 代理請(qǐng)求到不可用的主機(jī)上去了,就出現(xiàn)了這樣的錯(cuò)誤。我們可以使用下面的命令啟動(dòng)兩個(gè) HTTP 服務(wù),用來(lái)表示上游主機(jī):
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES3ecf1125bd0c cnych/docker-http-server "/app" 2 minutes ago Up 2 minutes 80/tcp loving_babbage0195f14ec57a cnych/docker-http-server "/app" 2 minutes ago Up 2 minutes 80/tcp heuristic_torvaldsa55f6175c5c7 envoyproxy/envoy:v1.28.0 "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 10000/tcp, 0.0.0.0:80->8080/tcp, :::80->8080/tcp proxy1
當(dāng)上面兩個(gè)服務(wù)啟動(dòng)成功后,現(xiàn)在我們?cè)偻ㄟ^(guò) Envoy 去訪(fǎng)問(wèn)目標(biāo)服務(wù)就正常了:
$ curl -H "Host: one.example.com" localhost -iHTTP/1.1 200 OKdate: Wed, 25 Oct 2023 07:42:51 GMTcontent-length: 58content-type: text/html; charset=utf-8x-envoy-upstream-service-time: 1server: envoy<h1>This request was processed by host: 3ecf1125bd0c</h1>$ curl -H "Host: one.example.com" localhost -iHTTP/1.1 200 OKdate: Wed, 25 Oct 2023 07:42:53 GMTcontent-length: 58content-type: text/html; charset=utf-8x-envoy-upstream-service-time: 8server: envoy<h1>This request was processed by host: 0195f14ec57a</h1>
當(dāng)訪(fǎng)問(wèn)請(qǐng)求的時(shí)候,我們可以看到是哪個(gè)容器處理了請(qǐng)求,在 Envoy 代理容器中,也可以看到請(qǐng)求的日志輸出:
[2023-10-25T07:42:51.297Z] "GET / HTTP/1.1" 200 - 0 58 3 1 "-" "curl/7.87.0" "ff1e1009-d5a3-4a71-87ef-479e234c9858" "one.example.com" "192.168.215.4:80"[2023-10-25T07:42:53.153Z] "GET / HTTP/1.1" 200 - 0 58 9 8 "-" "curl/7.87.0" "d0ebdd10-a1d2-406e-8c6e-6fd8451aded3" "one.example.com" "192.168.215.3:80"
到這里我們就完成了將 Nginx 配置遷移到 Envoy 的過(guò)程,可以看到 Envoy 的配置和 Nginx 的配置還是有很大的區(qū)別的,但是我們可以看到 Envoy 的配置更加的靈活,而且 Envoy 代理的配置是可以動(dòng)態(tài)更新的,這樣就可以實(shí)現(xiàn)無(wú)縫的服務(wù)升級(jí)。
接下來(lái)我們將來(lái)了解下如何使用 Envoy 保護(hù) HTTP 網(wǎng)絡(luò)請(qǐng)求。確保 HTTP 流量安全對(duì)于保護(hù)用戶(hù)隱私和數(shù)據(jù)是至關(guān)重要的。下面我們來(lái)了解下如何在 Envoy 中配置 SSL 證書(shū)。
這里我們將為 example.com 域名生成一個(gè)自簽名的證書(shū),當(dāng)然如果在生產(chǎn)環(huán)境時(shí)候,需要使用正規(guī) CA 機(jī)構(gòu)購(gòu)買(mǎi)的證書(shū),或者使用 Let's Encrypt 的免費(fèi)證書(shū)服務(wù)。
下面的命令會(huì)在目錄 certs/ 中創(chuàng)建一個(gè)新的證書(shū)和密鑰:
$ mkdir certs; cd certs;$ openssl req -nodes -new -x509 / -keyout example-com.key -out example-com.crt / -days 365 / -subj '/CN=example.com/O=youdianzhishi.com/C=CN'; Generating a RSA private key....+..+....+..+.......+.....+...+....+++++++++++++++++++++++++++++++++++++++*....+...+................+......+........+...+...+.+...+......+.....+......+.+.....+++++++++++++++++++++++++++++++++++++++*............+.....+................+....................+......+....+..............+.+..+...+...............+.......+............+...+...+......+.....+....+..+....+.........+....................+.+......+..+...+.........+.+..+..........+...............+..+...+.+......+...+..+......+.......+............+......+..+......+......++++++.....+............+.+..+....+.....+.+.........+.....+++++++++++++++++++++++++++++++++++++++*...+...+............+.....+.+...........+...+...+...............+...+......+.........+.+.....+...+.+......+...+...+++++++++++++++++++++++++++++++++++++++*........+..........+...+...+......+.........++++++-----$ cd -
在 Envoy 中保護(hù) HTTP 流量,需要通過(guò)添加 transport_socket 過(guò)濾器,該過(guò)濾器提供了為 Envoy 代理中配置的域名指定證書(shū)的功能,請(qǐng)求 HTTPS 請(qǐng)求時(shí)候,就使用匹配的證書(shū)。我們這里直接使用上一步中生成的自簽名證書(shū)即可。
我們這里的 Envoy 配置文件中包含了所需的 HTTPS 支持的配置,我們添加了兩個(gè)監(jiān)聽(tīng)器,一個(gè)監(jiān)聽(tīng)器在 8080 端口上用于 HTTP 通信,另外一個(gè)監(jiān)聽(tīng)器在 8443 端口上用于 HTTPS 通信。
在 HTTPS 監(jiān)聽(tīng)器中定義了 HTTP 連接管理器,該代理將代理 /service/1 和 /service/2 這兩個(gè)端點(diǎn)的傳入請(qǐng)求,這里我們需要通過(guò) envoy.transport_sockets.tls 配置相關(guān)證書(shū),如下所示:
transport_socket: name: envoy.transport_sockets.tls typed_config: # 一個(gè)監(jiān)聽(tīng)傳輸套接字,用于使用 TLS 接受下游連接(客戶(hù)端)。 "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: filename: "/etc/certs/example-com.crt" private_key: filename: "/etc/certs/example-com.key"
在 TLS 上下文中定義了生成的證書(shū)和密鑰,如果我們有多個(gè)域名,每個(gè)域名都有自己的證書(shū),則需要通過(guò) tls_certificates 定義多個(gè)證書(shū)鏈。
自動(dòng)跳轉(zhuǎn)
定義了 TLS 上下文后,該站點(diǎn)將能夠通過(guò) HTTPS 提供流量了,但是如果用戶(hù)是通過(guò) HTTP 來(lái)訪(fǎng)問(wèn)的服務(wù),為了確保安全,我們可以將其重定向到 HTTPS 版本服務(wù)上去。
在 HTTP 配置中,我們將 https_redirect: true 的標(biāo)志添加到過(guò)濾器的配置中即可實(shí)現(xiàn)跳轉(zhuǎn)功能。
route_config: virtual_hosts: - name: backend domains: - "example.com" routes: - match: prefix: "/" redirect: path_redirect: "/" https_redirect: true
當(dāng)用戶(hù)訪(fǎng)問(wèn)網(wǎng)站的 HTTP 版本時(shí),Envoy 代理將根據(jù)過(guò)濾器配置來(lái)匹配域名和路徑,匹配到過(guò)后將請(qǐng)求重定向到站點(diǎn)的 HTTPS 版本去。完整的 Envoy 配置如下所示:
# envoy-3.yamladmin: access_log_path: /tmp/admin_access.log address: socket_address: address: 0.0.0.0 port_value: 8001static_resources: listeners: - name: listener_http # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 8080 # 監(jiān)聽(tīng)器的端口 filter_chains: - filters: - name: envoy.filterswork.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filterswork.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過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: backend domains: - "example.com" routes: - match: prefix: "/" redirect: path_redirect: "/" https_redirect: true - name: listener_https # 監(jiān)聽(tīng)器的名稱(chēng) address: socket_address: address: 0.0.0.0 # 監(jiān)聽(tīng)器的地址 port_value: 8443 # 監(jiān)聽(tīng)器的端口 filter_chains: - transport_socket: name: envoy.transport_sockets.tls typed_config: # 一個(gè)監(jiān)聽(tīng)傳輸套接字,用于使用 TLS 接受下游連接(客戶(hù)端)。 "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext common_tls_context: tls_certificates: - certificate_chain: filename: "/etc/certs/example-com.crt" private_key: filename: "/etc/certs/example-com.key" filters: - name: envoy.filterswork.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filterswork.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過(guò)濾器鏈 - name: envoy.filters.http.router # 調(diào)用7層的路由過(guò)濾器 typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router route_config: name: local_route virtual_hosts: - name: backend domains: - "example.com" routes: - match: prefix: "/service/1" route: cluster: service1 - match: prefix: "/service/2" route: cluster: service2 clusters: - name: service1 connect_timeout: 0.25s type: STRICT_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: service1 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 192.168.215.3 port_value: 80 - name: service2 connect_timeout: 0.25s type: STRICT_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: service2 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 192.168.215.4 port_value: 80
現(xiàn)在配置已經(jīng)完成了,我們就可以啟動(dòng) Envoy 實(shí)例來(lái)進(jìn)行測(cè)試了。在我們這個(gè)示例中,Envoy 暴露 80 端口來(lái)處理 HTTP 請(qǐng)求,暴露 443 端口來(lái)處理 HTTPS 請(qǐng)求,此外還在 8001 端口上暴露了管理頁(yè)面,我們可以通過(guò)管理頁(yè)面查看有關(guān)證書(shū)的信息。
使用如下命令啟動(dòng) Envoy 代理:
$ docker run -it --name tls-proxy -p 80:8080 -p 443:8443 -p 8001:8001 -v $(pwd)/manifests/2.Envoy/certs:/etc/certs/ -v $(pwd)/manifests/2.Envoy/envoy-3.yaml:/etc/envoy/envoy.yaml envoyproxy/envoy:v1.28.0
啟動(dòng)完成后所有的 HTTPS 和 TLS 校驗(yàn)都是通過(guò) Envoy 來(lái)進(jìn)行處理的,所以我們不需要去修改應(yīng)該程序。同樣我們啟動(dòng)兩個(gè) HTTP 服務(wù)來(lái)處理傳入的請(qǐng)求:
$ docker run -d cnych/docker-http-server; docker run -d cnych/docker-http-server;$ docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES1690f6562870 cnych/docker-http-server "/app" 32 seconds ago Up 32 seconds 80/tcp distracted_ridef86925657e62 cnych/docker-http-server "/app" 32 seconds ago Up 32 seconds 80/tcp festive_almeidad02e37b26b22 envoyproxy/envoy:v1.28.0 "/docker-entrypoint.…" 3 minutes ago Up 3 minutes 0.0.0.0:8001->8001/tcp, :::8001->8001/tcp, 10000/tcp, 0.0.0.0:80->8080/tcp, :::80->8080/tcp, 0.0.0.0:443->8443/tcp, :::443->8443/tcp tls-proxy
上面的幾個(gè)容器啟動(dòng)完成后,就可以進(jìn)行測(cè)試了,首先我們請(qǐng)求 HTTP 的服務(wù),由于配置了自動(dòng)跳轉(zhuǎn),所以應(yīng)該會(huì)被重定向到 HTTPS 的版本上去:
$ curl -H "Host: example.com" http://localhost -iHTTP/1.1 301 Moved Permanentlylocation: https://example.com/date: Wed, 25 Oct 2023 08:30:48 GMTserver: envoycontent-length: 0
我們可以看到上面有 HTTP/1.1 301 Moved Permanently 這樣的重定向響應(yīng)信息。然后我們嘗試直接請(qǐng)求 HTTPS 的服務(wù):
$ curl -k -H "Host: example.com" https://localhost/service/1 -iHTTP/1.1 200 OKdate: Wed, 25 Oct 2023 08:31:17 GMTcontent-length: 58content-type: text/html; charset=utf-8x-envoy-upstream-service-time: 24server: envoy<h1>This request was processed by host: f86925657e62</h1>$ curl -k -H "Host: example.com" https://localhost/service/2 -iHTTP/1.1 200 OKdate: Wed, 25 Oct 2023 08:31:28 GMTcontent-length: 58content-type: text/html; charset=utf-8x-envoy-upstream-service-time: 22server: envoy<h1>This request was processed by host: 1690f6562870</h1>
我們可以看到通過(guò) HTTPS 進(jìn)行訪(fǎng)問(wèn)可以正常得到對(duì)應(yīng)的響應(yīng),需要注意的是由于我們這里使用的是自簽名的證書(shū),所以需要加上 -k 參數(shù)來(lái)忽略證書(shū)校驗(yàn),如果沒(méi)有這個(gè)參數(shù)則在請(qǐng)求的時(shí)候會(huì)報(bào)錯(cuò):
$ curl -H "Host: example.com" https://localhost/service/2 -icurl: (60) SSL certificate problem: self signed certificateMore details here: https://curl.se/docs/sslcerts.htmlcurl failed to verify the legitimacy of the server and therefore could notestablish a secure connection to it. To learn more about this situation andhow to fix it, please visit the web page mentioned above.
我們也可以通過(guò)管理頁(yè)面去查看證書(shū)相關(guān)的信息,上面我們啟動(dòng)容器的時(shí)候綁定了宿主機(jī)的 8001 端口,所以我們可以通過(guò)訪(fǎng)問(wèn) http://localhost:8001/certs 來(lái)獲取到證書(shū)相關(guān)的信息:
證書(shū)信息
到這里我們就基本了解了 Envoy 的靜態(tài)配置方式,接下來(lái)的 xDS 才是 Envoy 中的精華部分。
本文鏈接:http://www.tebozhan.com/showinfo-26-15753-0.htmlEnvoy 基礎(chǔ)入門(mén)教程,看這一篇就夠了
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com