我正在尝试运行我的WASM Go过滤器,以便使用net/HTTP模块进行外部HTTP调用。Envoy无法加载WASM代码。为什么导入失败?
Envoy/Istio版本:Istio/proxyv2:1.11.4
SDK版本:v0.16.1-0.20220127085108-af57b89bc067
TinyGo版本:TinyGo版本0.22.0 darwin/amd64(使用go版本go1.17.6和LLVM版本13.0.0(
错误日志
2022-01-31T20:34:18.513749Z error envoy wasm Failed to load Wasm module due to a missing import: env.time.resetTimer
2022-01-31T20:34:18.513794Z error envoy wasm Failed to load Wasm module due to a missing import: env.time.stopTimer
2022-01-31T20:34:18.513807Z error envoy wasm Failed to load Wasm module due to a missing import: env.time.startTimer
2022-01-31T20:34:18.513817Z error envoy wasm Failed to load Wasm module due to a missing import: env.sync/atomic.AddInt32
2022-01-31T20:34:18.513826Z error envoy wasm Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_filestat_get
2022-01-31T20:34:18.513833Z error envoy wasm Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_pread
2022-01-31T20:34:18.513840Z error envoy wasm Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_prestat_get
2022-01-31T20:34:18.513846Z error envoy wasm Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.fd_prestat_dir_name
2022-01-31T20:34:18.513854Z error envoy wasm Failed to load Wasm module due to a missing import: wasi_snapshot_preview1.path_open
2022-01-31T20:34:18.513864Z error envoy wasm Wasm VM failed Failed to initialize Wasm code
2022-01-31T20:34:18.517062Z critical envoy wasm Plugin configured to fail closed failed to load
2022-01-31T20:34:18.517191Z warning envoy config gRPC config for type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig rejected: Unable to create Wasm HTTP filter
tinygo build -o main.wasm -scheduler=asyncify -target=wasi main.go
实际代码
package main
import (
"errors"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
"github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
"io/ioutil"
"time"
"net/http"
)
const (
sharedDataKey = "hello_world_shared_data_key"
)
func main() {
proxywasm.SetVMContext(&vmContext{})
}
type (
vmContext struct{}
pluginContext struct {
// Embed the default plugin context here,
// so that we don't need to reimplement all the methods.
types.DefaultPluginContext
}
httpContext struct {
// Embed the default http context here,
// so that we don't need to reimplement all the methods.
types.DefaultHttpContext
}
)
// Override types.VMContext.
func (*vmContext) OnVMStart(vmConfigurationSize int) types.OnVMStartStatus {
proxywasm.LogInfo("Inside OnVMStart")
http := http.Client{Timeout: time.Duration(10) * time.Second}
resp, err := http.Get("http://SOME_URL:8001/echo?message=hello_world")
if err != nil {
proxywasm.LogWarnf("Error calling hello_world/echo on OnVMStart: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
proxywasm.LogWarnf("Error parsing hello_world/echo response on OnVMStart: %v", err)
}
proxywasm.LogInfof("Response Body : %s", body)
initialValueBuf := []byte("body")
if err := proxywasm.SetSharedData(sharedDataKey, initialValueBuf, 0); err != nil {
proxywasm.LogWarnf("Error setting shared hello_world data on OnVMStart: %v", err)
}
return types.OnVMStartStatusOK
}
// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}
// Override types.DefaultPluginContext.
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &httpContext{}
}
// Override types.DefaultHttpContext.
func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
for {
value, err := ctx.getSharedData()
if err == nil {
proxywasm.LogInfof("shared data value: %s", value)
} else if errors.Is(err, types.ErrorStatusCasMismatch) {
continue
}
break
}
return types.ActionContinue
}
func (ctx *httpContext) getSharedData() (string, error) {
value, cas, err := proxywasm.GetSharedData(sharedDataKey)
if err != nil {
proxywasm.LogWarnf("error getting shared data on OnHttpRequestHeaders with cas %d: %v ", cas, err)
return "error", err
}
shared_value := string(value)
return shared_value, err
}
不幸的是,这并不容易。
TinyGo可能支持该模块,但您不能"只是";为Envoy使用WASM模块时,调用一些任意的API。
更确切地说,WASM模块在沙箱中运行,并且只能进行运行时明确允许的调用。在Envoy的情况下,wasm代理sdk提供了一种简单的机制来调用这些API。
proxy-wasm-go-sdk
提供了这些您可以使用的API调用。
有一种功能代理主义。DispatchHttpCall。然而,你必须";使用Envoy方式";进行http调用。
注意;集群";在该调用中不是一个简单的URL,而是一个Envoy集群。如果您有任何使用Istio代理定义的服务,您也可以尝试使用像outbound|80||some-service.some-namespace.svc.cluster.local
这样的Istio定义的集群。
例如,您可以使用istioctl:查找入口网关的代理配置
istioctl proxy-config all istio-ingressgateway-YOUR-POD -o json | less
在Istio中添加ServiceEntries时,您可能还会得到这样的";集群";在网布中。请注意,服务条目也可以引用外部主机,而不仅仅是在集群服务中。
否则,您可能会尝试添加手动集群,如基于Envoy的速率限制,尽管这也很容易出错。
- applyTo: CLUSTER
match:
cluster:
service: ratelimit.default.svc.cluster.local
patch:
operation: ADD
# Adds the rate limit service cluster for rate limit service defined in step 1.
value:
name: rate_limit_cluster
type: STRICT_DNS
connect_timeout: 10s
lb_policy: ROUND_ROBIN
http2_protocol_options: {}
load_assignment:
cluster_name: rate_limit_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ratelimit.default.svc.cluster.local
port_value: 8081
在对Envoy Lua过滤器的描述中,您可以看到一些示例。虽然它不是WASM,但原理仍然是相同的
对于Go,你可以尝试类似的东西
headers := [][2]string{
{":method", "GET"},
{":path", "/echo?message=hello_world"},
{":authority", "SOME_HOST"},
{":scheme", "http"},
}
_, err := proxywasm.DispatchHttpCall("CLUSTER",
headers,
nil,
nil,
1000,
func(numHeaders, bodySize, numTrailers int) {
resp, _ := proxywasm.GetHttpCallResponseBody(0, 10000)
r := string(resp)
proxywasm.LogDebugf("RESPONSE %v", r)
},
)