eShopOnContainers 知多少[12]:Envoy gateways
阿新 • • 發佈:2020-10-19
![](https://upload-images.jianshu.io/upload_images/2799767-907fa556a105bc40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
# 1. 引言
在最新的eShopOnContainers 3.0 中Ocelot 閘道器被Envoy Proxy 替換。下面就來簡要帶大家瞭解下Envoy,並嘗試梳理下為什麼要使用Envoy替代Ocelot。
# 2. Hello Envoy
>**[ENVOY](https://www.envoyproxy.io)** 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管理頁面,如下圖所示:
![envoy admin page](https://upload-images.jianshu.io/upload_images/2799767-799930f950c87a20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 2.2. 配置簡介
第一次看Envoy的配置檔案,和第一次接觸Nginx的配置檔案一樣,絕對一臉懵逼。沒關係,咱們來理一理。
作為一個代理,不管是Nginx、HAProxy,還是Envoy,其處理流程都是一樣的。其首先都是要監聽指定埠獲取請求流量,然後分析請求資料,進行請求轉發。腦補完大致流程後,再來看 Envoy 是如何組織配置資訊的。先來了幾個核心配置:
- **listener** : Envoy 的監聽地址,用來接收請求,處理入站請求。Envoy 會暴露一個或多個 Listener 來監聽客戶端的請求。
- **filter** : 過濾器是處理入站和出站流量的鏈式結構的一部分。在過濾器鏈上可以整合很多特定功能的過濾器,例如,通過整合 GZip 過濾器可以在資料傳送到客戶端之前壓縮資料。
- **route_config** : 路由規則配置。即將請求路由到後端的哪個叢集。
- **cluster** : 叢集定義了流量的目標端點,同時還包括一些其他可選配置,如負載均衡策略等。
整體流程如下圖所示:
![圖片來源:https://fuckcloudnative.io/envoy-handbook/docs/gettingstarted/quick-start/](https://upload-images.jianshu.io/upload_images/2799767-51ed0f50d336c308.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
## 2.3. 代理 ASP.NET Core WebApi
有了上面的基礎,下面嘗試使用Envoy代理ASP.NET Core WebApi。
首先建立兩個簡單API,然後建立一個Envoy配置檔案,最後通過docker compose啟動三個容器進行測試。由於專案檔案結構簡單,這裡不再過多闡述,主要包含四個部分:
1. City Api
2. Weather Api
3. Envoy 代理配置
4. docker compose 配置
整體解決方案如下圖所示。原始碼路徑:[K8S.NET.Envoy](https://github.com/sheng-jie/dotnet.on.k8s/tree/master/K8S.NET.Envoy)。
![](https://upload-images.jianshu.io/upload_images/2799767-dcab57e426d6dcc0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
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"
```
從上可以看到,主要用來啟動三個服務:
1. envoy gateway:其中將專案路徑下`/Envoy/envoy.yaml`掛載到容器目錄`/etc/envoy/envoy.yaml`。同時暴露2個埠,9903,10003。
2. city api
3. weather api
因此最終可以通過以下路徑進行訪問:
1. http://localhost:10003/c 訪問city api。
2. 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,服務整體通訊架構如下圖所示:
![](https://upload-images.jianshu.io/upload_images/2799767-47be05d0bd32c83d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
有兩點需要補充說明:
1. [Linkerd](https://linkerd.io)是一種Service Mesh,其核心思想是藉助**Sidecar**模式無侵入式對應用進行服務治理,包括服務發現、流量管理、負載均衡、路由等。
2. 瞭解過Istio(目前比較流行的Service Mesh)應該知道,Envoy在Istio中作為Sidecar而存在,而在eShopOnContainers中Envoy被充當API Gateways。
基於上面的基礎,再來看eShopOnContainers中的配置,其實就很明白了,主要是配置檔案從Ocelot 轉變到envoy.yaml,配置如下圖所示。
![eShopOnContainers envoy proxy configuration](https://upload-images.jianshu.io/upload_images/2799767-5425c94996b90bd5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
路由配置如下:
1. /m/ 、/marketing-api/ 路由至:marketing api
2. /c/、/catalog-api/ 路由至:catalog api
3. /o/、/ordering-api/ 路由至:ordering api
4. /b/、/basket-api/ 路由至:basket api
5. / 路由至:web bff aggregator api
部署時,基於helm將`envoy.yaml`儲存至`ConfigMap`,在基於`envoyproxy/enovy`映象構建容器,將配置從`ConfigMap`掛載到容器中,容器內部即可基於配置啟動Envoy 網關了。
![](https://upload-images.jianshu.io/upload_images/2799767-7166fb7fafa25e35.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
# 4. Why Envoy
經過上面的瞭解發現,Envoy還是充當的閘道器角色,那為什麼要替換呢? 先來了解下Envoy的優勢:
* **非侵入式架構** : `Envoy` 基於`Sidecar`模式,是一個獨立程序,對應用透明。(在eShopOnContainer中還是獨立的閘道器專案,並非以`Sidecar`模式注入到服務中。)
![](https://upload-images.jianshu.io/upload_images/2799767-d4f4df96d0ad4b1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
* **基於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://www.grpc.io/) 使用 `HTTP/2` 作為底層多路複用傳輸協議)。
* **服務發現和動態配置** : 與 `Nginx` 等代理的熱載入不同,`Envoy` 可以通過 `API` 介面動態更新配置,無需重啟代理。
* **特殊協議支援** : Envoy 支援對特殊協議在 L7 進行嗅探和統計,包括:[MongoDB](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/mongo_proxy_filter#)、[DynamoDB](https://www.servicemesher.com/envoy/intro/arch_overview/dynamo.html#arch-overview-dynamo) 等。
* **可觀測性** : `Envoy` 內建 `stats` 模組,可以整合諸如 `prometheus/statsd` 等監控方案。還可以整合分散式追蹤系統,對請求進行追蹤。
再來看下Ocelot:其本質還是ASP.NET Core中的一個請求中介軟體。只能進行7層代理,不支援 gRPC,不支援監控。因此總體而言,Envoy更契合雲原生對網路代理的訴求。
# 5. 總結
本文簡要梳理了Envoy的基本用法,以及其在eShopOnContainers中的運用。Envoy作為一個比肩Nginx的服務代理,其特性在Service Mesh中有著靈活的運用。本文就講到這裡了,下次有機會在和大家分享下Envoy在Service Mesh中的應用。
>參考資料:
>1. [Envoy 介紹 - Envoy 中文指南]( https://fuckcloudnative.io/envoy-handbook/docs/overview/overview/)
> 2. [Build an API Gateway with Envoy and use with .NET Core APIs](https://www.youtube.com/watch?v=UsoH5cqE1OA)