簡介
Envoy是一個高性能、可程式設計的L3 / L4和L7代理,被服務網格ASM作為資料面的代理使用。Envoy的連接配接和流量處理的核心是網絡過濾器(Network Filter),該過濾器一旦融合進濾器鍊(Filter Chain),就可以實作用于通路控制、資料或協定轉換、資料增強、審計等進階功能。通過添加新的過濾器Filter,可以用來擴充Envoy的已有功能集。目前有兩種方法可以添加新的過濾器:
- 靜态預編譯:将其他過濾器Filter內建到Envoy的源代碼中,并編譯新的Envoy版本。這種方法的缺點是您需要維護自己的Envoy版本,并不斷使其與官方發行版保持同步。此外,由于Envoy是用C++實作的,是以新開發的過濾器Filter也必須用C++實作。
- 動态運作時加載:在運作時将新的過濾器動态加載到Envoy代理中。
顯而易見,從使用難易角度來看,第二種方式極大地簡化了擴充Envoy的過程。這種解決方案依賴于一種稱之為WebAssembly(WASM)的新技術,它是一種有效的可移植二進制指令格式,提供了可嵌入和隔離的執行環境。
ASM與WASM
阿裡雲服務網格ASM産品中提供了對WebAssembly(WASM)技術的支援,如下圖所示,服務網格使用人員可以把擴充的WASM Filter通過ASM部署到資料面叢集中相應的Envoy代理中。通過這種過濾器擴充機制,可以輕松擴充Envoy的功能并将其在服務網格中的應用推向了新的高度。

為什麼要使用WASM Filter
使用WebAssembly(WASM)實作過濾器Filter的擴充,可以得到如下優勢:
- 靈活性:過濾器Filter可以動态加載到正在運作的Envoy程序中,而無需停止或重新編譯。
- 可維護性:不必更改Envoy自身基礎代碼庫即可擴充其功能。
- 多樣性:可以将流行的程式設計語言(例如C / C ++和Rust)編譯為WASM,是以開發人員可以使用他們選擇的程式設計語言來實作過濾器Filter。
- 可靠性和隔離性:過濾器Filter會被部署到VM沙箱中,是以與Envoy程序本身是隔離的;即使當WASM Filter出現問題導緻崩潰時,它也不會影響Envoy程序。
- 安全性:過濾器Filter通過預定義API與Envoy代理進行通信,是以它們可以通路并隻能修改有限數量的連接配接或請求屬性。
目前WebAssembly(WASM)實作過濾器Filter的擴充,也需要考慮以下缺點是否可以容忍:
- 性能約為C++編寫的原生靜态編譯的Filter的70%。
- 由于需要啟動一個或多個WASM虛拟機,是以會消耗一定的記憶體使用量。
使用Proxy-WASM SDK建構過濾器
Envoy Proxy在基于堆棧的虛拟機中運作WASM過濾器,是以過濾器的記憶體與主機環境是隔離的。Envoy代理與WASM過濾器之間的所有互動都是通過Envoy Proxy-WASM SDK提供的功能實作的。Envoy Proxy-WASM SDK提供了多種程式設計語言的實作,包括C++、Rust、AssemblyScript以及處于實驗中的golang等。此外,社群也在推動相應的WebAssembly (Wasm) for Proxies (Proxy-Wasm)應用二進制接口ABI規範,具體可以參考:
https://github.com/proxy-wasm/spec。
- 建構WASM Filter的最簡單方法是使用Docker,參考 https://github.com/proxy-wasm/proxy-wasm-cpp-sdk#docker 所述,使用C++ Envoy Proxy-WASM SDK建立一個Docker鏡像。
或者直接使用已建構好的鏡像:registry.cn-hangzhou.aliyuncs.com/acs/wasmsdk:v0.1
- 參照 https://github.com/proxy-wasm/proxy-wasm-cpp-sdk#creating-a-project-for-use-with-the-docker-build-image 所述,建立一個項目并使用上述Docker鏡像進行建構。
- 具體開發過程在此不再贅述,具體可參見: https://github.com/proxy-wasm/proxy-wasm-cpp-sdk#webassembly-for-proxies-c-sdk
- 切換到項目根目錄,執行如下指令建構WASM:
docker run -v $PWD:/work -w /work registry.cn-hangzhou.aliyuncs.com/acs/wasmsdk:v0.1 /build_wasm.sh
在ASM中部署啟用WASM Filter
- 建立一個configmap,用于儲存WASM過濾器的二進制檔案内容。例如,在命名空間default下,建立一個名稱為wasm-example-filter的configmap,并将WASM過濾器的二進制檔案example-filter.wasm儲存到該configmap中。
kubectl create configmap -n default wasm-example-filter --from-file=example-filter.wasm
- 使用以下兩個annotation将WASM過濾器的二進制檔案注入到應用程式對應的Kubernetes服務中:
sidecar.istio.io/userVolume: '[{"name":"wasmfilters-dir","configMap": {"name": "wasm-example-filter"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/var/local/lib/wasm-filters","name":"wasmfilters-dir"}]'
- 執行以下指令更新productpage-v1:
kubectl patch deployment productpage-v1 -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/userVolume":"[{\"name\":\"wasmfilters-dir\",\"configMap\": {\"name\": \"wasm-example-filter\"}}]","sidecar.istio.io/userVolumeMount":"[{\"mountPath\":\"/var/local/lib/wasm-filters\",\"name\":\"wasmfilters-dir\"}]"}}}}}'
執行以下指令更新details-v1:
kubectl patch deployment details-v1 -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/userVolume":"[{\"name\":\"wasmfilters-dir\",\"configMap\": {\"name\": \"wasm-example-filter\"}}]","sidecar.istio.io/userVolumeMount":"[{\"mountPath\":\"/var/local/lib/wasm-filters\",\"name\":\"wasmfilters-dir\"}]"}}}}}'
- 現在,您可以在istio-proxy容器中的路徑
下,找到WASM過濾器的二進制檔案:/var/local/lib/wasm-filters
kubectl exec -it deployment/productpage-v1 -c istio-proxy -- ls /var/local/lib/wasm-filters/
kubectl exec -it deployment/details-v1 -c istio-proxy -- ls /var/local/lib/wasm-filters/
- 要使WASM過濾器在處理針對應用服務
的流量時,能夠以DEBUG日志級别記錄,執行如下指令:productpage
kubectl port-forward deployment/productpage-v1 15000
curl -XPOST "localhost:15000/logging?wasm=debug"
同樣地, 要使WASM過濾器在處理針對應用服務
productpage
kubectl port-forward deployment/details-v1 15000
curl -XPOST "localhost:15000/logging?wasm=debug"
- 将WASM過濾器插入到應用服務
的HTTP級别過濾器鍊中,執行如下指令:productpage
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: productpage-v1-examplefilter
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.http_connection_manager
subFilter:
name: envoy.router
patch:
operation: INSERT_BEFORE
value:
config:
config:
name: example-filter
rootId: my_root_id
vmConfig:
code:
local:
filename: /var/local/lib/wasm-filters/example-filter.wasm
runtime: envoy.wasm.runtime.v8
vmId: example-filter
allow_precompiled: true
name: envoy.filters.http.wasm
workloadSelector:
labels:
app: productpage
version: v1
- 通過在浏覽器中通路入口網關的位址, 将一些流量發送到productpage服務上, 在頁面響應中,可以看到過濾器的頭添加到響應頭中,如下圖所示。
為Envoy編寫WASM Filter并部署到服務網格ASM中使用
-
details
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: details-v1-examplefilter
spec:
configPatches:
- applyTo: HTTP_FILTER
match:
context: SIDECAR_INBOUND
listener:
filterChain:
filter:
name: envoy.http_connection_manager
subFilter:
name: envoy.router
patch:
operation: INSERT_BEFORE
value:
config:
config:
name: example-filter
rootId: my_root_id
vmConfig:
code:
local:
filename: /var/local/lib/wasm-filters/example-filter.wasm
runtime: envoy.wasm.runtime.v8
vmId: example-filter
allow_precompiled: true
name: envoy.filters.http.wasm
workloadSelector:
labels:
app: details
version: v1
- 将一些流量發送到details服務上 :
kubectl exec -ti deploy/productpage-v1 -c istio-proxy -- curl -v http://details:9080/details/123
在響應中,可以看到過濾器的頭添加到響應頭中:
kubectl exec -ti deploy/productpage-v1 -c istio-proxy -- curl -v http://details:9080/details/123
* Trying 172.31.13.58...
* TCP_NODELAY set
* Connected to details (172.31.13.58) port 9080 (#0)
> GET /details/123 HTTP/1.1
> Host: details:9080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
xxxxxxx
< resp-header-demo: added by our filter
xxxxx
* Connection #0 to host details left intact
xxxxx