hugo-teek is loading...

1、Istio Wasm插件

最后更新于:

Istio Wasm插件

目录

[toc]

本节实战

实战名称
🚩 实战:使用EnvoyFilter部署Wasm插件-2023.12.16(测试成功)
🚩 实战:使用WasmPlugin部署Wasm插件-2023.12.16(测试成功)

前言

WebAssembly(简称为 Wasm)的诞生源自前端,是一种为了解决日益复杂的 Web 前端应用以及有限的 JavaScript 性能而诞生的技术。它本身并不是一种语言,而是一种字节码标准。WASM 字节码和机器码非常接近,因此可以非常快速的装载运行。任何一种语言,都可以被编译成 WASM 字节码,然后在 WASM 虚拟机中执行,理论上,所有语言,包括 JavaScript、C、C++、Rust、Go、Java 等都可以编译成 WASM 字节码并在 WASM 虚拟机中执行。当然不仅可以嵌入浏览器增强 Web 应用,也可以应用于其他的场景。

img

WebAssembly 是为下列目标而生的:

  • 快速、高效、可移植 —— 通过利用常见的硬件能力,WebAssembly 代码在不同平台上能够以接近本地速度运行。
  • 可读、可调试 —— WebAssembly 是一门低阶语言,但是它也有一种人类可读的文本格式,这允许通过手工来写代码,看代码以及调试代码。
  • 保持安全 —— WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略
  • 不破坏网络 —— WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。

1、Istio WASM

对于 Istio 来说,WebAssembly 也使得 Istio 的扩展能力得到了极大的提升,Isstio 从 1.12 版本开始引入 WASM 扩展 Envoy,当你需要添加 Envoy 或 Istio 不支持的自定义功能时,那么我们就可以使用 Wasm 插件,比如使用 Wasm 插件来添加自定义验证、认证、日志或管理配额等等。

img

首先我们再回顾下 Envoy 的过滤机制,Envoy 通过过滤器来实现各种功能,比如路由、负载均衡、TLS、认证、日志、监控等等。Envoy 提供了进程外架构、支持 L3/L4 filter、HTTP L7 filter,过滤器包括侦听器过滤器(Listener Filters)、网络过滤器(Network Filters)、HTTP 过滤器(HTTP Filters)三种类型。

侦听器过滤器

侦听器过滤器在初始连接阶段访问原始数据并操作 L4 连接的元数据。例如,TLS 检查器过滤器标识连接是否经过 TLS 加密,并解析与该连接关联的 TLS 元数据;HTTP Inspector Filter 检测应用协议是否是 HTTP,如果是的话,再进一步检测 HTTP 协议类型 (HTTP/1.x or HTTP/2) ,这两种过滤器解析到的元数据都可以和 FilterChainMatch 结合使用。

网络过滤器

网络过滤器访问和操作 L4 连接上的原始数据,即 TCP 数据包。例如,TCP 代理过滤器将客户端连接数据路由到上游主机,它还可以生成连接统计数据。此外,MySQL proxy、Redis proxy、Dubbo proxy、Thrift proxy 等都属于网络过滤器。

HTTP 过滤器

HTTP 过滤器在 L7 上运行,由网络过滤器(即 HTTP 连接管理器,HTTP Connection Manager)创建。这些过滤器用于访问、操作 HTTP 请求和响应,例如,gRPC-JSON 转码器过滤器可以为 gRPC 后端提供一个 REST API,并将请求和响应转换为相应的格式。此外,还包括 JWT、Router、RBAC 等多种过滤器。

2、WASM 插件

有很多编程语言都支持编写 WASM 插件,比如 C、C++、Rust、Go、Java 等等,这里我们以 Go 语言为例来编写一个简单的 WASM 插件,编写 WASM 的工具有 Solo.io 团队的 wasmetinygo等,目前应用比较多是 tinygo,tinygo 支持的包可以查看 https://tinygo.org/docs/reference/lang-support/stdlib/ 进行了解。

TinyGo 是 Go 编程语言规范的一个编译器实现,为什么不使用官方的 Go 编译器?目前官方编译器无法生成可以在浏览器外部运行的 WASM 二进制文件,因此也无法生成与 Proxy-Wasm 兼容的二进制文件。

Proxy-Wasm

Proxy-Wasm是开源社区针对「网络代理场景」设计的一套 ABI 规范(二进制文件接口),定义了网络代理和运行在网络代理内部的 Wasm 虚拟机之间的接口,属于当前的事实规范。当前支持该规范的网络代理软件包括 Envoy、MOSN 和 ATS(Apache Traffic Server),支持该规范的 Wasm 扩展 SDK 包括 C++、Rust 和 Go。采用该规范的好处在于能让 Wasm 扩展程序在不同的网络代理产品上运行,比如 MOSN 的 Wasm 扩展程序可以运行在 Envoy 上,而 Envoy 的 Wasm 扩展程序也可以运行在 MOSN 上。

Proxy-Wasm 规范定义了宿主机与 Wasm 扩展程序之间的交互细节,包括 API 列表、函数调用规范以及数据传输规范这几个方面。其中,API 列表包含了 L4/L7、property、metrics、日志等方面的扩展点,涵盖了网络代理场景下所需的大部分交互点。目前实现该规范的 Wasm 扩展 SDK 包括 AssemblyScript、C++、Rust 和 Go:

为了方便,我们也直接选择已有的 proxy-wasm-go-sdk 这个 SDK 进行开发。这个 Proxy-Wasm Go SDK 是用于使用 Go 编程语言在 Proxy-Wasm ABI 规范之上扩展网络代理(例如 Envoyproxy)的 SDK,有了这个 SDK,每个人都可以轻松地生成与 Proxy-Wasm 规范兼容的 Wasm 二进制文件,而无需了解低级且对于没有专业知识的人来说难以理解的 Proxy-Wasm ABI 规范。

使用EnvoyFilter部署Wasm插件

🚩 实战:使用EnvoyFilter部署Wasm插件-2023.12.16(测试成功)

  • 测试环境
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2istio v1.19.3(--set profile=demo)
3
4tinygo version 0.30.0

实验软件:

链接:https://pan.baidu.com/s/1FdvpYnV5kfmbKLJgDCjk6Q?pwd=562j 提取码:562j 2023.12.16-Istio Wasm插件

1.环境准备

首先安装 tinygo 工具,前往 https://github.com/tinygo-org/tinygo/releases/tag/v0.30.0 下载对应的版本,比如我们这里是 Linux 系统,可以使用下面的命令进行安装:

  • 下载
1$ wget https://github.com/tinygo-org/tinygo/releases/download/v0.30.0/tinygo0.30.0.linux-amd64.tar.gz
2$ tar -xvf tinygo0.30.0.linux-amd64.tar.gz
  • 配置环境变量

/root/tinygo/bin放到PATH环境变量里去:

1[root@master1 ~]#vim .bashrc
2……
3export PATH=$PATH:/root/tinygo/bin
4……
5
6#生效
7source .bashrc
  • 验证:
1[root@master1 ~]#tinygo version
2tinygo version 0.30.0 linux/amd64 (using go version <unknown> and LLVM version 16.0.1)

当然我们也可以直接使用 docker 镜像来进行编译。

部署测试服务

  • 这里先部署下要测试的服务

⚠️ 注意下

这里要启用istio!

1[root@master1 ~]#cd istio-1.19.3/
2[root@master1 istio-1.19.3]#ls samples/httpbin/
3gateway-api  httpbin-gateway.yaml  httpbin-nodeport.yaml  httpbin-vault.yaml  httpbin.yaml  README.md  sample-client

部署httpbin服务:

1[root@master1 istio-1.19.3]#kubectl apply -f samples/httpbin/httpbin.yaml 
2serviceaccount/httpbin created
3service/httpbin created
4deployment.apps/httpbin created
5[root@master1 istio-1.19.3]#kubectl apply -f samples/httpbin/httpbin-gateway.yaml 
6gateway.networking.istio.io/httpbin-gateway created
7virtualservice.networking.istio.io/httpbin created

验证:

1[root@master1 istio-1.19.3]#kubectl get po -l app=httpbin
2NAME                       READY   STATUS    RESTARTS   AGE
3httpbin-86869bccff-tlcw9   2/2     Running   0          2m3s
4
5[root@master1 istio-1.19.3]#kubectl get svc -nistio-system
6NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                      AGE
7istio-ingressgateway   LoadBalancer   10.105.233.167   <pending>     15021:31410/TCP,80:31666/TCP,443:32213/TCP,31400:30291/TCP,15443:31787/TCP   34d
8
9#curl http://172.29.9.61:31666

  • 我们请求一个200码看下效果
 1[root@master1 istio-1.19.3]#curl -v  http://172.29.9.61:31666/status/200
 2* About to connect() to 172.29.9.61 port 31666 (#0)
 3*   Trying 172.29.9.61...
 4* Connected to 172.29.9.61 (172.29.9.61) port 31666 (#0)
 5> GET /status/200 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.61:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 200 OK
11< server: istio-envoy
12< date: Mon, 11 Dec 2023 22:49:41 GMT
13< content-type: text/html; charset=utf-8
14< access-control-allow-origin: *
15< access-control-allow-credentials: true
16< content-length: 0
17< x-envoy-upstream-service-time: 3
18< 
19* Connection #0 to host 172.29.9.61 left intact

浏览器效果:

  • 如果是一个418码的话:(是一个茶壶哦)

 1[root@master1 istio-1.19.3]#curl -v  http://172.29.9.61:31666/status/418
 2* About to connect() to 172.29.9.61 port 31666 (#0)
 3*   Trying 172.29.9.61...
 4* Connected to 172.29.9.61 (172.29.9.61) port 31666 (#0)
 5> GET /status/418 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.61:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 418 Unknown
11< server: istio-envoy
12< date: Mon, 11 Dec 2023 22:51:49 GMT
13< x-more-info: http://tools.ietf.org/html/rfc2324
14< access-control-allow-origin: *
15< access-control-allow-credentials: true
16< content-length: 135
17< x-envoy-upstream-service-time: 3
18< 
19
20    -=[ teapot ]=-
21
22       _...._
23     .'  _ _ `.
24    | ."` ^ `". _,
25    \_;`"---"`|//
26      |       ;/
27      \_     _/
28        `"""`
29* Connection #0 to host 172.29.9.61 left intact
  • 如果此时请求一个banana的话呢?
 1[root@master1 istio-1.19.3]#curl -v  http://172.29.9.61:31666/banana/418
 2* About to connect() to 172.29.9.61 port 31666 (#0)
 3*   Trying 172.29.9.61...
 4* Connected to 172.29.9.61 (172.29.9.61) port 31666 (#0)
 5> GET /banana/418 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.61:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 404 Not Found
11< server: istio-envoy
12< date: Mon, 11 Dec 2023 22:53:46 GMT
13< content-type: text/html
14< content-length: 233
15< access-control-allow-origin: *
16< access-control-allow-credentials: true
17< x-envoy-upstream-service-time: 3
18< 
19<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
20<title>404 Not Found</title>
21<h1>Not Found</h1>
22<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>
23* Connection #0 to host 172.29.9.61 left intact
24[root@master1 istio-1.19.3]#curl -v  http://172.29.9.61:31666/banana/200
25* About to connect() to 172.29.9.61 port 31666 (#0)
26*   Trying 172.29.9.61...
27* Connected to 172.29.9.61 (172.29.9.61) port 31666 (#0)
28> GET /banana/200 HTTP/1.1
29> User-Agent: curl/7.29.0
30> Host: 172.29.9.61:31666
31> Accept: */*
32> 
33< HTTP/1.1 404 Not Found
34< server: istio-envoy
35< date: Mon, 11 Dec 2023 22:53:49 GMT
36< content-type: text/html
37< content-length: 233
38< access-control-allow-origin: *
39< access-control-allow-credentials: true
40< x-envoy-upstream-service-time: 3
41< 
42<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
43<title>404 Not Found</title>
44<h1>Not Found</h1>
45<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>
46* Connection #0 to host 172.29.9.61 left intact
47[root@master1 istio-1.19.3]#

肯定是404失败的,因为httpbin服务本身就没提供这个服务。

  • 所以,当前要实现的需求就是:

当我们需要访问http://172.29.9.61:31666/banana/418时,它就会帮我们把它重定向到http://172.29.9.61:31666/status/418去。

2.编写插件

使用TinyGo开发Istio Wasm插件。

接下来我们就可以来编写一个 WASM 插件,这里我们将包含一个 Envoy 过滤器,将来自 http://service/banana/X 的请求重定向到 http://service/status/X

  • 首先,这里我们要安装下go环境

具体安装细节见如下文章:(这里提供一键可部署脚本)

https://onedayxyy.cn/docs/go-centos-install

  • 这里我们使用 Go 语言来编写插件,首先初始化项目:
 1[root@master1 ~]#cd istio
 2[root@master1 istio]#mkdir wasm-go-demo && cd wasm-go-demo
 3[root@master1 wasm-go-demo]#go mod init github.com/cnych/wasm-go-demo
 4go: creating new go.mod: module github.com/cnych/wasm-go-demo
 5[root@master1 wasm-go-demo]#go get github.com/tetratelabs/proxy-wasm-go-sdk
 6go: downloading github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
 7go get: added github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0
 8[root@master1 wasm-go-demo]#ls
 9go.mod  go.sum
10
11[root@master1 wasm-go-demo]#cat go.mod 
12module github.com/cnych/wasm-go-demo
13
14go 1.16
15
16require github.com/tetratelabs/proxy-wasm-go-sdk v0.22.0 // indirect

特别注意:这里需要的go版本最起码为1.19以上。

  • 然后创建 main.go 文件,内容如下:
1[root@master1 wasm-go-demo]#ls
2go.mod  go.sum  main.go
3[root@master1 wasm-go-demo]#
  1package main
  2
  3import (
  4	"regexp"
  5
  6	"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
  7	"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
  8)
  9
 10type vmContext struct {
 11	// 嵌入默认的 VM 上下文,这样我们就不需要重新实现所有方法
 12	types.DefaultVMContext
 13}
 14
 15func (ctx *vmContext) NewPluginContext(contextID uint32) types.PluginContext {
 16	return &pluginContext{}
 17}
 18
 19type pluginContext struct {
 20	// 嵌入默认的插件上下文,这样我们就不需要重新实现所有方法
 21	types.DefaultPluginContext
 22
 23	pattern     string
 24	replaceWith string
 25	configData  string // 保存插件的一些配置信息
 26}
 27
 28// 注入额外的 Header
 29var additionalHeaders = map[string]string{
 30	"who-am-i":    "go-wasm-demo",
 31	"injected-by": "istio-api!",
 32	"site":        "youdianzhishi.com",
 33	"author":      "阳明",
 34	// 定义自定义的header,每个返回中都添加以上header
 35}
 36
 37// NewHttpContext 为每个 HTTP 请求创建一个新的上下文。
 38func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
 39	return &httpRegex{
 40		contextID:     contextID,
 41		pluginContext: ctx,
 42	}
 43}
 44
 45// OnPluginStart 在插件被加载时调用。
 46func (ctx *pluginContext) OnPluginStart(pluginCfgSize int) types.OnPluginStartStatus {
 47	proxywasm.LogWarnf("regex/main.go OnPluginStart()")
 48	// 获取插件配置
 49	data, err := proxywasm.GetPluginConfiguration()
 50	if data == nil {
 51		return types.OnPluginStartStatusOK
 52	}
 53	if err != nil {
 54		proxywasm.LogWarnf("failed read plug-in config: %v", err)
 55		return types.OnPluginStartStatusFailed
 56	}
 57
 58	proxywasm.LogWarnf("read plug-in config: %s\n", string(data))
 59
 60	// 插件启动的时候读取配置
 61	ctx.configData = string(data)
 62	ctx.pattern = "banana/([0-9]*)"
 63	ctx.replaceWith = "status/$1"
 64
 65	return types.OnPluginStartStatusOK
 66}
 67
 68// OnPluginDone 在插件被卸载时调用。
 69func (ctx *pluginContext) OnPluginDone() bool {
 70	proxywasm.LogWarnf("regex/main.go OnPluginDone()")
 71	return true
 72}
 73
 74type httpRegex struct {
 75	// 嵌入默认的 HTTP 上下文,这样我们就不需要重新实现所有方法
 76	types.DefaultHttpContext
 77	// contextID 是插件上下文的 ID,它是唯一的。
 78	contextID     uint32
 79	pluginContext *pluginContext
 80}
 81
 82// OnHttpResponseHeaders 在收到 HTTP 响应头时调用。
 83func (ctx *httpRegex) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
 84	proxywasm.LogWarnf("%d httpRegex.OnHttpResponseHeaders(%d, %t)", ctx.contextID, numHeaders, endOfStream)
 85
 86	// 添加 Header
 87	for k, v := range additionalHeaders {
 88		if err := proxywasm.AddHttpResponseHeader(k, v); err != nil {
 89			proxywasm.LogWarnf("failed to add response header %s: %v", k, err)
 90		}
 91	}
 92
 93	//为了便于演示观察,将配置信息也加到返回头里
 94	proxywasm.AddHttpResponseHeader("configData", ctx.pluginContext.configData)
 95	return types.ActionContinue
 96}
 97
 98// OnHttpRequestHeaders 在收到 HTTP 请求头时调用。
 99func (ctx *httpRegex) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
100	proxywasm.LogWarnf("%d httpRegex.OnHttpRequestHeaders(%d, %t)", ctx.contextID, numHeaders, endOfStream)
101
102	re := regexp.MustCompile(ctx.pluginContext.pattern)
103	replaceWith := ctx.pluginContext.replaceWith
104
105	s, err := proxywasm.GetHttpRequestHeader(":path")
106	if err != nil {
107		proxywasm.LogWarnf("Could not get request header: %v", err)
108	} else {
109		result := re.ReplaceAllString(s, replaceWith)
110		proxywasm.LogWarnf("path: %s, result: %s", s, result)
111
112		err = proxywasm.ReplaceHttpRequestHeader(":path", result)
113		if err != nil {
114			proxywasm.LogWarnf("Could not set request header to %q: %v", result, err)
115		}
116	}
117
118	return types.ActionContinue
119}
120
121func (ctx *httpRegex) OnHttpStreamDone() {
122	proxywasm.LogWarnf("%d OnHttpStreamDone", ctx.contextID)
123}
124
125func main() {
126	proxywasm.LogWarnf("regex/main.go main() REACHED")
127	// 设置 VM 上下文,这样我们就可以在插件启动时读取配置。
128	proxywasm.SetVMContext(&vmContext{})
129}

在上面的代码中,我们主要关注 pluginContexthttpRegex 这两个结构体,其中 pluginContext 结构体主要用于插件的初始化,而 httpRegex 结构体主要用于处理 HTTP 请求。这里我们主要关注 OnHttpRequestHeadersOnHttpResponseHeaders 这两个方法,这两个方法分别用于处理 HTTP 请求头和响应头,我们在这两个方法中添加了一些自定义的 Header,然后在 Istio 中就可以看到这些 Header 了。

3.部署插件

使用EnvoyFilter部署Wasm插件。

  • 代码编写完成后,我们就可以使用 tinygo 来编译了,执行以下命令:
1[root@master1 wasm-go-demo]#tinygo build -o main.wasm -scheduler=none -target=wasi main.go
2[root@master1 wasm-go-demo]#ls
3go.mod  go.sum  main.go  main.wasm

上面的命令会生成一个 main.wasm 文件,这个文件就是我们的 WASM 插件,接下来我们就可以将这个插件部署到 Istio 中了。

img

  • 我们可以将这个 main.wasm 文件放到一个 ConfigMap 中,然后挂载到 Envoy 中,这样就可以在 Envoy 中使用了,比如我们可以使用下面的命令来创建一个 ConfigMap:
1kubectl create configmap new-filter --from-file=new-filter.wasm=main.wasm
  • 然后接下来我们以 httpbin 为例来测试下,这里我们需要修改下 httpbin 的部署文件,将 ConfigMap 挂载到 Envoy 中。

img

修改后的部署文件如下所示:

1[root@master1 wasm-go-demo]#kubectl edit deploy httpbin
 1# httpbin.yaml
 2apiVersion: v1
 3kind: ServiceAccount
 4metadata:
 5  name: httpbin
 6---
 7apiVersion: v1
 8kind: Service
 9metadata:
10  name: httpbin
11  labels:
12    app: httpbin
13    service: httpbin
14spec:
15  ports:
16    - name: http
17      port: 8000
18      targetPort: 80
19  selector:
20    app: httpbin
21---
22apiVersion: apps/v1
23kind: Deployment
24metadata:
25  name: httpbin
26spec:
27  selector:
28    matchLabels:
29      app: httpbin
30      version: v1
31  template:
32    metadata:
33      labels:
34        app: httpbin
35        version: v1
36      annotations:
37        # 不能在容器上使用 volume 挂载,因为它来自 injector。
38        # NOTE: 我们这个示例始终挂在 "new-filter" ConfigMap 到 /var/local/wasm/new-filter.wasm
39        sidecar.istio.io/userVolume: '[{"name":"new-filter","configMap":{"name":"new-filter"}}]'
40        sidecar.istio.io/userVolumeMount: '[{"mountPath":"/var/local/wasm","name":"new-filter"}]'
41    spec:
42      serviceAccountName: httpbin
43      containers:
44        - image: docker.io/kennethreitz/httpbin
45          imagePullPolicy: IfNotPresent
46          name: httpbin
47          ports:
48            - containerPort: 80

查看:

 1[root@master1 wasm-go-demo]#kubectl get po  httpbin-59b9d799cd-hrtbh -oyaml
 2……
 3 - mountPath: /var/local/wasm
 4      name: new-filter
 5……
 6  - configMap:
 7      defaultMode: 420
 8      name: new-filter
 9    name: new-filter
10……    
11
12[root@master1 wasm-go-demo]#kubectl exec  httpbin-59b9d799cd-hrtbh -c istio-proxy -- ls /var/local/wasm
13new-filter.wasm
  • 部署完成后接下来我们先访问下 httpbin 服务,看下是否正常:
 1[root@master1 wasm-go-demo]#export GATEWAY_URL=$(kubectl get po -l istio=ingressgateway -n istio-system -o 'jsonpath={.items[0].status.hostIP}'):$(kubectl get svc istio-ingressgateway -n istio-system -o 'jsonpath={.spec.ports[?(@.name=="http2")].nodePort}')
 2[root@master1 wasm-go-demo]#curl -v http://$GATEWAY_URL/status/418
 3* About to connect() to 172.29.9.63 port 31666 (#0)
 4*   Trying 172.29.9.63...
 5* Connected to 172.29.9.63 (172.29.9.63) port 31666 (#0)
 6> GET /status/418 HTTP/1.1
 7> User-Agent: curl/7.29.0
 8> Host: 172.29.9.63:31666
 9> Accept: */*
10> 
11< HTTP/1.1 418 Unknown
12< server: istio-envoy
13< date: Tue, 12 Dec 2023 23:12:38 GMT
14< x-more-info: http://tools.ietf.org/html/rfc2324
15< access-control-allow-origin: *
16< access-control-allow-credentials: true
17< content-length: 135
18< x-envoy-upstream-service-time: 11
19< 
20
21    -=[ teapot ]=-
22
23       _...._
24     .'  _ _ `.
25    | ."` ^ `". _,
26    \_;`"---"`|//
27      |       ;/
28      \_     _/
29        `"""`
30* Connection #0 to host 172.29.9.63 left intact
31[root@master1 wasm-go-demo]#
  • 上面我们编写的插件逻辑是当我们访问 http://service/banana/X 时,会将请求重定向到 http://service/status/X,所以我们可以使用下面的命令来测试下:
 1[root@master1 wasm-go-demo]#curl -v http://$GATEWAY_URL/banana/418
 2* About to connect() to 172.29.9.63 port 31666 (#0)
 3*   Trying 172.29.9.63...
 4* Connected to 172.29.9.63 (172.29.9.63) port 31666 (#0)
 5> GET /banana/418 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.63:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 404 Not Found
11< server: istio-envoy
12< date: Tue, 12 Dec 2023 23:13:09 GMT
13< content-type: text/html
14< content-length: 233
15< access-control-allow-origin: *
16< access-control-allow-credentials: true
17< x-envoy-upstream-service-time: 25
18< 
19<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
20<title>404 Not Found</title>
21<h1>Not Found</h1>
22<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>
23* Connection #0 to host 172.29.9.63 left intact
24[root@master1 wasm-go-demo]#

从结果可以看到,我们定义的插件并没有生效,其实也能预料到,现在我们只是将编译后的 WASM 文件挂载到了 Envoy 中,但是 Envoy 并不知道这个文件是用来做什么的,或者说 Envoy 并不知道要将这个文件当成 WASM 插件来使用。

1$ kubectl get pods -l app=httpbin
2NAME                       READY   STATUS    RESTARTS   AGE
3httpbin-55db5999b4-qtlcg   2/2     Running   0          8m23s
4$ kubectl exec -it httpbin-55db5999b4-qtlcg -c istio-proxy -- ls /var/local/wasm
5new-filter.wasm

img

这个时候我们还需要使用到一个名为 EnvoyFilter 的 CRD 资源对象,EnvoyFilter 提供了一种机制,可以自定义 Istio Pilot 生成的 Envoy 配置,使用 EnvoyFilter 可以修改某些字段的值、添加特定的过滤器,甚至添加全新的监听器、集群等。需要注意的是这个功能必须小心使用,因为不正确的配置可能会导致整个网格不稳定。与其他 Istio 网络对象不同,EnvoyFilters 是附加应用的。对于特定命名空间中给定工作负载来说,可以存在任意数量的 EnvoyFilters,这些 EnvoyFilters 的应用顺序如下:首先是配置根命名空间中所有的 EnvoyFilters,然后是工作负载所在命名空间中匹配到的所有 EnvoyFilters

  • 比如我们可以使用下面的配置来将我们的 WASM 插件挂载到 Envoy 中:
 1# httpbin-wasm-filter.yaml
 2apiVersion: networking.istio.io/v1alpha3
 3kind: EnvoyFilter
 4metadata:
 5  name: httpbin-wasm-filter
 6spec:
 7  workloadSelector:
 8    labels:
 9      app: httpbin
10  configPatches:
11    - applyTo: HTTP_FILTER
12      match:
13        context: SIDECAR_INBOUND # 仅对入站流量进行过滤
14        listener:
15          filterChain:
16            filter:
17              name: envoy.filters.network.http_connection_manager
18              subFilter:
19                name: envoy.filters.http.router
20      patch:
21        operation: INSERT_BEFORE # 在 router 之前插入
22        value:
23          name: mydummy
24          typed_config:
25            "@type": type.googleapis.com/udpa.type.v1.TypedStruct
26            type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
27            value:
28              config:
29                configuration:
30                  "@type": type.googleapis.com/google.protobuf.StringValue
31                  value: dummy
32                root_id: "regex_replace"
33                vm_config:
34                  code:
35                    local:
36                      filename: /var/local/wasm/new-filter.wasm
37                  runtime: envoy.wasm.runtime.v8
38                  vm_id: myvmdummy

上面的 EnvoyFilter 对象中,首先我们通过 workloadSelector 来指定要对哪些 Pod 进行过滤,这里我们指定了 app: httpbin,表示只对 httpbin 这个 Pod 进行过滤,然后主要关注 configPatches 字段,这个字段用于配置 Envoy 的过滤器,其中的 match 字段用于匹配 Envoy 的过滤器,这里我们匹配的是 envoy.filters.network.http_connection_manager,然后 patch 字段用于指定要挂载的 WASM 插件,在 value.config 中指定了插件的配置信息以及 WASM 插件的路径。

  • 然后直接应用上面的这个资源对象即可:
1kubectl apply -f httpbin-wasm-filter.yaml
  • 部署完成后我们可以先查看 httpbin 应用的 sidecar 日志:
1$ kubectl logs -f httpbin-55db5999b4-qtlcg -c istio-proxy
2# ......
32023-12-08T07:48:03.605802Z     warning envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1151 wasm log: regex/main.go main() REACHED  thread=25
42023-12-08T07:48:03.606902Z     warning envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1151 wasm log regex_replace myvmdummy: read plug-in config: dummy
5        thread=25
62023-12-08T07:48:03.729847Z     info    Readiness succeeded in 1.025422807s
72023-12-08T07:48:03.730148Z     info    Envoy proxy is ready
  • 正常现在就可以看到上面我们在插件中添加的一些日志了,然后我们再来测试下 httpbin 服务,访问 http://service/banana/X,看下是否能够正常重定向到 http://service/status/X
 1[root@master1 wasm-go-demo]#curl -v http://$GATEWAY_URL/banana/418
 2* About to connect() to 172.29.9.63 port 31666 (#0)
 3*   Trying 172.29.9.63...
 4* Connected to 172.29.9.63 (172.29.9.63) port 31666 (#0)
 5> GET /banana/418 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.63:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 418 Unknown
11< server: istio-envoy
12< date: Tue, 12 Dec 2023 23:50:01 GMT
13< x-more-info: http://tools.ietf.org/html/rfc2324
14< access-control-allow-origin: *
15< access-control-allow-credentials: true
16< content-length: 135
17< x-envoy-upstream-service-time: 9
18< who-am-i: go-wasm-demo
19< injected-by: istio-api!
20< site: youdianzhishi.com
21< author: 阳明
22< configdata: dummy
23< 
24
25    -=[ teapot ]=-
26
27       _...._
28     .'  _ _ `.
29    | ."` ^ `". _,
30    \_;`"---"`|//
31      |       ;/
32      \_     _/
33        `"""`
34* Connection #0 to host 172.29.9.63 left intact
35[root@master1 wasm-go-demo]#

从上面的结果可以看到,我们的插件已经生效了,当我们访问 http://service/banana/X 时,会将请求重定向到 http://service/status/X,并且在响应头中添加了我们定义的一些 Header。到这里我们就实现了一个简单的 WASM 插件,当然这个插件只是一个简单的示例,实际上我们可以实现更加复杂的逻辑,比如可以实现自定义认证、自定义日志、自定义路由等等。

测试结束。😘

使用WasmPlugin部署Wasm插件

需要注意的是这里我们的部署方式是创建一个包含已编译的 Wasm 插件的 ConfigMap,将 ConfigMap 挂载到 Pod 的 Envoy Sidecar 中去,然后通过 EnvoyFilter 配置 Envoy,从本地文件加载 Wasm 插件。这种方法的确可以实现我们的需求,但是配置 EnvoyFilter 对象有点复杂,功能丰富的 Wasm 插件可能超出 ConfigMap 1MB 的大小限制

为了解决这个问题,Istio 便引入了一个新的用于自定义 Wasm 插件对 Istio 代理功能进行扩展的新顶层 API - WasmPlugin CRD,不再需要使用 EnvoyFilter 资源向代理添加自定义 Wasm 模块,取而代之的是使用 WasmPlugin 资源:

 1apiVersion: extensions.istio.io/v1alpha1
 2kind: WasmPlugin
 3metadata:
 4  name: your-filter
 5spec:
 6  selector:
 7    matchLabels:
 8      app: server
 9  phase: AUTHN
10  priority: 10
11  pluginConfig:
12    someSetting: true
13    someOtherSetting: false
14    youNameIt:
15      - first
16      - second
17  url: docker.io/your-org/your-filter:1.0.0

WasmPluginEnvoyFilter 之间有不少相似的地方,但也存在一些不同之处。比如在上面的示例中是将 Wasm 模块部署到与 selector 字段匹配的所有工作负载 —— 这与 EnvoyFilter 是完全相同的。

接下来的字段是 phase,该字段决定了 Wasm 模块将被注入到代理过滤器链中的哪个位置。我们为其定义了四个不同的阶段:

  • AUTHN:在所有 Istio 身份验证和授权过滤器之前。
  • AUTHZ:在 Istio 身份验证过滤器之后以及所有一级授权过滤器之前,即应用于 AuthorizationPolicy 资源之前。
  • STATS:在所有授权过滤器之后,以及 Istio 统计过滤器之前。
  • UNSPECIFIED_PHASE:让控制平面决定注入的位置,通常位于过滤器链的末端,也就是在路由之前。这也是该 phase 字段的默认值。

pluginConfig 字段用于 Wasm 插件的具体配置。在此字段中输入的任何内容都将通过 JSON 格式进行编码并传递到过滤器中,我们可以在 Proxy-Wasm SDK 的配置回调中访问它,比如在 Go SDK 中的 OnPluginStart 回调中可以获取这些配置信息。

url 字段指定了 Wasm 模块的拉取位置,这里的 url 是一个 docker URI,除了通过 HTTP、HTTPS 和本地文件系统 (使用 file://)方式加载 Wasm 模块之外,还可以使用 OCI 镜像格式作为分发 Wasm 插件,这也是推荐的方式。

🚩 实战:使用WasmPlugin部署Wasm插件-2023.12.16(测试成功)

  • 测试环境
1k8s v1.27.6(containerd://1.6.20)(cni:flannel:v0.22.2)
2istio v1.19.3(--set profile=demo)
3
4tinygo version 0.30.0

实验软件:

链接:https://pan.baidu.com/s/1FdvpYnV5kfmbKLJgDCjk6Q?pwd=562j 提取码:562j 2023.12.16-Istio Wasm插件

1.构建镜像

接下来我们按照上面的要求在代码根目录中新建一个 Dockerfile,用来将我们的 Wasm 插件打包到 Docker 镜像中:

1# Dockerfile for building "compat" variant of Wasm Image Specification.
2# https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md
3
4FROM scratch
5
6COPY main.wasm ./plugin.wasm

scratch(空镜像)

  • 当然也可以将构建的动作放到 Dockerfile 中,进行多阶段构建font>:
 1[root@master1 wasm-go-demo]#pwd
 2/root/istio/wasm-go-demo
 3[root@master1 wasm-go-demo]#ls
 4go.mod  go.sum  httpbin-wasm-filter.yaml  main.go  main.wasm
 5[root@master1 wasm-go-demo]#vim Dockerfile
 6FROM tinygo/tinygo as build
 7WORKDIR /src
 8
 9COPY . .
10RUN go env -w GOPROXY=https://goproxy.cn,direct
11RUN tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go
12
13FROM scratch
14
15COPY --from=build /src/main.wasm ./plugin.wasm
 1FROM tinygo/tinygo as build
 2WORKDIR /src
 3
 4COPY . .
 5RUN go env -w GOPROXY=https://goproxy.cn,direct
 6RUN tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go
 7
 8FROM scratch
 9
10COPY --from=build /src/main.wasm ./plugin.wasm
  • 直接使用 docker build 命令来构建镜像即可,构建完成后推送到镜像仓库:
1docker build -t cnych/wasm-go-demo:v0.1 .
2
3docker.io/cnych/wasm-go-demo:v0.1

这里是需要docker环境的:(我这里手动安装了下docker环境)

自己这里构建时报错了,我使用老师的镜像就行。

奇怪,为什么会报错呢???

2.创建WasmPlugin 资源对象

接下来就可以使用 WasmPlugin 资源对象来部署我们的 WASM 插件了。

  • 创建如下所示的 WasmPlugin 资源对象:
 1# httpbin-wasm-plugin.yaml
 2apiVersion: extensions.istio.io/v1alpha1
 3kind: WasmPlugin
 4metadata:
 5  name: httpbin-wasm-plugin
 6  namespace: default
 7spec:
 8  selector:
 9    matchLabels:
10      app: httpbin
11  url: oci://docker.io/cnych/wasm-go-demo:v0.1
12  pluginConfig:
13    testConfig: abcd
14    website: youdianzhishi.com
15    listconfig:
16      - abc
17      - def
  • 先删除之前的 EnvoyFilter 资源:
1$ kubectl delete cm new-filter
2configmap "new-filter" deleted
3$ kubectl delete envoyfilter httpbin-wasm-filter
4envoyfilter.networking.istio.io "httpbin-wasm-filter" deleted

记得将 httpbin.yaml 中的 sidecar.istio.io/userVolumesidecar.istio.io/userVolumeMount 字段删除:

kubectl edit deployments.apps httpbin

 1apiVersion: apps/v1
 2kind: Deployment
 3metadata:
 4  name: httpbin
 5spec:
 6  selector:
 7    matchLabels:
 8      app: httpbin
 9      version: v1
10  template:
11    metadata:
12      labels:
13        app: httpbin
14        version: v1
15      # annotations: # 去掉这里的注解
16    spec:
17      serviceAccountName: httpbin
18      containers:
19        - image: docker.io/kennethreitz/httpbin
20          imagePullPolicy: IfNotPresent
21          name: httpbin
22          ports:
23            - containerPort: 80
  • 然后直接应用上面的 WasmPlugin 资源对象:
1$ kubectl apply -f httpbin-wasm-plugin.yaml
2$ kubectl get wasmplugins
3NAME                  AGE
4httpbin-wasm-plugin   5s
  • 部署完成后我们可以先查看 httpbin 应用的 sidecar 日志:
1$ kubectl logs -f httpbin-86869bccff-6wqdc -c istio-proxy
2# ......
32023-12-08T08:43:11.360127Z     warning envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1151 wasm log: regex/main.go main() REACHED  thread=25
42023-12-08T08:43:11.360757Z     warning envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1151 wasm log: regex/main.go OnPluginStart() thread=25
52023-12-08T08:43:11.360895Z     warning envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1151 wasm log: read plug-in config: {"listconfig":["abc","def"],"testConfig":"abcd","website":"youdianzhishi.com"}
6        thread=25
72023-12-08T08:43:12.692961Z     info    Readiness succeeded in 6.774251572s
82023-12-08T08:43:12.693263Z     info    Envoy proxy is ready

可以看到我们的插件已经生效了。

3.测试

  • 然后我们再来测试下 httpbin 服务,访问 http://service/banana/X,看下是否能够正常重定向到 http://service/status/X
 1[root@master1 wasm-go-demo]#curl -v http://$GATEWAY_URL/banana/418
 2* About to connect() to 172.29.9.63 port 31666 (#0)
 3*   Trying 172.29.9.63...
 4* Connected to 172.29.9.63 (172.29.9.63) port 31666 (#0)
 5> GET /banana/418 HTTP/1.1
 6> User-Agent: curl/7.29.0
 7> Host: 172.29.9.63:31666
 8> Accept: */*
 9> 
10< HTTP/1.1 418 Unknown
11< server: istio-envoy
12< date: Sat, 16 Dec 2023 02:33:44 GMT
13< x-more-info: http://tools.ietf.org/html/rfc2324
14< access-control-allow-origin: *
15< access-control-allow-credentials: true
16< content-length: 135
17< x-envoy-upstream-service-time: 5
18< who-am-i: go-wasm-demo
19< injected-by: istio-api!
20< site: youdianzhishi.com
21< author: 阳明
22< configdata: {"listconfig":["abc","def"],"testConfig":"abcd","website":"youdianzhishi.com"}
23< 
24
25    -=[ teapot ]=-
26
27       _...._
28     .'  _ _ `.
29    | ."` ^ `". _,
30    \_;`"---"`|//
31      |       ;/
32      \_     _/
33        `"""`
34* Connection #0 to host 172.29.9.63 left intact
35[root@master1 wasm-go-demo]#

从上面的结果可以看到结果是符合我们的预期的,证明我们的插件已经生效了。

测试结束。😘

性能数据

参考阿里云性能测试结果(仅供参考):

1000 并发 1000QPS 持续 10 秒钟基准WASMLUA
平均延迟0.6317 secs0.6395 secs0.7012 secs
延迟 99%分布0.9167 secs0.9352 secs1.1355 secs
QPS154115191390
Total16281161091390

相对于基准版本,增加 Wasm 插件的两个版本,平均延迟多出几十个到几百个毫秒,增加耗时比为:

  • wasm 1.2% (0.6395-0.6317)/0.6317和 1% (1.3290-1.2078)/1.2078
  • lua 11%(0.7012-0.6317)/0.6317和 20% (1.4593-1.2078)/1.2078

可以看出 WASM 版本的性能明显优于 LUA 版本。

关于我

我的博客主旨:

  • 排版美观,语言精炼;
  • 文档即手册,步骤明细,拒绝埋坑,提供源码;
  • 本人实战文档都是亲测成功的,各位小伙伴在实际操作过程中如有什么疑问,可随时联系本人帮您解决问题,让我们一起进步!

🍀 微信二维码 x2675263825 (舍得), qq:2675263825。

image-20230107215114763

🍀 微信公众号 《云原生架构师实战》

image-20230107215126971

🍀 个人博客站点

http://onedayxyy.cn/

🍀 语雀

https://www.yuque.com/xyy-onlyone

🍀 csdn

https://blog.csdn.net/weixin_39246554?spm=1010.2135.3001.5421

image-20230107215149885

🍀 知乎

https://www.zhihu.com/people/foryouone

image-20230107215203185

最后

好了,关于本次就到这里了,感谢大家阅读,最后祝大家生活快乐,每天都过的有意义哦,我们下期见!

推荐使用微信支付
微信支付二维码
推荐使用支付宝
支付宝二维码
最新文章

文档导航