Quay lại trang blogXây dựng Serverless WASM Runtime thuần Go với Wazero
GoWebAssemblyWASMServerless

Xây dựng Serverless WASM Runtime thuần Go với Wazero

20 tháng 7, 20263 phút đọc

"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.