关于阿里云:Higress-实战30-行代码写一个-Wasm-Go插件

0次阅读

共计 5849 个字符,预计需要花费 15 分钟才能阅读完成。

作者: 澄潭、如葑

前言

在 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…

https://github.com/alibaba/hi…

能够看到,Higress 的 wasm-go sdk 是通过 Go 1.18 引入的泛型个性封装了插件上下文解决细节,从而升高插件开发所需代码量,开发者只用关怀配置解析和申请应答解决的逻辑。

失效插件

编写实现代码后,一共有三个步骤,实现插件逻辑的失效:

  1. 编译:将 go 代码编译成 Wasm 格式文件
  2. 镜像推送:将 Wasm 文件打包成 docker 镜像,并推送至镜像仓库
  3. 下发配置:在 K8s 上创立 WasmPlugin 资源

编译

将下面的 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,配置阐明:

  • 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,应用全局配置)

插件失效原理

这里对插件的失效机制简略做个阐明:

  1. 用户将代码编译成 wasm 文件
  2. 用户将 wasm 文件构建成 docker 镜像
  3. 用户将 docker 镜像推送至镜像仓库
  4. 用户创立 WasmPlugin 资源
  5. Istio watch 到 WasmPlugin 资源的变动
  6. Higress Gateway 中的 xDS proxy 过程从 Istio 获取到配置,发现插件的镜像地址
  7. xDS proxy 从镜像仓库拉取镜像
  8. xDS proxy 从镜像中提取出 wasm 文件
  9. Higress Gateway 中的 envoy 过程从 xDS proxy 获取到配置,发现 wasm 文件的本地门路
  10. envoy 从本地文件中加载 wasm 文件 

这里 envoy 获取配置并加载 wasm 文件应用到了 ECDS (Extension Config Discovery Service) 的机制,实现了 wasm 文件更新,间接热加载,不会导致任何连贯中断,业务流量齐全无损。

三个革命性的个性

下面的 Wasm 插件机制为网关自定义插件开发带来了三个革命性的个性。

个性一:插件生命周期和网关解耦

这个个性次要得益于 Istio 的 WasmPlugin 机制设计。能够和 K8s Nginx Ingress 的插件机制做个比照:

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

https://github.com/kubernetes…

能够看到 Nginx Ingress 加载自定义插件,须要将 lua 文件挂载进 pod,或者在构建镜像时装入。这样就将插件的生命周期跟网关绑定在一起,插件逻辑更新,须要公布新版本,网关也须要公布新版本或者重新部署。

应用 WasmPlugin 的机制,插件须要公布新版本,只需构建插件本身的镜像并进行下发失效,而且能够基于镜像的 tag 进行插件的版本治理。这样插件变更,不仅无需重新部署网关,联合 Envoy 的 ECDS 机制对流量也是齐全无损。

个性二:高性能的多语言反对

基于 Wasm 的能力,能够用多种语言编写插件,对开发人员更加敌对。实现多语言开发插件的另一种形式是基于 RPC 和网关过程通信的外置过程 / 服务插件,这种模式会有额定的 IO 开销,并且附加的过程 / 服务也带来额定的运维复杂度。目前大家对 Wasm 插件的性能比较关心,从咱们的测试数据来看,指令执行性能相比原生的 C++ 语言的确有差距,但性能和 Lua 持平,且远好于外置插件。

对于一段逻辑:循环执行 20 次申请头设置,循环执行 20 次申请头获取,循环执行 20 次申请头移除。咱们比照了别离用 Lua 和不同语言实现的 Wasm 的解决性能,上面是对单个申请延时的影响比照:

实现语言 申请延时减少
Lua 0.20 毫秒
Wasm (C++) 0.19 毫秒
Wasm (Go) 0.20 毫秒
Wasm (Rust) 0.21 毫秒
Wasm (AssemblyScript) 0.21 毫秒

个性三:平安沙箱

Envoy 目前反对多种 Wasm 的运行时,例如 V8,WAMR,wasmtime 等等,这些运行时均提供了平安沙箱能力,即 Wasm 插件中呈现了拜访空指针、异样未捕捉等逻辑,也不会令 Envoy 宿主过程 Crash。并且能够通过配置,在插件逻辑出现异常后进行 Fail Open 解决,跳过插件的执行逻辑,将对业务的影响降至最低。

开源社区

特别感谢 Istio/Envoy 社区的前置工作,让 Higress 能够实现对 Ingress 资源启用 WasmPlugin,加强了 Ingress Controller 的自定义扩大能力。

特别感谢 Tetrate 社区实现的 proxy-wasm-go-sdk,Higress 在这个根底上封装了 wasm-go sdk,升高了开发插件的上手门槛。

Higress 对 Istio/Envoy 的 Wasm 能力做了一些 Bugfix 的工作,目前曾经都合并进了上游社区。后续的一些 Feature 能力,也会继续反哺上游社区。

同时欢送大家一起为 Higress 的插件以及其余社区生态添砖加瓦,为 Higress 奉献请参考文档:

https://higress.io/zh-cn/docs…

如有产品疑难,能够扫码立刻退出群聊:

Higress 社区交换 3 群

正文完
 0