在11月15号的直播 《Higress 开源背后的发展历程和上手 Demo 演示》中,为大家演示了 Higress 的 Wasm 插件如何面向 Ingress 资源进行配置生效,本文对当天的 Demo 进行一个回顾,并说明背后的原理机制。
本文中 Demo 运行的前提,需要在 K8s 集群中安装了 Higress,并生效了下面这份 quickstart 配置:
https://github.com/alibaba/higress/releases/download/v0.5.2/quickstart.yaml
这个 Demo 要实现的功能是一个 Mock 应答的功能,需要实现根据配置的内容,返回 HTTP 应答。
本文会按以下方式进行介绍:
package main
import (
. "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
)
func main() {
SetCtx(
"my-plugin",
ParseConfigBy(parseConfig),
ProcessRequestHeadersBy(onHttpRequestHeaders),
)
}
type MyConfig struct {
content string
}
func parseConfig(json gjson.Result, config *MyConfig, log Log) error {
config.content = json.Get("content").String()
return nil
}
func onHttpRequestHeaders(ctx HttpContext, config MyConfig, log Log) types.Action {
proxywasm.SendHttpResponse(200, nil, []byte(config.content), -1)
return types.ActionContinue
}
上面代码中可以看到三个函数:
传入的三个参数分别是:
这个 30 行代码实现的插件功能比较简单,这里有一些功能相对复杂的例子:https://github.com/alibaba/higress/tree/main/plugins/wasm-go/extensions
这里有插件 sdk 的详细使用文档:
https://higress.io/zh-cn/docs/user/wasm-go.html
这个插件 sdk 是基于 Tetrate 社区的 proxy-wasm-go-sdk 实现的,如果关注更底层的细节,可以查看:
https://github.com/tetratelabs/proxy-wasm-go-sdk
https://github.com/alibaba/higress/blob/main/plugins/wasm-go/pkg/wrapper
可以看到,Higress 的 wasm-go sdk 是通过 Go 1.18 引入的泛型特性封装了插件上下文处理细节,从而降低插件开发所需代码量,开发者只用关心配置解析和请求应答处理的逻辑。
编写完成代码后,一共有三个步骤,实现插件逻辑的生效:
将上面的 Go 文件 main.go 编译成 plugin.wasm
tinygo build -o plugin.wasm -scheduler=none -target=wasi main.go
编写 Dockerfile
FROM scratch
COPY plugin.wasm ./
构建并推送 Docker 镜像 (这里示例用的是 Higress 的官方镜像仓库)
docker build -t higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/demo:1.0.0 .
docker push higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/demo:1.0.0
编写 wasmplugin.yaml,配置说明:
除了这些配置外,还可以定义插件的执行阶段和优先级等进阶配置,可以参考 Istio API 官方文档:https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/
# wasmplugin.yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: mock-response
namespace: higress-system
spec:
selector:
matchLabels:
higress: higress-system-higress-gateway
pluginConfig:
content: "hello higress"
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/demo:1.0.0
通过 kubectl 创建这个资源
kubectl apply -f wasmplugin.yaml
基于之前生效的 quickstart.yaml,目前集群中的 Ingress 访问拓扑如下所示:
未生效插件的情况下:
/foo
将返回 HTTP 应答 "foo"
/bar
将返回 HTTP 应答 "bar"
基于上文生效插件阶段,下发的 wasmplugin.yaml,生效插件后效果如下:
/foo
将返回 HTTP 应答 "hello higress"
/bar
将返回 HTTP 应答 "hello higress"
将 wasmplugin.yaml 配置修改如下
# wasmplugin.yaml
apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
name: mock-response
namespace: higress-system
spec:
selector:
matchLabels:
higress: higress-system-higress-gateway
pluginConfig:
content: "hello higress"
_rules_:
- content: "hello foo"
_match_route_:
- "default/foo"
- content: "hello bar"
_match_route_:
- "default/bar"
- content: "hello world"
_match_domain_:
- "*.example.com"
- "www.test.com"
url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/demo:1.0.0
在 pluginConfig 中增加了 _rules_
规则列表,规则中可以指定匹配方式,并填写对应生效的配置:
生效这份修改后的配置:
kubectl apply -f wasmplugin.yaml
可以看到效果如下:
/foo
将返回 HTTP 应答 "hello foo"
(匹配到第一条 rule)/bar
将返回 HTTP 应答 "hello bar"
(匹配到第二条 rule)www.example.com
将返回 HTTP 应答 "hello world"
(匹配到第三条 rule)www.abc.com
将返回 HTTP 应答 "hello higress"
(没有匹配的 rule,使用全局配置)这里对插件的生效机制简单做个说明:
这里 envoy 获取配置并加载 wasm 文件使用到了 ECDS (Extension Config Discovery Service)的机制,实现了 wasm 文件更新,直接热加载,不会导致任何连接中断,业务流量完全无损。
上面的 Wasm 插件机制为网关自定义插件开发带来了三个革命性的特性。
这个特性主要得益于 Istio 的 WasmPlugin 机制设计。可以和 K8s Nginx Ingress 的插件机制做个对比:
reference: https://github.com/kubernetes/ingress-nginx/blob/main/rootfs/etc/nginx/lua/plugins/README.md
Installing a plugin
There are two options:
mount your plugin into /etc/nginx/lua/plugins/in the ingress-nginx pod
build your own ingress-nginx image like it is done in the example and install your plugin during image build
可以看到 Nginx Ingress 加载自定义插件,需要将 lua 文件挂载进 pod,或者在构建镜像时装入。这样就将插件的生命周期跟网关绑定在一起,插件逻辑更新,需要发布新版本,网关也需要发布新版本或者重新部署。
使用 WasmPlugin 的机制,插件需要发布新版本,只需构建插件自身的镜像并进行下发生效,而且可以基于镜像的 tag 进行插件的版本管理。这样插件变更,不仅无需重新部署网关,结合 Envoy 的 ECDS 机制对流量也是完全无损。
基于 Wasm 的能力,可以用多种语言编写插件,对开发人员更加友好。实现多语言开发插件的另一种方式是基于 RPC 和网关进程通信的外置进程/服务插件,这种模式会有额外的 IO 开销,并且附加的进程/服务也带来额外的运维复杂度。目前大家对 Wasm 插件的性能比较关心,从我们的测试数据来看,指令执行性能相比原生的 C++ 语言确实有差距,但性能和 Lua 持平,且远好于外置插件。
对于一段逻辑:循环执行20次请求头设置,循环执行20次请求头获取,循环执行20次请求头移除。
我们对比了分别用 Lua 和不同语言实现的 Wasm 的处理性能,下面是对单个请求延时的影响对比:
Envoy 目前支持多种 Wasm 的运行时,例如 V8,WAMR,wasmtime 等等,这些运行时均提供了安全沙箱能力,即 Wasm 插件中出现了访问空指针、异常未捕获等逻辑,也不会令 Envoy 宿主进程 Crash。并且可以通过配置,在插件逻辑出现异常后进行 Fail Open 处理,跳过插件的执行逻辑,将对业务的影响降至最低。
原文链接
本文为阿里云原创内容,未经允许不得转载。