前言
在 11 月 15 号的直播《Higress 开源背地的倒退历程和上手 Demo 演示》中,为大家演示了 Higress 的 Wasm 插件如何面向 Ingress 资源进行配置失效,本文对当天的 Demo 进行一个回顾,并阐明背地的原理机制。
本文中 Demo 运行的前提,须要在 K8s 集群中装置了 Higress,并失效了上面这份 quickstart 配置:https://github.com/alibaba/hi…
这个 Demo 要实现的性能是一个 Mock 应答的性能,须要实现依据配置的内容,返回 HTTP 应答。
本文会按以下形式进行介绍:
- 编写代码:代码逻辑解析
- 失效插件:阐明代码如何进行编译打包并部署失效
- 测试插件性能:阐明全局粒度,路由 / 域名级粒度如何失效
- 插件失效原理:对整体流程进行回顾,阐明插件失效的原理
- 三个革命性的个性:介绍 Wasm 插件机制为网关插件开发带来的改革
编写代码 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
}
下面代码中能够看到三个函数:
- main:插件通过 main 函数定义插件上下文,包含插件名称,用于解析配置的函数,以及用于解决申请 / 应答的函数
- parseConfig:这个函数通过在 SetCtx 中指定的 ParseConfigBy 被挂载到插件配置解析阶段,传入的三个参数别离是:
- json:传入插件的配置,将对立序列化为一个 json 字典对象,提供 parseConfig 进行解析
- config:parseConfig 将解析后的插件配置输入到这个 MyConfig 对象
- log:提供日志输入接口
- onHttpRequestHeaders:函数中调用的 proxywasm.SendHttpResponse,用于实现间接返回 HTTP 应答,这个函数通过在 SetCtx 中指定的 ProcessRequestHeadersBy 被挂载到解析申请 Header 的执行阶段,其余的挂载形式还有:
- ProcessRequestBodyBy:挂载到解析申请 Body 的执行阶段
- ProcessResponseHeadersBy:挂载到结构应答 Header 的执行阶段
- ProcessResponseBodyBy:挂载到结构应答 Body 的执行阶段
传入的三个参数别离是:
ctx:用于获取申请上下文,如 scheme/method/path 等,通过 ctx 能够设置自定义上下文,能跨执行阶段拜访
config:提供 parseConfig 解析好的自定义配置
log:提供日志输入接口
这个 30 行代码实现的插件性能比较简单,这里有一些性能绝对简单的例子:https://github.com/alibaba/hi…
这里有插件 sdk 的具体应用文档:https://higress.io/zh-cn/docs…
这个插件 sdk 是基于 Tetrate 社区的 proxy-wasm-go-sdk 实现的,如果关注更底层的细节,能够查看:https://github.com/tetratelab…
能够看到,Higress 的 wasm-go sdk 是通过 Go 1.18 引入的泛型个性封装了插件上下文解决细节,从而升高插件开发所需代码量,开发者只用关怀配置解析和申请应答解决的逻辑。
失效插件
编写实现代码后,一共有三个步骤,实现插件逻辑的失效:
- 编译:将 go 代码编译成 Wasm 格式文件
- 镜像推送:将 Wasm 文件打包成 docker 镜像,并推送至镜像仓库
- 下发配置:在 K8s 上创立 WasmPlugin 资源
编译
将下面的 Go 文件 main.go 编译成 plugin.wasm
tinygo build -o plugin.wasm -scheduler=none -target=wasi main.go
镜像推送
编写 Docker file
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,配置阐明:
- selector:选中了默认装置在 higress-system 命名空间下的 higress-gateway 失效这份插件
- pluginConfig:插件配置,最终会被转换成下面代码中的 MyConfig 对象
- url:填写镜像地址,须要以 ”oci://” 结尾
除了这些配置外,还能够定义插件的执行阶段和优先级等进阶配置,能够参考 Istio API 官网文档:https://istio.io/latest/docs/…
# 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_ 规定列表,规定中能够指定匹配形式,并填写对应失效的配置:
- _match_route_:匹配 Ingress 失效,匹配格局为:Ingress 所在命名空间 + “/” + Ingress 名称
- _match_domain_:匹配域名失效,填写域名即可,反对通配符
失效这份批改后的配置:
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,应用全局配置)
插件失效原理
这里对插件的失效机制简略做个阐明:
- 用户将代码编译成 wasm 文件
- 用户将 wasm 文件构建成 docker 镜像
- 用户将 docker 镜像推送至镜像仓库
- 用户创立 WasmPlugin 资源
- Istio watch 到 WasmPlugin 资源的变动
- Higress Gateway 中的 xDS proxy 过程从 Istio 获取到配置,发现插件的镜像地址
- xDS proxy 从镜像仓库拉取镜像
- xDS proxy 从镜像中提取出 wasm 文件
- Higress Gateway 中的 envoy 过程从 xDS proxy 获取到配置,发现 wasm 文件的本地门路
- envoy 从本地文件中加载 wasm 文件
这里 envoy 获取配置并加载 wasm 文件应用到了 ECDS (Extension Config Discovery Service)的机制,实现了 wasm 文件更新,间接热加载,不会导致任何连贯中断,业务流量齐全无损。
三个革命性的个性
下面的 Wasm 插件机制为网关自定义插件开发带来了三个革命性的个性。
个性一:插件生命周期和网关解耦
这个个性次要得益于 Istio 的 WasmPlugin 机制设计。能够和 K8s Nginx Ingress 的插件机制做个比照:
reference: https://github.com/kubernetes…
Installing a plugin
There are two options:
- mount your plugin into /etc/nginx/lua/plugins/<your plugin name> 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 解决,跳过插件的执行逻辑,将对业务的影响降至最低。
原文链接
本文为阿里云原创内容,未经容许不得转载。