1. 引言
在最新的eShopOnContainers 3.0 中Ocelot 網關被Envoy Proxy 替換。下面就來簡要帶大家了解下Envoy,並嘗試梳理下為什么要使用Envoy替代Ocelot。
2. Hello Envoy
ENVOY IS AN OPEN SOURCE EDGE AND SERVICE PROXY, DESIGNED FOR CLOUD-NATIVE APPLICATIONS.
Enovy(信使) 是一款開源的專為雲原生應用設計的服務代理。
2.1. 快速體驗
首先基於本地Dockers快速體驗以下,先啟動本地Docker-Desktop,拉取Envoy鏡像:
> docker search envoy-dev
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
envoyproxy/envoy Images for tagged releases. Use envoy-dev fo… 96
> docker image pull envoyproxy:envoy-dev
latest: Pulling from envoyproxy/envoy-dev
171857c49d0f: Pull complete
419640447d26: Pull complete
61e52f862619: Pull complete
3f2a8c910457: Pull complete
b2ce823b3fd3: Pull complete
ec09faba9bc7: Pull complete
b0b9168845d0: Pull complete
39a220277151: Pull complete
9081a11f5983: Pull complete
1880b475bc3a: Pull complete
Digest: sha256:cd8dbbbd8ce4c8c6eb52e4f8eebf55f29d1e597ca8311fecf9eda08b8cca813a
Status: Downloaded newer image for envoyproxy/envoy-dev:latest
docker.io/envoyproxy/envoy-dev:latest
該Docker 鏡像將包含最新版本的 Envoy 和一個基本的 Envoy 配置,可以將10000端口的入站請求路由到www.google.com
。
下面啟動容器測試:
> docker run -d --name envoy -p 10000:10000 envoyproxy/envoy-dev:latest
27e422f34b389d99e9180e47d8109a19975ccd139f42ac2f4fa9f724906b72f6
> docker ps | findstr 'envoy'
27e422f34b38 envoyproxy/envoy-dev:latest "/docker-entrypoint.?? 2 minutes ago Up 2 minutes 0.0.0.0:10000->10000/tcp envoy
> curl -I http://localhost:10000
HTTP/1.1 200 OK
content-type: text/html; charset=ISO-8859-1
p3p: CP="This is not a P3P policy! See g.co/p3phelp for more info."
date: Sat, 17 Oct 2020 04:38:38 GMT
server: envoy
x-xss-protection: 0
x-frame-options: SAMEORIGIN
expires: Sat, 17 Oct 2020 04:38:38 GMT
cache-control: private
set-cookie: 1P_JAR=2020-10-17-04; expires=Mon, 16-Nov-2020 04:38:38 GMT; path=/; domain=.google.com; Secure
set-cookie: NID=204=h0EoJXNOTbQA11L-tVowqcwloS0-BCTR71IeN4irsmpubdPIIS4sU8Gco79pt1NhONAxxFdUJ46SKvbX4Ni-jKMWbSW0k_kn3fFkVrfLm7OOBbAtUWtxGGOCRJGbSNIRyOPfDB7_wMngEWW3yoFEs9diSCtZK9DWFZdtJJZtWuI; expires=Sun, 18-Apr-2021 04:38:38 GMT; path=/; domain=.google.com; HttpOnly
alt-svc: h3-Q050=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-27=":443"; ma=2592000,h3-T051=":443"; ma=2592000,h3-T050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
x-envoy-upstream-service-time: 37
transfer-encoding: chunked
PS: 請確保本地機器能訪問Google,否則curl -I http://localhost:10000
會出錯。
接下來我們進入容器內部,查看下配置文件,默認路徑為/etc/envoy/envoy.yaml
:
docker exec -it envoy /bin/bash
root@27e422f34b38:/# cat /etc/envoy/envoy.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
protocol: TCP
address: 127.0.0.1
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
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
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/"
route:
host_rewrite_literal: www.google.com
cluster: service_google
http_filters:
- name: envoy.filters.http.router
clusters:
- name: service_google
connect_timeout: 30s
type: LOGICAL_DNS
# Comment out the following line to test on v6 networks
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: service_google
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: www.google.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.google.com
我們把上面的配置文件拷貝到本地,將上面的www.google.com
改為www.baidu.com
,將admin.address.socket_address.address: 127.0.0.1
該為0.0.0.0
,然后把配置文件命名為envoy-baidu.yaml
,然后掛載到容器的/etc/envoy/envoy.yaml
。
> docker run --rm -d --name envoy-baidu -v $Home/k8s/envoy-baidu.yaml:/etc/envoy/envoy.yaml -p 9901:9901 -p 15001:15001 envoyproxy/envoy-dev:latest
> docker ps | findstr 'envoy'
f07f6a1e9305 envoyproxy/envoy-dev:latest "/docker-entrypoint.?? 2 minutes ago Up 2 minutes 10000/tcp, 0.0.0.0:9901->9901/tcp, 0.0.0.0:15001->15001/tcp envoy-baidu
3cd12b5f6ddd envoyproxy/envoy-dev:latest "/docker-entrypoint.?? About an hour ago Up About an hour 0.0.0.0:10000->10000/tcp envoy
> curl -I http://localhost:15001
HTTP/1.1 200 OK
accept-ranges: bytes
cache-control: private, no-cache, no-store, proxy-revalidate, no-transform
content-length: 277
content-type: text/html
date: Sat, 17 Oct 2020 05:41:01 GMT
etag: "575e1f65-115"
last-modified: Mon, 13 Jun 2016 02:50:13 GMT
pragma: no-cache
server: envoy
x-envoy-upstream-service-time: 24
使用瀏覽器訪問http://localhost:9901即可訪問envoy管理頁面,如下圖所示:
2.2. 配置簡介
第一次看Envoy的配置文件,和第一次接觸Nginx的配置文件一樣,絕對一臉懵逼。沒關系,咱們來理一理。
作為一個代理,不管是Nginx、HAProxy,還是Envoy,其處理流程都是一樣的。其首先都是要監聽指定端口獲取請求流量,然后分析請求數據,進行請求轉發。腦補完大致流程后,再來看 Envoy 是如何組織配置信息的。先來了幾個核心配置:
- listener : Envoy 的監聽地址,用來接收請求,處理入站請求。Envoy 會暴露一個或多個 Listener 來監聽客戶端的請求。
- filter : 過濾器是處理入站和出站流量的鏈式結構的一部分。在過濾器鏈上可以集成很多特定功能的過濾器,例如,通過集成 GZip 過濾器可以在數據發送到客戶端之前壓縮數據。
- route_config : 路由規則配置。即將請求路由到后端的哪個集群。
- cluster : 集群定義了流量的目標端點,同時還包括一些其他可選配置,如負載均衡策略等。
整體流程如下圖所示:
2.3. 代理 ASP.NET Core WebApi
有了上面的基礎,下面嘗試使用Envoy代理ASP.NET Core WebApi。
首先創建兩個簡單API,然后創建一個Envoy配置文件,最后通過docker compose啟動三個容器進行測試。由於項目文件結構簡單,這里不再過多闡述,主要包含四個部分:
- City Api
- Weather Api
- Envoy 代理配置
- docker compose 配置
整體解決方案如下圖所示。源碼路徑:K8S.NET.Envoy。
Envoy 代理配置基於第一節的基礎上進行修改,如下所示:
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9903
static_resources:
listeners:
- name: listener_0
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 10003
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match:
prefix: "/c"
route:
prefix_rewrite: "/city"
cluster: city_service
- match:
prefix: "/w"
route:
prefix_rewrite: "/weather"
cluster: weather_service
http_filters:
- name: envoy.filters.http.router
clusters:
- name: city_service
connect_timeout: 0.25s
type: LOGICAL_DNS
# Comment out the following line to test on v6 networks
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: city_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: cityapi
port_value: 80
- name: weather_service
connect_timeout: 0.25s
type: LOGICAL_DNS
# Comment out the following line to test on v6 networks
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: weather_service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: weatherapi
port_value: 80
以上配置Envoy監聽10003
端口,通過指定prefix_rewrite
重寫前綴,將/c
路由至cityapi
的/city
路徑,將/w
路由至weatherapi
的/weather
路徑。
docker-compose配置如下:
version: '3'
services:
envoygateway:
build: Envoy/
ports:
- "9903:9903"
- "10003:10003"
volumes:
- ./Envoy/envoy.yaml:/etc/envoy/envoy.yaml
cityapi:
build: K8S.NET.CityApi/
ports:
- "8080:80"
environment:
ASPNETCORE_URLS: "http://+"
ASPNETCORE_ENVIRONMENT: "Development"
weatherapi:
build: K8S.NET.WeatherApi/
ports:
- "8082:80"
environment:
ASPNETCORE_URLS: "http://+"
ASPNETCORE_ENVIRONMENT: "Development"
從上可以看到,主要用來啟動三個服務:
- envoy gateway:其中將項目路徑下
/Envoy/envoy.yaml
掛載到容器目錄/etc/envoy/envoy.yaml
。同時暴露2個端口,9903,10003。 - city api
- weather api
因此最終可以通過以下路徑進行訪問:
- http://localhost:10003/c 訪問city api。
- http://localhost:10003/w 訪問weather api。
執行以下命令,啟動應用和代理,並測試:
> docker-compose up -d
Starting k8snetenvoy_envoygateway_1 ... done
Starting k8snetenvoy_cityapi_1 ... done
Starting k8snetenvoy_weatherapi_1 ... done
> docker-compose ps
Name Command State Ports
-----------------------------------------------------------------------------------------------------------------------
k8snetenvoy_cityapi_1 dotnet K8S.NET.CityApi.dll Up 443/tcp, 0.0.0.0:8080->80/tcp
k8snetenvoy_envoygateway_1 /docker-entrypoint.sh envo ... Up 10000/tcp, 0.0.0.0:10003->10003/tcp,
0.0.0.0:9903->9903/tcp
k8snetenvoy_weatherapi_1 dotnet K8S.NET.WeatherApi.dll Up 443/tcp, 0.0.0.0:8082->80/tcp
> curl http://localhost:10003/c
Shanghai
> curl http://localhost:10003/w
Cool
3. eShopOnContainers 中的應用
eShopOnContainer 中主要定義了四個API 網關(BFF 模式),服務間通信方式主要有兩種,一種是HTTP,一種是gRPC。如果啟用Service Mesh並且部署至K8S,服務整體通信架構如下圖所示:
有兩點需要補充說明:
- Linkerd是一種Service Mesh,其核心思想是借助Sidecar模式無侵入式對應用進行服務治理,包括服務發現、流量管理、負載均衡、路由等。
- 了解過Istio(目前比較流行的Service Mesh)應該知道,Envoy在Istio中作為Sidecar而存在,而在eShopOnContainers中Envoy被充當API Gateways。
基於上面的基礎,再來看eShopOnContainers中的配置,其實就很明白了,主要是配置文件從Ocelot 轉變到envoy.yaml,配置如下圖所示。
路由配置如下:
- /m/ 、/marketing-api/ 路由至:marketing api
- /c/、/catalog-api/ 路由至:catalog api
- /o/、/ordering-api/ 路由至:ordering api
- /b/、/basket-api/ 路由至:basket api
- / 路由至:web bff aggregator api
部署時,基於helm將envoy.yaml
保存至ConfigMap
,在基於envoyproxy/enovy
鏡像構建容器,將配置從ConfigMap
掛載到容器中,容器內部即可基於配置啟動Envoy 網關了。
4. Why Envoy
經過上面的了解發現,Envoy還是充當的網關角色,那為什么要替換呢? 先來了解下Envoy的優勢:
-
非侵入式架構 :
Envoy
基於Sidecar
模式,是一個獨立進程,對應用透明。(在eShopOnContainer中還是獨立的網關項目,並非以Sidecar
模式注入到服務中。) -
基於C++開發實現:擁有強大的定制化能力和優異的性能。
-
L3/L4/L7 架構 : 傳統的網絡代理,要么在
HTTP
層工作,要么在TCP
層工作。而Envoy
同時支持 3/4 層和 7 層代理。 -
頂級 HTTP/2 支持 : 它將
HTTP/2
視為一等公民,並且可以在HTTP/2
和HTTP/1.1
之間相互轉換(雙向),建議使用HTTP/2
。 -
gRPC 支持 : Envoy 完美支持 HTTP/2,也可以很方便地支持
gRPC
(gRPC 使用HTTP/2
作為底層多路復用傳輸協議)。 -
服務發現和動態配置 : 與
Nginx
等代理的熱加載不同,Envoy
可以通過API
接口動態更新配置,無需重啟代理。 -
可觀測性 :
Envoy
內置stats
模塊,可以集成諸如prometheus/statsd
等監控方案。還可以集成分布式追蹤系統,對請求進行追蹤。
再來看下Ocelot:其本質還是ASP.NET Core中的一個請求中間件。只能進行7層代理,不支持 gRPC,不支持監控。因此總體而言,Envoy更契合雲原生對網絡代理的訴求。
5. 總結
本文簡要梳理了Envoy的基本用法,以及其在eShopOnContainers中的運用。Envoy作為一個比肩Nginx的服務代理,其特性在Service Mesh中有着靈活的運用。本文就講到這里了,下次有機會在和大家分享下Envoy在Service Mesh中的應用。
參考資料: