简介
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镜像进行构建。
- 切换到项目根目录,执行如下命令构建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容器中的路径
/var/local/lib/wasm-filters
下,找到WASM过滤器的二进制文件:
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过滤器在处理针对应用服务
productpage
的流量时,能够以DEBUG日志级别记录,执行如下命令:
kubectl port-forward deployment/productpage-v1 15000
curl -XPOST "localhost:15000/logging?wasm=debug"
同样地, 要使WASM过滤器在处理针对应用服务productpage
的流量时,能够以DEBUG日志级别记录,执行如下命令:
kubectl port-forward deployment/details-v1 15000
curl -XPOST "localhost:15000/logging?wasm=debug"
- 将WASM过滤器插入到应用服务
productpage
的HTTP级别过滤器链中,执行如下命令:
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服务上, 在页面响应中,可以看到过滤器的头添加到响应头中,如下图所示。
- 将WASM过滤器插入到应用服务
details
的HTTP级别过滤器链中,执行如下命令:
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