英文:
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)
},
)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论