WebAssembly For Proxies是一种新插件规范,它允许开发者使用WebAssembly编写可移植插件,并且在各种代理服务器中运行。ASM支持WebAssembly For Proxies规范。本文介绍如何在ASM中使用Golang为网格代理编写Wasm插件。
前提条件
已添加集群到ASM实例,且ASM实例版本为1.18及以上。
已启用Sidecar注入。具体操作,请参见配置Sidecar注入策略。
已创建入口网关。
已部署httpbin应用,且可以正常访问。
已创建阿里云容器镜像服务企业版(企业版实例支持OCI镜像)。具体操作,请参见创建企业版实例。
背景信息
WebAssembly(Wasm)是一种新兴的可移植的二进制可执行文件格式。这些代码将会在一个内存安全(对于主机)的沙箱中以接近本机的运行速度执行,使用的资源严格受限,且通过明确定义的API与主机通信。
使用Wasm插件有以下好处:
敏捷性:无需重启Envoy就可以完成插件二进制更新,不会影响当前请求处理。
可靠性和隔离性:插件运行在沙箱内,即使插件崩溃,Envoy本身不会受到影响。
安全性:Proxy沙箱提供的API很明确,插件行为可控。
多样性:支持使用多种语言编写插件(C++、Go、Rust等)。
关于Wasm插件的更多信息,请参见WebAssembly-in-Envoy.md和OVERVIEW.md。
示例介绍
本文将编写一个基于Go语言的Wasm插件,编写完成后生成Wasm二进制文件,然后打包到镜像中。该镜像需要上传至镜像服务的OCI镜像仓库。上传之后,在ASM中配置WasmPlugin资源,将该插件应用至指定的网格代理上。
本文将开发一个插件用于判断请求中是否存在allow: true
Header。如果不存在,返回403和指定的body;如果存在,则正常访问httpbin应用。
步骤一:开发环境准备
使用Go语言开发Envoy的Wasm插件需要提前安装以下工具:
Go:编写Go语言项目依赖Go编译器及相关工具,详情请参见The Go Programming Language。
Docker:本文主要使用Docker构建和推送OCI镜像。
TinyGo:编写本项目依赖Go语言工具,但是Go代码编译成Wasm并不能使用Go官方提供的编译器,还需要使用TinyGo,详情请参见Quick install guide。
关于Wasm插件依赖的SDK,请参见proxy-wasm-go-sdk。本文将提供完整的代码,如果您需要使用其他SDK能力,请自行参考该项目。
步骤二:编写插件代码
新建一个文件夹,使用以下内容,创建main.go文件。
在新建的文件夹下执行以下命令,获取SDK相关依赖。
go mod init go mod tidy
执行以下命令,编译代码得到Wasm二进制文件。
tinygo build -o plugin.wasm -scheduler=none -target=wasi main.go
您可以看到生成一个plugin.wasm文件。该文件即为Wasm的二进制可执行文件。
步骤三:制作OCI镜像并推送至阿里云容器镜像服务
在步骤二新建的文件夹下,使用以下内容,创建Dockerfile文件。
FROM scratch ADD ./plugin.wasm ./plugin.wasm
执行以下命令,制作镜像。
docker build -t header-authorization:v0.0.1 .
创建镜像仓库。具体操作,请参见使用Coraza Wasm插件在ASM网关上实现WAF能力的步骤一.2的a、b步骤。
本文使用的命名空间为
test-oci
,仓库名称为header-authorization
。创建完之后界面显示如下:您可以参考图中的将镜像推送到Registry步骤完成镜像推送。
步骤四:将Wasm插件应用在网关上
配置镜像拉取权限。具体操作,请参见步骤二:配置镜像拉取权限。
本文使用的Secret名称为
wasm-secret
,具体命令如下:kubectl create secret docker-registry -n istio-system wasm-secret --docker-server=${镜像服务实例域名} --docker-username=${用户名} --docker-password=${密码}
使用以下内容,创建asm-plugin.yaml。
apiVersion: extensions.istio.io/v1alpha1 kind: WasmPlugin metadata: name: header-authorization namespace: istio-system spec: imagePullPolicy: IfNotPresent imagePullSecret: wasm-secret selector: matchLabels: istio: ingressgateway url: oci://${镜像服务实例域名}/test-oci/header-authorization:v0.0.1 phase: AUTHN
在ASM实例对应的KubeConfig环境下,执行以下命令,将WasmPlugin应用到ASM实例。
kubectl apply -f wasm-plugin.yaml
步骤五:验证插件是否生效
使用网关所在数据面集群的KubeConfig,执行以下命令,开启网关的Wasm组件debug日志。
kubectl -n istio-system exec ${网关pod名称} -c istio-proxy -- curl -XPOST "localhost:15000/logging?wasm=debug"
执行以下命令,访问网关的httpbin应用。
curl ${ASM网关IP}/status/418
预期输出:
Forbidden by ASM Wasm Plugin
查看网关Pod日志。
日志示例如下:
2024-03-08T08:16:46.747394Z debug envoy wasm external/envoy/source/extensions/common/wasm/context.cc:1168 wasm log istio-system.header-authorization: request header: 'allow' is , only true can passthrough thread=24 {"bytes_received":"0","bytes_sent":"28","downstream_local_address":"xxxxxxx","downstream_remote_address":"xxxxxxxx","duration":"0","istio_policy_status":"-","method":"GET","path":"/status/418","protocol":"HTTP/1.1","request_id":"780c8493-13e4-4f97-9771-486efe30347c","requested_server_name":"-","response_code":"403","response_flags":"-","route_name":"httpbin","start_time":"2024-03-08T08:16:46.747Z","trace_id":"-","upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local","upstream_host":"-","upstream_local_address":"-","upstream_service_time":"-","upstream_response_time":"-","upstream_transport_failure_reason":"-","user_agent":"curl/8.4.0","x_forwarded_for":"xxxxxx","authority_for":"xxxxxx"}
执行以下命令,访问网关的httpbin应用。
curl ${ASM网关IP}/status/418 -H "allow: true"
预期输出:
-=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`
预期输出表明可以正常访问httpbin应用。
TinyGo的内存泄漏问题说明
当前TinyGo编译出的Envoy Wasm插件存在内存泄露问题。proxy-wasm-go-sdk社区目前建议使用nottinygc进行编译优化。具体操作步骤如下:
在main.go文件开头添加如下
import
代码。import _ "github.com/wasilibs/nottinygc"
如果提示没有相关依赖,可以运行
go mod tidy
命令自动下载。执行以下命令,进行编译。
tinygo build -o plugin.wasm -gc=custom -tags='custommalloc nottinygc_envoy' -target=wasi -scheduler=none main.go
以上命令主要设置了
-gc
、-tags
两个选项。更多信息,请参见nottinygc。