Wie Apache APISIX Wasm-Funktionalität ermöglicht
Xinxin Zhu
April 7, 2023
Um leistungsstarke Anwendungen in Webbrowsern ausführen zu können, unterstützt Apache APISIX Wasm auf der Gateway-Ebene, ein binäres Instruktionsformat, das eine effiziente und sichere Ausführung von Code im Web ermöglicht. Dadurch können Entwickler Hochsprachen wie C/C++/Go/Rust verwenden und sich an die proxy-wasm-Spezifikation halten, um Wasm-Plugins zu erstellen. Die proxy-wasm-Spezifikation gewährleistet Kompatibilität und Interoperabilität mit anderen Systemen. Lassen Sie uns in diesen Artikel eintauchen und mehr Details erfahren.
Was ist Wasm?
WebAssembly (abgekürzt Wasm) ist ein binäres Instruktionsformat für eine stapelbasierte virtuelle Maschine.
Vor dem Aufkommen von Wasm konnte nur Javascript in Webbrowsern ausgeführt werden. Mit der Einführung von Wasm können jedoch nun Hochsprachen wie C/C++/Golang in Webbrowsern ausgeführt werden. Große Browser wie Chrome, Firefox und Safari unterstützen mittlerweile alle Wasm. Darüber hinaus ermöglichen die Fortschritte des WASI-Projekts (WebAssembly System Interface), dass auch serverseitige Umgebungen die Ausführung von Wasm-Instruktionen unterstützen.
Derzeit unterstützt Apache APISIX Wasm auf der Gateway-Ebene. Entwickler können Hochsprachen wie C/C++/Go/Rust verwenden und sich an die proxy-wasm-Spezifikation halten, um Wasm-Plugins zu erstellen.
Warum unterstützt APISIX Wasm-Plugins?
Im Vergleich zu nativen Lua-Plugins bieten Wasm-Plugins mehrere Vorteile:
-
Skalierbarkeit: Durch die Unterstützung von Wasm kann APISIX das von proxy-wasm bereitgestellte SDK verwenden, um Plugins in Hochsprachen wie C++, Golang und Rust zu entwickeln. Diese Sprachen verfügen oft über reichere Ökosysteme, was Entwicklern ermöglicht, funktionsreichere Plugins zu implementieren.
-
Sicherheit: Da die Interaktion zwischen APISIX und Wasm auf der Application Binary Interface (ABI) von proxy-wasm basiert, ist dieser Zugriff sicherer. Wasm-Plugins können nur spezifische Änderungen an Anfragen vornehmen, wodurch sichergestellt wird, dass sie keine bösartigen Aktionen ausführen können. Darüber hinaus laufen Wasm-Plugins in einer separaten VM, sodass selbst bei einem Absturz des Plugins der Hauptprozess von APISIX nicht beeinträchtigt wird.
Wie unterstützt APISIX Wasm?
Nachdem wir nun verstanden haben, was Wasm ist, werfen wir einen Blick darauf, wie APISIX Wasm-Plugins unterstützt.
APISIX Wasm-Plugins
APISIX ermöglicht es Entwicklern, Plugins mit beliebten Hochsprachen wie C/C++, Go und Rust zu erstellen. Diese Plugins können mit dem entsprechenden SDK und unter Einhaltung der proxy-wasm-Spezifikation erstellt werden.
proxy-wasm ist eine Spezifikation für ABIs zwischen L4/L7-Proxys, die von Envoy eingeführt wurde. Diese Spezifikation definiert ABIs, einschließlich Speicherverwaltung, L4-Proxy und L7-Proxy-Erweiterungen. Beispielsweise definiert die proxy-wasm-Spezifikation in HTTP(L7) ABIs wie
proxy_on_http_request_headers
,proxy_on_http_request_body
,proxy_on_http_request_trailers
undproxy_on_http_response_headers
, die es Modulen ermöglichen, Anfrageinhalte in verschiedenen Phasen abzurufen und zu modifizieren.
Beispielsweise verwenden wir Golang und das proxy-wasm-go-sdk, um dieses Plugin zu entwickeln:
Das proxy-wasm-go-sdk ist ein SDK für die proxy-wasm-Spezifikation, das Entwicklern hilft, proxy-wasm-Plugins einfacher mit Golang zu erstellen. Es ist jedoch wichtig zu beachten, dass aufgrund einiger Probleme mit der nativen Golang-Unterstützung für WASI dieses SDK auf TinyGo basiert. Weitere Informationen finden Sie hier.
Die Hauptfunktion dieses Plugins besteht darin, den HTTP-Antwortstatuscode und den Antwortkörper einer HTTP-Modifikationsanfrage zu ändern, wie im APISIX-Link referenziert.
...
func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
data, err := proxywasm.GetPluginConfiguration()
if err != nil {
proxywasm.LogErrorf("error reading plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
var p fastjson.Parser
v, err := p.ParseBytes(data)
if err != nil {
proxywasm.LogErrorf("error decoding plugin configuration: %v", err)
return types.OnPluginStartStatusFailed
}
ctx.Body = v.GetStringBytes("body")
ctx.HttpStatus = uint32(v.GetUint("http_status"))
if v.Exists("percentage") {
ctx.Percentage = v.GetInt("percentage")
} else {
ctx.Percentage = 100
}
// schema check
if ctx.HttpStatus < 200 {
proxywasm.LogError("bad http_status")
return types.OnPluginStartStatusFailed
}
if ctx.Percentage < 0 || ctx.Percentage > 100 {
proxywasm.LogError("bad percentage")
return types.OnPluginStartStatusFailed
}
return types.OnPluginStartStatusOK
}
func (ctx *httpLifecycle) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
plugin := ctx.parent
if !sampleHit(plugin.Percentage) {
return types.ActionContinue
}
err := proxywasm.SendHttpResponse(plugin.HttpStatus, nil, plugin.Body, -1)
if err != nil {
proxywasm.LogErrorf("failed to send local response: %v", err)
return types.ActionContinue
}
return types.ActionPause
}
...
Anschließend verwenden wir TinyGo, um den obigen Golang-Code zu kompilieren und eine .wasm
-Datei zu generieren.
tinygo build -o wasm_fault_injection.go.wasm -scheduler=none -target=wasi ./main.go
Nach der Kompilierung erhalten wir die Datei fault_injection.go.wasm
.
Wenn Sie an den Inhalten der Wasm-Datei interessiert sind, können Sie das wasm-tool verwenden, um die spezifischen Inhalte der Wasm-Datei anzuzeigen.
wasm-tools dump hello.go.wasm
Konfigurieren Sie wasm_fault_injection.go.wasm
in der config.yaml
-Datei von APISIX und benennen Sie das Plugin als wasm_fault_injection
.
apisix:
...
wasm:
plugins:
- name: wasm_fault_injection
priority: 7997
file: wasm_fault_injection.go.wasm
Danach starten wir APISIX und erstellen eine Route, die das Wasm-Plugin referenziert:
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{
"uri":"/*",
"upstream":{
"type":"roundrobin",
"timeout":{
"connect":1,
"read":1,
"send":1
},
"nodes":{
"httpbin.org:80":1
}
},
"plugins":{
"wasm_fault_injection":{
"conf":"{\"http_status\":200, \"body\":\"Hello WebAssembly!\n\"}"
}
},
"name":"wasm_fault_injection"
}'
Nach einem Zugriffstest stellen wir fest, dass der Antwortkörper auf "Hello WebAssembly" geändert wurde, was darauf hinweist, dass das Wasm-Plugin nun wirksam ist.
curl 127.0.0.1:9080/get -v
* Trying 127.0.0.1:9080...
* Connected to 127.0.0.1 (127.0.0.1) port 9080 (#0)
> GET /get HTTP/1.1
> Host: 127.0.0.1:9080
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 09 Feb 2023 07:46:50 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: APISIX/3.1.0
<
Hello WebAssembly!
Wasm-nginx-module
Nachdem wir gelernt haben, wie Apache APISIX Wasm-Plugins verwendet, tauchen wir tiefer in die Frage ein: "Warum können wir in Wasm-Plugins auf Anfrageinhalte zugreifen und sie modifizieren?"
Da APISIX OpenResty als zugrunde liegendes Framework verwendet, müssen wir, um in Wasm-Plugins auf Anfrageinhalte zugreifen und sie modifizieren zu können, mit APIs interagieren, die von OpenResty oder NGINX bereitgestellt werden. Genau das macht wasm-nginx-module
.
wasm-nginx-module ist ein NGINX-Modul, das Wasm unterstützt und von API7 entwickelt wurde. Dieses Modul versucht, proxy-wasm-abi auf Basis von NGINX zu implementieren und kapselt die Lua-API, sodass wir proxy-wasm-abi-Aufrufe auf der Lua-Ebene durchführen können. Weitere Informationen finden Sie unter wasm-nginx-module.
Beispielsweise ruft APISIX, wenn es in die access-Phase eintritt, die Lua-Methode on_http_request_headers
auf, die von wasm-nginx-module
bereitgestellt wird.
-- apisix/wasm.lua
...
local ok, err = wasm.on_http_request_headers(plugin_ctx)
if not ok then
core.log.error(name, ": failed to run wasm plugin: ", err)
return 503
end
end
...
Später in dieser Methode wird die ngx_http_wasm_on_http
-Methode aufgerufen, die von wasm-nginx-module
bereitgestellt wird.
ngx_int_t
ngx_http_wasm_on_http(ngx_http_wasm_plugin_ctx_t *hwp_ctx, ngx_http_request_t *r,
ngx_http_wasm_phase_t type, const u_char *body, size_t size,
int end_of_body)
{
...
ctx = ngx_http_wasm_get_module_ctx(r);
if (type == HTTP_REQUEST_HEADERS) {
cb_name = &proxy_on_request_headers;
} else if (type == HTTP_REQUEST_BODY) {
cb_name = &proxy_on_request_body;
} else if (type == HTTP_RESPONSE_HEADERS) {
cb_name = &proxy_on_response_headers;
} else {
cb_name = &proxy_on_response_body;
}
if (type == HTTP_REQUEST_HEADERS || type == HTTP_RESPONSE_HEADERS) {
if (hwp_ctx->hw_plugin->abi_version == PROXY_WASM_ABI_VER_010) {
rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin,
cb_name,
true, NGX_WASM_PARAM_I32_I32, http_ctx->id, 0);
} else {
rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin,
cb_name,
true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id,
0, 1);
}
} else {
rc = ngx_wasm_vm->call(hwp_ctx->hw_plugin->plugin,
cb_name,
true, NGX_WASM_PARAM_I32_I32_I32, http_ctx->id,
size, end_of_body);
}
...
}
In wasm-nginx-module
setzen wir cb_name
basierend auf verschiedenen Phasen, z.B. entspricht HTTP_REQUEST_HEADERS
proxy_on_request_headers
, und rufen dann die Methode in der VM über ngx_wasm_vm->call
auf, was die OnHttpRequestHeaders
-Methode des Wasm-Plugins ist, die wir zuvor in diesem Artikel erwähnt haben.
Damit ist die gesamte Aufrufkette von APISIX, das Wasm-Plugin aufruft und Golang ausführt, abgeschlossen. Die Aufrufkette sieht wie folgt aus:
Wasm VM
Wasm VM ist eine virtuelle Maschine, die zur Ausführung von Wasm-Code verwendet wird. wasm-nginx-module
implementiert zwei Arten von virtuellen Maschinen, "wasmtime" und "wasmedge", um Wasm-Code auszuführen. In APISIX ist "wasmtime" die Standardeinstellung für die Ausführung von Wasm-Code.
Wasmtime ist eine schnelle und sichere Laufzeitumgebung für WebAssembly und WASI, die von der Bytecode Alliance open-source bereitgestellt wird. Es kann WebAssembly-Code außerhalb der Webumgebung ausführen und kann als Befehlszeilentool oder als WebAssembly-Laufzeitengine in anderen Programmen als Bibliothek eingebettet werden. Wasmedge ist eine leichte, hochleistungsfähige und skalierbare WebAssembly (Wasm)-virtuelle Maschine, die für Edge-Computing optimiert ist. Es kann für Cloud-native, Edge- und dezentrale Anwendungen verwendet werden.
Zuerst laden wir in der Wasm VM die .wasm
-Datei mit der load
-Methode in den Speicher. Danach können wir die call
-Methode der VM verwenden, um diese Funktionen aufzurufen. Die VM basiert auf der WASI-Schnittstellenimplementierung, die es Wasm-Code ermöglicht, nicht nur auf der Browserseite, sondern auch serverseitig ausgeführt zu werden.
Zusammenfassung
Wir haben verstanden, was Wasm ist und wie APISIX Wasm-Plugins unterstützt. Durch die Unterstützung von Wasm-Plugins erweitert APISIX nicht nur seine Fähigkeiten, mehrere Sprachen wie C++, Rust, Golang und AssemblyScript für die Plugin-Entwicklung zu unterstützen, sondern profitiert auch vom umfangreichen Ökosystem und den Anwendungsfällen von WebAssembly, das sich über den Browser hinaus auf Cloud-native Umgebungen ausdehnt.
Dadurch kann APISIX Wasm nutzen, um auf der API-Gateway-Seite fortschrittlichere Funktionen bereitzustellen und so ein breiteres Spektrum von Anwendungsfällen abzudecken.