Envoy WASM由于缺少导入而无法加载(使用net/http Go模块)

huangapple go评论87阅读模式
英文:

Envoy WASM failing to load due to missing import (using net/http Go module)

问题

我正在尝试运行我的WASM Go过滤器,使用net/http模块进行外部HTTP调用。Envoy无法加载WASM代码。为什么导入失败?

Envoy/Istio版本:istio/proxyv2:1.11.4

SDK版本:v0.16.1-0.20220127085108-af57b89bc067

TinyGo版本:tinygo version 0.22.0 darwin/amd64(使用go版本go1.17.6和LLVM版本13.0.0)

错误日志

2022-01-31T20:34:18.513749Z error envoy wasm 由于缺少导入而无法加载Wasm模块:env.time.resetTimer
2022-01-31T20:34:18.513794Z error envoy wasm 由于缺少导入而无法加载Wasm模块:env.time.stopTimer
2022-01-31T20:34:18.513807Z error envoy wasm 由于缺少导入而无法加载Wasm模块:env.time.startTimer
2022-01-31T20:34:18.513817Z error envoy wasm 由于缺少导入而无法加载Wasm模块:env.sync/atomic.AddInt32
2022-01-31T20:34:18.513826Z error envoy wasm 由于缺少导入而无法加载Wasm模块:wasi_snapshot_preview1.fd_filestat_get
2022-01-31T20:34:18.513833Z error envoy wasm 由于缺少导入而无法加载Wasm模块:wasi_snapshot_preview1.fd_pread
2022-01-31T20:34:18.513840Z error envoy wasm 由于缺少导入而无法加载Wasm模块:wasi_snapshot_preview1.fd_prestat_get
2022-01-31T20:34:18.513846Z error envoy wasm 由于缺少导入而无法加载Wasm模块:wasi_snapshot_preview1.fd_prestat_dir_name
2022-01-31T20:34:18.513854Z error envoy wasm 由于缺少导入而无法加载Wasm模块:wasi_snapshot_preview1.path_open
2022-01-31T20:34:18.513864Z error envoy wasm Wasm VM失败,无法初始化Wasm代码
2022-01-31T20:34:18.517062Z critical envoy wasm 配置为关闭失败的插件加载失败
2022-01-31T20:34:18.517191Z warning envoy config 类型为type.googleapis.com/envoy.config.core.v3.TypedExtensionConfig的gRPC配置被拒绝:无法创建Wasm HTTP过滤器

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 {
// 在这里嵌入默认的插件上下文,
// 这样我们就不需要重新实现所有的方法。
types.DefaultPluginContext
}

httpContext struct {
	// 在这里嵌入默认的HTTP上下文,
	// 这样我们就不需要重新实现所有的方法。
	types.DefaultHttpContext
}

)

// 重写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

}

// 重写types.DefaultVMContext。
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
return &pluginContext{}
}

// 重写types.DefaultPluginContext。
func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
return &httpContext{}
}

// 重写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

}

英文:

I'm trying to run my WASM Go filter to make an external HTTP call using the net/http module. Envoy fails to load the WASM code. Why is the import failing?

Envoy/Istio version: istio/proxyv2:1.11.4

SDK version: v0.16.1-0.20220127085108-af57b89bc067

TinyGo version: tinygo version 0.22.0 darwin/amd64 (using go version go1.17.6 and LLVM version 13.0.0)

Error Logs

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

Actual Code

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
}

答案1

得分: 5

很遗憾,这并不容易。

TinyGo可能支持该模块,但在使用WASM模块进行Envoy调用时,你不能“只是”调用任意的API。

更准确地说,WASM模块在沙盒中运行,只能调用运行时明确允许的API。在Envoy的情况下,wasm代理SDK提供了一种简单的机制来调用这些API。

proxy-wasm-go-sdk提供了可以使用的这些API调用。

有一个名为proxywasm.DispatchHttpCall的函数。然而,你必须按照“Envoy方式”进行HTTP调用。

请注意,该调用中的“cluster”不是简单的URL,而是Envoy Cluster。如果你使用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时,你可能也会在网格中获得这样一个“cluster”。请注意,Service Entries也可以引用外部主机,不仅限于集群内的服务。

否则,你可以尝试像基于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 Filters的描述中,你可以看到一些示例。虽然它不是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)
	},
)
英文:

Unfortunately, this is not so easy.

TinyGo might support the module, but you can't "just" call some arbitrary API when using a WASM module for Envoy.

To be slightly more precise, WASM modules run a in sandbox and can only make calls which are explicitly allowed by the runtime. In the case of Envoy, the wasm proxy sdk provides a simple mechanism to call those API.

proxy-wasm-go-sdk provides these API calls which you can use.

There is a function proxywasm.DispatchHttpCall. However, you have to "use the Envoy way" of making http calls.

Note that the "cluster" in that call is not a simple URL, but an Envoy Cluster. You might also try to use Istio-defined cluster like outbound|80||some-service.some-namespace.svc.cluster.local if you have any services defined with Istio Proxies.

You can look up the proxy-config, for example, for an ingress gateway, with istioctl:

istioctl proxy-config all istio-ingressgateway-YOUR-POD -o json | less

When adding ServiceEntries in Istio, you might also get such a "cluster" in your mesh. Note that Service Entries can also refer to external hosts, not only in-cluster services.

Otherwise, you might try adding a manual cluster like in an Envoy-based rate limiting, although this is also easy to get wrong.

- 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

In this description of Envoy Lua Filters, you see some examples. Although it is not WASM, the principle remains the same

For Go, you might try something like

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)
	},
)

huangapple
  • 本文由 发表于 2022年2月1日 05:41:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/70933142.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定