5

istio: 基于assemblyscript/Go SDK开发Istio Envoy Wasm Filter

 1 year ago
source link: https://ieevee.com/tech/2022/06/27/07-wasm.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

针对istio 1.10版本验证。注意网上部分文章已经不适配1.10版本了。

本文只会简单介绍envoy web assembly以及一个Demo实例,不会详细介绍 envoy web assembly原理或编程。

本文将使用solo开发的wasme初始化一个assemblyscript类型的项目,并执行编译,得到wasm二进制文件。然后通过注解,将wasm文件以configmap的形式,自动挂载到istio-proxy容器中。最后创建WASM类型的envoy filter,将规则下发给对应Pod的envoy。

之后介绍了如何使用Go SDK来开发wasm。

envoy wasm plugins

envoy有着很强的可配置性,但总归有一些功能是没有实现的,那么如何对envoy进行扩展呢?

有2种做法:一个是直接修改envoy源码,另一个是通过Web Assembly插件。

直接修改源码的好处是envoy native,性能最好,但带来了可维护性的降低,不利于未来升级,另外也需要使用c++进行编码,对开发人员有一定的要求。

相对来说,Web Assembly的好处就比较多了,如下是banzaicloud总结的几个点:

  • Agility - filters can be dynamically loaded into the running Envoy process without the need to stop or re-compile.
  • Maintainability - we don’t have to change the Envoy’s codebase to extend its functionality.
  • Diversity - popular programming languages such as C/C++ and Rust can be compiled into WASM, thus developers can implement filters using their programming language of choice.
  • Reliability and isolation - filters are deployed into a VM (sandbox), therefore are isolated from the hosting Envoy process itself (e.g. when the WASM filter crashes it will not impact the Envoy process).
  • Security - since filters communicate with the host (Envoy Proxy) through a well-defined API, they have access to and can modify only a limited number of connection or request properties.

目前Envoy 提供了如下几种语言的 Web Assembly SDK:

  • AssemblyScript
  • Go - still experimental

当然,Web Assembly也有其缺点:

  • 性能降低,只有原生c++的70%
  • 内存使用增加,毕竟运行了一个v8的虚拟机
extending.svg

assemblyscript SDK

下面介绍如何使用wasme,基于assemblyscript SDK ,制作wasm二进制文件。

wasme工具

wasme是solo.io公司开发的工具,可以很方便的用来开发和管理 envoy WASM filter。其设计理念和docker是类似的,会有针对本地的build,针对云端的push等;类似docker hub,solo.io也维护了Webassembly Hub作为插件市场。

命令行安装

wasme安装参考官网,步骤如下:

curl -sL https://run.solo.io/wasme/install | sh
export PATH=$HOME/.wasme/bin:$PATH

我这里安装的是0.0.33版本。

初始化示例项目

wasme init 可以初始化一个针对WASM支持语言(cpp/rust/assemblyscript/tinyGo)的项目。这里示例使用的是assemblyscript。工具宣称目前只支持到istio 1.9,实测1.10版本也是支持的。

$ wasme init ./new-filter
✔ assemblyscript
✔ gloo:1.3.x, gloo:1.5.x, gloo:1.6.x, istio:1.5.x, istio:1.6.x, istio:1.7.x, istio:1.8.x, istio:1.9.x
INFO[0005] extracting 1812 bytes to /home/hubottle/wasm/new-filter

示例项目功能

初始化示例项目的功能为http response自动添加header:hello,value默认为world,如果用户设置了 configuration, 则值设置为 configuration的内容。

export * from "@solo-io/proxy-runtime/proxy";
import { RootContext, Context, RootContextHelper, ContextHelper, registerRootContext, FilterHeadersStatusValues, stream_context } from "@solo-io/proxy-runtime";

class AddHeaderRoot extends RootContext {
  configuration : string;

  onConfigure(): bool {
    let conf_buffer = super.getConfiguration();
    let result = String.UTF8.decode(conf_buffer);
    this.configuration = result;
    return true;
  }

  createContext(): Context {
    return ContextHelper.wrap(new AddHeader(this));
  }
}

class AddHeader extends Context {
  root_context : AddHeaderRoot;
  constructor(root_context:AddHeaderRoot){
    super();
    this.root_context = root_context;
  }
  onResponseHeaders(a: u32): FilterHeadersStatusValues {
    const root_context = this.root_context;
    if (root_context.configuration == "") {
      stream_context.headers.response.add("hello", "world!");
    } else {
      stream_context.headers.response.add("hello", root_context.configuration);
    }
    return FilterHeadersStatusValues.Continue;
  }
}

registerRootContext(() => { return RootContextHelper.wrap(new AddHeaderRoot()); }, "add_header");

npm install && npm run asbuild 进行编译。编译得到的文件在 build 目录下,其中 optimized.wasm 文件较小,是优化后的二进制,而 untouched.wasm 较大,包含了debug信息。

注意,这里并没有使用 wasme build/push 进行管理,主要是考虑国内访问外网的问题,下面envoy filter将使用local模式而非remote模式。

$ npm install && npm run asbuild
> asbuild:untouched
> asc assembly/index.ts -b build/untouched.wasm --use abort=abort_proc_exit -t build/untouched.wat --validate --sourceMap --debug


> asbuild:optimized
> asc assembly/index.ts -b build/optimized.wasm --use abort=abort_proc_exit -t build/optimized.wat --validate --sourceMap --optimize
$ ls build/
optimized.wasm  optimized.wasm.map  optimized.wat  untouched.wasm  untouched.wasm.map  untouched.wat

wasm文件挂载

目标:将wasm文件挂载到目标Pod的istio-proxy容器中。

创建为configmap

将optimized.wasm创建为一个configmap。注意,生产上不要这么做,如果量比较大,有可能写满etcd,可以考虑将wasm文件上传到s3,然后将该文件挂载到sidecar容器,或者直接使用remote http的方式。

$ kubectl create cm example-filter --from-file=optimized.wasm

patch Annotation

为Deployment Template增加如下2个注解:

sidecar.istio.io/userVolume: '[{"name":"wasmfilters-dir","configMap": {"name": "example-filter"}}]'
sidecar.istio.io/userVolumeMount: '[{"mountPath":"/var/local/lib/wasm-filters","name":"wasmfilters-dir"}]'

istiod会依据这2个注解,将名为 example-filter 的configmap,挂载到istio-proxy 容器的 /var/local/lib/wasm-filters 目录下。

$ kubectl create deployment httpbin --image docker.io/kennethreitz/httpbin
$ kubectl patch deployment httpbin -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/userVolume":"[{\"name\":\"wasmfilters-dir\",\"configMap\": {\"name\": \"example-filter\"}}]","sidecar.istio.io/userVolumeMount":"[{\"mountPath\":\"/var/local/lib/wasm-filters\",\"name\":\"wasmfilters-dir\"}]"}}}}}'

Pod重新创建后,在istio-proxy的 /var/local/lib/wasm-filters 下可以查看到 optimized.wasm 文件。

envoy wasm filter

接下来是创建 envoy wasm filter,通知envoy加载 wasm 插件。

创建envoy filter

  • proxyVersion与istio-proxy版本保持一致
  • root_id需要与 wasm registerRootContext保持一致
  • filename需要与前面Annotation保持一致
  • workloadSelector设置为目标Pod的label

如下envoy filter适应于 istio 1.10版本,Ref部分有多篇文章使用的是旧版本的配置,不适应于1.10版本(未验证是否适应于其他istio版本)。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: examplefilter
spec:
  configPatches:
  - applyTo: HTTP_FILTER
    match:
      context: SIDECAR_INBOUND
      proxy:
        proxyVersion: '^1\.10.*'
      listener:
        portNumber: 80
        filterChain:
          filter:
            name: envoy.filters.network.http_connection_manager
            subFilter:
              name: envoy.filters.http.router
    patch:
      operation: INSERT_BEFORE
      value:
        name: envoy.filters.http.wasm
        typed_config:
          "@type": type.googleapis.com/udpa.type.v1.TypedStruct
          type_url: type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm
          value:
            config:
              root_id: add_header
              vm_config:
                code:
                  local:
                    filename: /var/local/lib/wasm-filters/optimized.wasm
                runtime: envoy.wasm.runtime.v8
                vm_id: "my_vm_id"
                allow_precompiled: false
  workloadSelector:
    labels:
      app: httpbin

登录到其他服务网格上的容器,请求 httpbin/headers ,可以看到客户端请求时没有携带 hello header,但是应答的报文是携带了的,envoy filter生效了。

[root@debian-77dc9c5f4f-htvc2 /]# curl -i httpbin/headers
HTTP/1.1 200 OK
server: envoy
date: Wed, 21 Jul 2021 15:08:48 GMT
content-type: application/json
content-length: 526
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 5
hello: world!

{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin",
    "User-Agent": "curl/7.29.0",
    "X-B3-Parentspanid": "6b1605afd18ad4fe",
    "X-B3-Sampled": "0",
    "X-B3-Spanid": "33bc8d7277665508",
    "X-B3-Traceid": "6ccb755e06d510386b1605afd18ad4fe",
    "X-Envoy-Attempt-Count": "1",
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/istio-demo/sa/httpbin;Hash=4de05d86f84ae38da23ff1f0961f43bb1dc94a45207ffc388dee687c2fb34837;Subject=\"\";URI=spiffe://cluster.local/ns/istio-demo/sa/default"
  }
}

Go SDK

上文描述了如何使用 assemblyscript SDK 来开发envoy WASM proxy,很多后端开发人员并不熟悉Type Script,使用起来多有不便。

envoy WASM是支持 GO SDK 的,该SDK由 tetrate 开发,项目为 proxy-wasm-go-sdk

proxy-wasm-go-sdk 依赖 TinyGo编译器,而不是普通的Go编译器。与服务端使用的Go编译器不同,TinyGo主要面向嵌入式系统和WASM。

下面介绍下使用方法。

安装tinyGo

参照官网。我这里是ubuntu。

wget https://github.com/tinygo-org/tinygo/releases/download/v0.18.0/tinygo_0.18.0_amd64.deb
sudo dpkg -i tinygo_0.18.0_amd64.deb

这里直接使用 proxy-wasm-go-sdk 的example http_headers,它会replace http header。

func (ctx *httpHeaders) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
	err := proxywasm.ReplaceHttpRequestHeader("test", "best")
	if err != nil {
		proxywasm.LogCriticalf("failed to set request header: test, %v", err)
	}

	hs, err := proxywasm.GetHttpRequestHeaders()
	if err != nil {
		proxywasm.LogCriticalf("failed to get request headers: %v", err)
	}

	for _, h := range hs {
		proxywasm.LogInfof("request header --> %s: %s", h[0], h[1])
	}
	return types.ActionContinue
}

从代码上来看,go sdk要比较方便一些,主要是比较熟悉,可以直接查看 proxy-wasm-go-sdk 提供的接口。

在http_headers目录下执行tinygo编译,生成p.wasm。

tinygo build -o p.wasm -scheduler=none -target=wasi -no-debug

应用与验证

接下来的步骤和 assemblyscript SDK 类似,也是创建configmap、挂载到istio-proxy容器、创建envoy wasm filter,不再赘述。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK