为Envoy编写WASM Filter并部署到服务网格ASM中使用

简介

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的功能并将其在服务网格中的应用推向了新的高度。

为Envoy编写WASM Filter并部署到服务网格ASM中使用

为什么要使用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

  1. 构建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

  1. 参照https://github.com/proxy-wasm/proxy-wasm-cpp-sdk#creating-a-project-for-use-with-the-docker-build-image 所述,创建一个项目并使用上述Docker镜像进行构建。
  1. 具体开发过程在此不再赘述,具体可参见:https://github.com/proxy-wasm/proxy-wasm-cpp-sdk#webassembly-for-proxies-c-sdk
  1. 切换到项目根目录,执行如下命令构建WASM:
docker run -v $PWD:/work -w /work  registry.cn-hangzhou.aliyuncs.com/acs/wasmsdk:v0.1 /build_wasm.sh

在ASM中部署启用WASM Filter

  1. 创建一个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
  1. 使用以下两个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"}]'
  1. 执行以下命令更新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\"}]"}}}}}'
  1. 现在,您可以在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/
  1. 要使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"


  1. 将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
  1. 通过在浏览器中访问入口网关的地址, 将一些流量发送到productpage服务上, 在页面响应中,可以看到过滤器的头添加到响应头中,如下图所示。
    为Envoy编写WASM Filter并部署到服务网格ASM中使用
  1. 将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
  1. 将一些流量发送到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
上一篇:Service Mesh 最火项目 Istio 架构解析


下一篇:如何使用 Istio 进行多集群部署管理(1): 单控制平面 VPN 连接拓扑