Bagaimana Apache APISIX Mengaktifkan Fungsi Wasm
Xinxin Zhu
April 7, 2023
Untuk memungkinkan aplikasi berkinerja tinggi berjalan di browser web, Apache APISIX mendukung Wasm di tingkat gateway, sebuah format instruksi biner yang memungkinkan eksekusi kode yang efisien dan aman di web. Akibatnya, pengembang dapat menggunakan bahasa pemrograman tingkat tinggi seperti C/C++/Go/Rust dan mengikuti spesifikasi proxy-wasm untuk membuat plugin Wasm. Spesifikasi proxy-wasm memastikan kompatibilitas dan interoperabilitas dengan sistem lain. Mari kita masuk ke artikel ini dan pelajari lebih detail.
Apa itu Wasm
WebAssembly (disingkat Wasm) adalah format instruksi biner untuk mesin virtual berbasis stack.
Sebelum kemunculan Wasm, hanya Javascript yang dapat dieksekusi di browser web. Namun, dengan diperkenalkannya Wasm, bahasa tingkat tinggi seperti C/C++/Golang sekarang dapat dijalankan di browser web. Browser utama seperti Chrome, Firefox, dan Safari sekarang semuanya mendukung Wasm. Selain itu, berkat kemajuan yang dibuat oleh proyek WASI (WebAssembly System Interface), lingkungan sisi server juga dapat mendukung eksekusi instruksi Wasm.
Saat ini, Apache APISIX mendukung Wasm di tingkat gateway. Pengembang dapat menggunakan bahasa pemrograman tingkat tinggi seperti C/C++/Go/Rust dan mengikuti spesifikasi proxy-wasm untuk membuat plugin Wasm.

Mengapa APISIX Mendukung Plugin Wasm?
Dibandingkan dengan plugin Lua asli, plugin Wasm menawarkan beberapa keunggulan:
-
Skalabilitas: Dengan mendukung Wasm, APISIX dapat memanfaatkan SDK yang disediakan oleh proxy-wasm untuk mengembangkan plugin dalam bahasa pemrograman tingkat tinggi seperti C++, Golang, dan Rust. Bahasa-bahasa ini sering memiliki ekosistem yang lebih kaya, memungkinkan pengembang untuk mengimplementasikan plugin yang lebih kaya fitur.
-
Keamanan: Karena interaksi antara APISIX dan Wasm bergantung pada Application Binary Interface (ABI) yang disediakan oleh proxy-wasm, akses ini lebih aman. Plugin Wasm hanya dapat melakukan modifikasi tertentu pada permintaan, memastikan bahwa mereka tidak dapat melakukan tindakan berbahaya. Selain itu, karena plugin Wasm berjalan di VM terpisah, bahkan jika plugin mengalami crash, itu tidak akan memengaruhi proses utama APISIX.
Bagaimana APISIX Mendukung Wasm?
Setelah kita memahami apa itu Wasm, mari kita lihat secara top-down bagaimana APISIX mendukung plugin Wasm.

Plugin Wasm APISIX
APISIX memungkinkan pengembang untuk membuat plugin menggunakan bahasa pemrograman tingkat tinggi populer seperti C/C++, Go, dan Rust. Plugin ini dapat dibangun menggunakan SDK yang sesuai dan mengikuti spesifikasi proxy-wasm.
proxy-wasm adalah spesifikasi untuk ABI antara proxy L4/L7, diperkenalkan oleh Envoy. Spesifikasi ini mendefinisikan ABI termasuk manajemen memori, ekstensi proxy L4, dan ekstensi proxy L7. Misalnya, dalam HTTP(L7), spesifikasi proxy-wasm mendefinisikan ABI seperti proxy_on_http_request_headers, proxy_on_http_request_body, proxy_on_http_request_trailers, dan proxy_on_http_response_headers, memungkinkan modul untuk mengambil dan memodifikasi konten permintaan di berbagai tahap.
Sebagai contoh, kita akan menggunakan Golang dan proxy-wasm-go-sdk untuk mengembangkan plugin ini:
proxy-wasm-go-sdk adalah SDK untuk spesifikasi proxy-wasm yang membantu pengembang untuk membuat plugin proxy-wasm lebih mudah menggunakan Golang. Namun, penting untuk dicatat bahwa karena beberapa masalah dengan dukungan native Golang untuk WASI, SDK ini diimplementasikan berdasarkan TinyGo. Untuk informasi lebih lanjut, Anda dapat klik di sini untuk melihat detailnya.
Fungsi utama plugin ini adalah memodifikasi kode status respons HTTP dan badan respons dari permintaan modifikasi HTTP, seperti yang dirujuk dari tautan APISIX.
... 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 } ...
Setelah itu, kita menggunakan TinyGo untuk mengompilasi kode Golang di atas dan menghasilkan file .wasm.
tinygo build -o wasm_fault_injection.go.wasm -scheduler=none -target=wasi ./main.go
Setelah kompilasi selesai, kita mendapatkan file fault_injection.go.wasm.
Jika Anda tertarik dengan isi file wasm, Anda dapat menggunakan wasm-tool untuk melihat isi spesifik dari file wasm.
wasm-tools dump hello.go.wasm
Konfigurasikan wasm_fault_injection.go.wasm di file config.yaml APISIX dan beri nama plugin sebagai wasm_fault_injection.
apisix: ... wasm: plugins: - name: wasm_fault_injection priority: 7997 file: wasm_fault_injection.go.wasm
Setelah itu, kita memulai APISIX dan membuat rute yang merujuk pada plugin Wasm:
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" }'
Setelah melakukan tes akses, kami menemukan bahwa badan respons telah dimodifikasi menjadi "Hello WebAssembly", menunjukkan bahwa plugin Wasm sekarang efektif.
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
Setelah mempelajari bagaimana Apache APISIX menggunakan plugin Wasm, mari kita menyelami lebih dalam "mengapa kita dapat mengakses dan memodifikasi konten permintaan dalam plugin Wasm?"
Karena APISIX menggunakan OpenResty sebagai kerangka kerja dasar, untuk dapat mengakses dan memodifikasi konten permintaan dalam plugin Wasm, kita perlu berinteraksi dengan API yang disediakan oleh OpenResty atau NGINX. Inilah yang dilakukan oleh wasm-nginx-module.
wasm-nginx-module adalah modul NGINX yang mendukung Wasm dan dikembangkan oleh API7. Modul ini mencoba mengimplementasikan proxy-wasm-abi berdasarkan NGINX dan mengemas API Lua, memungkinkan kita untuk menyelesaikan panggilan proxy-wasm-abi di tingkat Lua. Untuk informasi lebih lanjut, silakan merujuk ke wasm-nginx-module.
Sebagai contoh, ketika APISIX berjalan ke fase access, ia memanggil metode Lua on_http_request_headers yang disediakan oleh wasm-nginx-module.
-- 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 ...
Kemudian dalam metode ini, metode ngx_http_wasm_on_http yang disediakan oleh wasm-nginx-module akan dipanggil.
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); } ... }
Di wasm-nginx-module, kita mengatur cb_name berdasarkan tahapan yang berbeda, seperti HTTP_REQUEST_HEADERS sesuai dengan proxy_on_request_headers, dan kemudian memanggil metode di VM melalui ngx_wasm_vm->call, yang merupakan metode OnHttpRequestHeaders dari plugin wasm yang disebutkan sebelumnya dalam artikel ini.
Dengan ini, seluruh rantai panggilan APISIX memanggil plugin wasm dan menjalankan Golang selesai. Rantai panggilan adalah sebagai berikut:
Wasm VM
Wasm VM adalah mesin virtual yang digunakan untuk mengeksekusi kode Wasm. wasm-nginx-module mengimplementasikan dua jenis mesin virtual, "wasmtime" dan "wasmedge", untuk mengeksekusi kode Wasm. Di APISIX, "wasmtime" adalah default untuk menjalankan kode Wasm.
Wasmtime adalah runtime yang cepat dan aman untuk WebAssembly dan WASI, open-source oleh Bytecode Alliance. Ini dapat menjalankan kode WebAssembly di luar lingkungan web dan dapat digunakan sebagai alat baris perintah atau disematkan sebagai mesin runtime WebAssembly dalam program lain sebagai pustaka. Wasmedge adalah mesin virtual WebAssembly (Wasm) ringan, berkinerja tinggi, dan dapat diskalakan yang dioptimalkan untuk komputasi tepi. Ini dapat digunakan untuk aplikasi cloud-native, tepi, dan terdesentralisasi.
Pertama, di Wasm VM, kita menggunakan metode load untuk memuat file .wasm ke dalam memori. Setelah itu, kita dapat menggunakan metode call dari VM untuk memanggil fungsi-fungsi ini. VM didasarkan pada implementasi antarmuka WASI, yang memungkinkan kode Wasm tidak hanya berjalan di sisi browser tetapi juga mendukung berjalan di sisi server.
Ringkasan
Kita telah memahami apa itu Wasm dan bagaimana APISIX mendukung plugin Wasm. Dengan menawarkan dukungan untuk plugin Wasm, APISIX tidak hanya meningkatkan kemampuannya dalam mendukung berbagai bahasa, seperti C++, Rust, Golang, dan AssemblyScript untuk pengembangan plugin, tetapi juga mendapat manfaat dari ekosistem dan kasus penggunaan WebAssembly yang luas, yang meluas di luar browser ke lingkungan cloud-native.
Akibatnya, APISIX dapat memanfaatkan Wasm untuk memberikan fitur yang lebih canggih di sisi gateway API, memungkinkannya untuk menangani berbagai skenario penggunaan yang lebih luas.