WebAssembly(WASM)是一种编程语言,ASM提供了对WASM技术的支持,可以把扩展的WASM Filter通过ASM部署到数据面集群中相应的Envoy代理中。通过这种过滤器扩展机制,可以轻松扩展Envoy的功能并将其在服务网格中的应用推向了新的高度。本文介绍了WASM Filter以及如何为Envoy编写WASM Filter并部署到ASM中。
背景信息
Envoy是一个高性能、可编程的L3、L4和L7代理,作为ASM数据面的代理使用。Envoy的连接和流量处理的核心是网络过滤器(Network Filter),该过滤器一旦融合进过滤器链(Filter Chain),就可以实现访问控制、数据或协议转换、数据增强、审计等高级功能。通过添加新的过滤器,可以用来扩展Envoy的已有功能集。当前有两种方法可以添加新的过滤器:
静态预编译:将其他过滤器集成到Envoy的源代码中,并编译新的Envoy版本。这种方法的缺点是您需要维护Envoy版本,并不断使其与官方发行版保持同步。此外,由于Envoy是用C++实现的,因此新开发的过滤器也必须用C++实现。
动态运行时加载:在运行时将新的过滤器动态加载到Envoy代理中。
显而易见,第二种方法极大地简化了扩展Envoy的过程。这种解决方案依赖于一种称之为WebAssembly(WASM)的新技术,它是一种有效的可移植二进制指令格式,提供了可嵌入和隔离的执行环境。
ASM提供了对WASM技术的支持,如下图所示。

为什么要使用WASM Filter?
使用WASM实现过滤器的扩展,有如下优势:
敏捷性:过滤器可以动态加载到正在运行的Envoy进程中,而无需停止或重新编译。
可维护性:不必更改Envoy自身基础代码库即可扩展其功能。
多样性:可以将流行的编程语言(例如C/C++和Rust)编译为WASM,因此开发人员可以选择实现过滤器的编程语言。
可靠性和隔离性:过滤器会被部署到VM沙箱中,因此与Envoy进程本身是隔离的;即使当WASM Filter出现问题导致崩溃时,它也不会影响Envoy进程。
安全性:过滤器通过预定义API与Envoy代理进行通信,因此它们可以访问并只能修改有限数量的连接或请求属性。
当前WASM实现过滤器的扩展,也需要考虑以下缺点是否可以容忍:
性能约为C++编写的原生静态编译的Filter的70%。
由于需要启动一个或多个WASM虚拟机,因此会消耗一定的内存使用量。
使用Proxy-WASM SDK构建过滤器
Envoy Proxy在基于堆栈的虚拟机中运行WASM过滤器,因此过滤器的内存与主机环境是隔离的。Envoy代理与WASM过滤器之间的所有交互都是通过Envoy Proxy-WASM SDK提供的功能实现的。Envoy Proxy-WASM SDK提供了多种编程语言的实现,包括C++、Rust、AssemblyScript以及处于实验中的Golang等。此外,社区也在推动相应的WASMfor Proxies (Proxy-Wasm)应用二进制接口ABI规范,详情请参见spec。
构建WASM Filter的最简单方法是使用Docker,使用C++ Envoy Proxy-WASM SDK创建一个Docker镜像。详情请参见Docker。
创建一个项目并使用上述Docker镜像进行构建。详情请参见Creating a project for use with the Docker build image。
编辑开发此项目。详情请参见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容器中的路径/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过滤器在处理针对应用服务
details-v1
的流量时,能够以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 labels: asm-system: 'true' provider: asm spec: configPatches: - applyTo: HTTP_FILTER match: context: SIDECAR_INBOUND proxy: proxyVersion: '^1\.*.*' listener: filterChain: filter: name: envoy.filters.network.http_connection_manager subFilter: name: envoy.filters.http.router patch: operation: INSERT_BEFORE value: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 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
执行以下命令,将WASM过滤器插入到应用服务details的HTTP级别过滤器链中。
apiVersion: networking.istio.io/v1alpha3 kind: EnvoyFilter metadata: name: details-v1-examplefilter labels: asm-system: 'true' provider: asm spec: configPatches: - applyTo: HTTP_FILTER match: context: SIDECAR_INBOUND proxy: proxyVersion: '^1\.*.*' listener: filterChain: filter: name: envoy.filters.network.http_connection_manager subFilter: name: envoy.filters.http.router patch: operation: INSERT_BEFORE value: typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 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
验证结果
通过在浏览器中访问入口网关的地址,将一些流量发送到productpage服务上,在页面响应中,可以看到过滤器的头添加到响应头中,如下图所示。
执行以下命令,将一些流量发送到details服务上。在响应中,可以看到过滤器的头添加到响应头中。
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