Xây dựng Serverless WASM Runtime thuần Go với Wazero
"Cách thực thi mã nguồn tùy chỉnh của người dùng một cách an toàn tại Edge bằng các sandbox WebAssembly trong Go không phụ thuộc CGO."
WebAssembly (WASM) đã nổi lên như một sự thay thế siêu nhẹ và an toàn cho container để chạy mã nguồn không tin cậy tại biên mạng (Edge). Trong khi hệ sinh thái Serverless WASM phần lớn bị thống trị bởi Rust và Wasmtime, chúng ta hoàn toàn có thể xây dựng một bộ thực thi hiệu năng cao bằng Go thuần túy.
Dưới đây là cách chúng tôi thiết kế một bộ định tuyến Edge thông minh sử dụng wazero — một WASM runtime viết hoàn toàn bằng Go.
Thử thách Memory Bridge
Khi chạy các module WebAssembly khách (như biên dịch từ Go, Rust hoặc C) bên trong hệ thống chủ, chúng ta cần truyền tải các payload phức tạp (như HTTP headers và body). Vì sandbox WebAssembly sở hữu vùng nhớ tuyến tính (linear memory) bị cô lập hoàn toàn, máy chủ và máy khách không thể chia sẻ con trỏ trực tiếp cho nhau.
Chúng tôi giải quyết vấn đề này bằng cách thiết kế một Memory Bridge. Máy chủ sẽ cấp phát bộ nhớ bên trong instance WASM, ghi dữ liệu request vào đó, rồi chuyển con trỏ đó cho hàm xử lý của máy khách.
Dưới đây là đoạn mã Go giả lập quy trình máy chủ gọi module WASM:
package main
import (
"context"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
func runWasmHandler(ctx context.Context, mod wazero.CompiledModule, payload []byte) ([]byte, error) {
r := wazero.NewRuntime(ctx)
defer r.Close(ctx)
// Khởi tạo module
instance, err := r.InstantiateModule(ctx, mod, wazero.NewModuleConfig())
if err != nil {
return nil, err
}
// Lấy các hàm được xuất bản từ WASM
malloc := instance.ExportedFunction("malloc")
handler := instance.ExportedFunction("handle_request")
free := instance.ExportedFunction("free")
// 1. Cấp phát bộ nhớ bên trong sandbox WASM khách
results, err := malloc.Call(ctx, uint64(len(payload)))
if err != nil {
return nil, err
}
guestPtr := uint32(results[0])
defer free.Call(ctx, uint64(guestPtr))
// 2. Ghi trực tiếp payload vào vùng nhớ tuyến tính WebAssembly
instance.Memory().Write(guestPtr, payload)
// 3. Thực thi hàm xử lý request
res, err := handler.Call(ctx, uint64(guestPtr), uint64(len(payload)))
if err != nil {
return nil, err
}
// 4. Giải mã con trỏ và kích thước dữ liệu trả về
resPtr := uint32(res[0] >> 32)
resLen := uint32(res[0])
// Đọc dữ liệu phản hồi từ bộ nhớ
out, _ := instance.Memory().Read(resPtr, resLen)
return out, nil
}
Bảng so sánh hiệu năng
Nhờ sử dụng sandbox thuần Go qua wazero, chúng tôi đạt được tốc độ khởi động nguội (cold start) tính bằng miligiây, hỗ trợ scale-to-zero hoàn hảo so với Docker container thông thường.
| Chỉ số | Docker Containers | Wazero Go Sandbox |
|---|---|---|
| Thời gian khởi động nguội | ~500ms - 3s | ~7ms |
| RAM tiêu thụ khi rảnh | ~100MB mỗi service | 0MB (Chỉ khởi tạo khi có yêu cầu) |
| Kích thước ảnh Sandbox | 100MB - 1GB | ~3MB (WASM Bytecode thô) |
Kết luận
Xây dựng môi trường thực thi serverless trong Go là hoàn toàn khả thi. Bằng cách sử dụng WebAssembly sandbox, bạn có thể chạy các script của bên thứ ba trên các Edge node, thiết bị IoT hoặc máy chủ một cách cô lập tuyệt đối, tốn ít tài nguyên RAM và độ trễ cực thấp.