Higress 实战:30 行代码写一个 Wasm Go插件

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
注册配置 MSE Nacos/ZooKeeper,182元/月
简介: 在 11 月 15 号的直播 《Higress 开源背后的发展历程和上手 Demo 演示》中,为大家演示了 Higress 的 Wasm 插件如何面向 Ingress 资源进行配置生效,本文对当天的 Demo 进行一个回顾,并说明背后的原理机制。

作者:澄潭、如葑


前言


在 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 应答。本文会按以下方式进行介绍:


  • 编写代码:代码逻辑解析
  • 生效插件:说明代码如何进行编译打包并部署生效
  • 测试插件功能:说明全局粒度,路由/域名级粒度如何生效
  • 插件生效原理:对整体流程进行回顾,说明插件生效的原理
  • 三个革命性的特性:介绍 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/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 引入的泛型特性封装了插件上下文处理细节,从而降低插件开发所需代码量,开发者只用关心配置解析和请求应答处理的逻辑。


生效插件


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


  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/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 访问拓扑如下所示:


1.png


未生效插件的情况下:


  • 请求/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,使用全局配置)


插件生效原理

2.png


这里对插件的生效机制简单做个说明:


  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/ingress-nginx/blob/main/rootfs/etc/nginx/lua/plugins/README.md


可以看到 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/developers/guide_dev.html


如有产品疑问,可以扫码立即加入群聊:


Higress社区交流3群

3.png

相关实践学习
Docker镜像管理快速入门
本教程将介绍如何使用Docker构建镜像,并通过阿里云镜像服务分发到ECS服务器,运行该镜像。
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
3月前
|
Linux Go iOS开发
Go语言100个实战案例-进阶与部署篇:使用Go打包生成可执行文件
本文详解Go语言打包与跨平台编译技巧,涵盖`go build`命令、多平台构建、二进制优化及资源嵌入(embed),助你将项目编译为无依赖的独立可执行文件,轻松实现高效分发与部署。
|
4月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
341 0
|
4月前
|
存储 人工智能 Go
Go-Zero全流程实战即时通讯
Go-Zero 是一个功能丰富的微服务框架,适用于开发高性能的即时通讯应用。它具备中间件、工具库和代码生成器,简化开发流程。本文介绍其环境搭建、项目初始化及即时通讯功能实现,涵盖用户认证、消息收发和实时推送,帮助开发者快速上手。
316 0
|
3月前
|
存储 前端开发 JavaScript
Go语言实战案例-项目实战篇:编写一个轻量级在线聊天室
本文介绍如何用Go语言从零实现一个轻量级在线聊天室,基于WebSocket实现实时通信,支持多人消息广播。涵盖前后端开发、技术选型与功能扩展,助你掌握Go高并发与实时通信核心技术。
|
4月前
|
负载均衡 监控 Java
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
在微服务架构中,高可用与稳定性至关重要。本文详解熔断、限流与负载均衡三大关键技术,结合API网关与Hystrix-Go实战,帮助构建健壮、弹性的微服务系统。
502 1
微服务稳定性三板斧:熔断、限流与负载均衡全面解析(附 Hystrix-Go 实战代码)
|
4月前
|
安全 Go 开发者
Go语言实战案例:使用sync.Mutex实现资源加锁
在Go语言并发编程中,数据共享可能导致竞态条件,使用 `sync.Mutex` 可以有效避免这一问题。本文详细介绍了互斥锁的基本概念、加锁原理及实战应用,通过构建并发安全的计数器演示了加锁与未加锁的区别,并封装了一个线程安全的计数器结构。同时对比了Go中常见的同步机制,帮助开发者理解何时应使用 `Mutex` 及其注意事项。掌握 `Mutex` 是实现高效、安全并发编程的重要基础。
|
4月前
|
数据采集 Go API
Go语言实战案例:使用context控制协程取消
本文详解 Go 语言中 `context` 包的使用,通过实际案例演示如何利用 `context` 控制协程的生命周期,实现任务取消、超时控制及优雅退出,提升并发程序的稳定性与资源管理能力。
|
4月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
4月前
|
Go 开发者
Go语言实战案例:使用select监听多个channel
本文为《Go语言100个实战案例 · 网络与并发篇》第5篇,详解Go并发核心工具`select`的使用。通过实际案例讲解如何监听多个Channel、实现多任务处理、超时控制和非阻塞通信,帮助开发者掌握Go并发编程中的多路异步事件处理技巧。
|
4月前
|
数据采集 编解码 监控
Go语言实战案例:使用channel实现生产者消费者模型
本文是「Go语言100个实战案例 · 网络与并发篇」第4篇,通过实战案例详解使用 Channel 实现生产者-消费者模型,涵盖并发控制、任务调度及Go语言并发哲学,助你掌握优雅的并发编程技巧。