边缘计算到端侧AI框架的演化(二) 边缘计算支持跨语言调用的AI推理

306 阅读14分钟

随着智能穿戴设备,工控机以及一些终端设备的发展。在终端上做一些小型推理的需求迫在眉睫. 需要一个应用服务框架,用于在服务器、个人电脑和边缘设备上的 GPU 上运行 GenAI 模型(例如LLM语音转文本文本转图像TTS).

WasmEdge介绍

WasmEdge 是一个轻量级、高性能且可扩展的 WebAssembly 运行时.从 CLI 或Docker运行独立的 Wasm 程序通过LlamaEdge与开源 LLM聊天 在GoRustC应用程序 中嵌入 Wasm 函数 使用Kubernetes数据流框架区块链 管理和编排 Wasm 运行时. 如果边缘计算需要支持小型GenAI的推理。支持WasmEdge是必然趋势

EasyFaas如何支持WasmEdge运行时?

可以三步走

  • Wasm实现c++计算任务的跨语言调用

  • EasyFaas集成WasmEdge方案

  • WebAssembly 容器资源更新实现

Wasm实现c++计算任务的跨语言调用

WasmEdge提供了Go SDK用于嵌入WebAssembly运行时到Go应用程序中

整个方案分为三个主要步骤:

1.  C++计算任务编写

2.  编译C++为WebAssembly模块

3.  Go程序通过WasmEdge SDK调用WASM模块

1. C++计算任务代码

解释

// math_calc.cpp  
extern "C" {  
    // 简单的数学计算函数  
    int add(int a, int b) {  
        return a + b;  
    }  
      
    // 更复杂的计算任务  
    double fibonacci(int n) {  
        if (n <= 1) return n;  
        double a = 0, b = 1, temp;  
        for (int i = 2; i <= n; i++) {  
            temp = a + b;  
            a = b;  
            b = temp;  
        }  
        return b;  
    }  
      
    // 矩阵计算示例  
    void matrix_multiply(double* a, double* b, double* result, int size) {  
        for (int i = 0; i < size; i++) {  
            for (int j = 0; j < size; j++) {  
                result[i * size + j] = 0;  
                for (int k = 0; k < size; k++) {  
                    result[i * size + j] += a[i * size + k] * b[k * size + j];  
                }  
            }  
        }  
    }  
}

2. 编译C++到WebAssembly

使用Emscripten或WASI-SDK编译C++代码:

# 使用Emscripten编译  
emcc math_calc.cpp -o math_calc.wasm \  
    -s EXPORTED_FUNCTIONS='["_add","_fibonacci","_matrix_multiply"]' \  
    -s EXPORT_ALL=1 \  
    -s WASM=1 \  
    --no-entry  
  
# 或使用WASI-SDK编译  
clang++ --target=wasm32-wasi math_calc.cpp -o math_calc.wasm \  
    -Wl,--export-all \  
    -Wl,--no-entry

3. Go程序调用WASM模块

// main.go  
package main  
  
import (  
    "fmt"  
    "log"  
    "os"  
      
    "github.com/second-state/WasmEdge-go/wasmedge"  
)  
  
func main() {  
    // 初始化WasmEdge配置  
    conf := wasmedge.NewConfigure(wasmedge.WASI)  
    vm := wasmedge.NewVMWithConfig(conf)  
    defer vm.Delete()  
      
    // 设置WASI选项  
    wasi := vm.GetImportModule(wasmedge.WASI)  
    wasi.InitWasi(  
        os.Args[1:],     // 命令行参数  
        os.Environ(),    // 环境变量  
        []string{".:."}, // 预打开目录  
    )  
      
    // 加载WASM文件  
    err := vm.LoadWasmFile("math_calc.wasm")  
    if err != nil {  
        log.Fatal("加载WASM文件失败:", err)  
    }  
      
    // 验证WASM模块  
    err = vm.Validate()  
    if err != nil {  
        log.Fatal("验证WASM模块失败:", err)  
    }  
      
    // 实例化WASM模块  
    err = vm.Instantiate()  
    if err != nil {  
        log.Fatal("实例化WASM模块失败:", err)  
    }  
      
    // 调用add函数  
    result, err := vm.Execute("add", int32(10), int32(20))  
    if err != nil {  
        log.Fatal("执行add函数失败:", err)  
    }  
    fmt.Printf("add(10, 20) = %d\n", result[0].(int32))  
      
    // 调用fibonacci函数  
    result, err = vm.Execute("fibonacci", int32(10))  
    if err != nil {  
        log.Fatal("执行fibonacci函数失败:", err)  
    }  
    fmt.Printf("fibonacci(10) = %f\n", result[0].(float64))  
      
    // 调用矩阵乘法(需要内存操作)  
    matrixCalc(vm)  
}  
  
func matrixCalc(vm *wasmedge.VM) {  
    // 获取内存实例  
    memInst := vm.GetActiveModule().FindMemory("memory")  
    if memInst == nil {  
        log.Fatal("未找到内存实例")  
    }  
      
    size := 3  
    matrixSize := size * size  
      
    // 准备矩阵数据  
    matrixA := []float64{1, 2, 3, 4, 5, 6, 7, 8, 9}  
    matrixB := []float64{9, 8, 7, 6, 5, 4, 3, 2, 1}  
    result := make([]float64, matrixSize)  
      
    // 分配内存并写入数据  
    ptrA := allocateMemory(vm, matrixSize*8) // 8 bytes per double  
    ptrB := allocateMemory(vm, matrixSize*8)  
    ptrResult := allocateMemory(vm, matrixSize*8)  
      
    writeMatrix(memInst, ptrA, matrixA)  
    writeMatrix(memInst, ptrB, matrixB)  
      
    // 调用矩阵乘法函数  
    _, err := vm.Execute("matrix_multiply", ptrA, ptrB, ptrResult, int32(size))  
    if err != nil {  
        log.Fatal("执行matrix_multiply函数失败:", err)  
    }  
      
    // 读取结果  
    readMatrix(memInst, ptrResult, result)  
    fmt.Println("矩阵乘法结果:", result)  
}  
  
func allocateMemory(vm *wasmedge.VM, size int) int32 {  
    // 简化的内存分配,实际应用中可能需要更复杂的内存管理  
    result, _ := vm.Execute("malloc", int32(size))  
    return result[0].(int32)  
}  
  
func writeMatrix(mem *wasmedge.Memory, ptr int32, data []float64) {  
    for i, val := range data {  
        offset := int(ptr) + i*8  
        mem.SetData([]byte{  
            byte(val), byte(val >> 8), byte(val >> 16), byte(val >> 24),  
            byte(val >> 32), byte(val >> 40), byte(val >> 48), byte(val >> 56),  
        }, offset, 8)  
    }  
}  
  
func readMatrix(mem *wasmedge.Memory, ptr int32, result []float64) {  
    for i := range result {  
        offset := int(ptr) + i*8  
        data := mem.GetData(offset, 8)  
        // 将字节转换回float64 (简化实现)  
        // 实际应用中需要正确的字节序处理  
        result[i] = float64(data[0]) // 简化示例  
    }  
}

4. 项目构建配置

# go.mod  
module wasm-cpp-calc  
  
go 1.19  
  
require github.com/second-state/WasmEdge-go v0.13.5
# Makefile  
.PHONY: build-wasm build-go run clean  
  
build-wasm:  
	emcc math_calc.cpp -o math_calc.wasm \  
		-s EXPORTED_FUNCTIONS='["_add","_fibonacci","_matrix_multiply","_malloc"]' \  
		-s EXPORT_ALL=1 \  
		-s WASM=1 \  
		--no-entry  
  
build-go:  
	go build -o main main.go  
  
run: build-wasm build-go  
	./main  
  
clean:  
	rm -f math_calc.wasm main

5. 运行流程

# 1. 安装WasmEdge  
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash  
  
# 2. 安装Go依赖  
go mod tidy  
  
# 3. 编译和运行  
make run

EasyFaas集成WasmEdge方案

一般智能网关的算法插件可能会用c++或者rust重构, 此时需要一种runtime 支持编译型函数的运行

1. 新增 WebAssembly 运行时类型

修改文件: pkg/funclet/runtime/api/container.go container.go:25

在现有常量后添加:

const RuntimeTypeWasm = "wasmedge"

2. 实现 WasmEdge ContainerManager

新建文件: pkg/funclet/runtime/wasmedge/types.go

package wasmedge  
  
import (  
	"sync"  
	"time"  
	"github.com/second-state/WasmEdge-go/wasmedge"  
	"github.com/baidu/easyfaas/pkg/funclet/runtime/api"  
)  
  
type WasmInstance struct {  
	ID       string         //模拟的进程ID,由于 WebAssembly 不是真正的进程,这是一个模拟的 PID
                           // 用于与 EasyFaaS 现有的进程管理接口保持兼容
	VM       *wasmedge.VM  //WasmEdge 虚拟机实例,这是实际执行 WebAssembly 代码的核心组件
                          // 通过这个字段可以调用 WebAssembly 函数和管理 WASM 模块
	Status   string     //容器当前的状态,用于跟踪 WebAssembly 实例的生命周期状态
	Bundle   string  //WASM 模块文件路径,指向包含 WebAssembly 模块文件的目录路径
                     // 通常包含 function.wasm 文件和相关配置
	Created  time.Time  
	Pid      int // 模拟的进程ID  
	mutex    sync.RWMutex  
}  
  
type WasmContainerRuntime struct {  
	instances map[string]*WasmInstance  
	mutex     sync.RWMutex  
	logger    *logs.Logger  
}  
  
const (  
	StatusCreated = "created"  
	StatusRunning = "running"  
	StatusStopped = "stopped"  
	StatusPaused  = "paused"  
)

新建文件: pkg/funclet/runtime/wasmedge/container.go

package wasmedge  
  
import (  
	"fmt"  
	"os"  
	"path/filepath"  
	"strconv"  
	"time"  
	  
	"github.com/second-state/WasmEdge-go/wasmedge"  
	"github.com/baidu/easyfaas/pkg/funclet/runtime/api"  
	runtimeErr "github.com/baidu/easyfaas/pkg/funclet/runtime/error"  
	"github.com/baidu/easyfaas/pkg/util/logs"  
)  
  
func NewContainerRuntime(logger *logs.Logger) api.ContainerManager {  
	return &WasmContainerRuntime{  
		instances: make(map[string]*WasmInstance),  
		logger:    logger,  
	}  
}  
  
func (w *WasmContainerRuntime) Name() string {  
	return api.RuntimeTypeWasm  
}  
  
func (w *WasmContainerRuntime) StartContainer(request *api.CreateContainerRequest) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	// 检查容器是否已存在  
	if _, exists := w.instances[request.ID]; exists {  
		return fmt.Errorf("container %s already exists", request.ID)  
	}  
	  
	// 查找WASM文件  
	wasmFile := filepath.Join(request.Bundle, "function.wasm")  
	if _, err := os.Stat(wasmFile); os.IsNotExist(err) {  
		return fmt.Errorf("wasm file not found: %s", wasmFile)  
	}  
	  
	// 初始化WasmEdge配置  
	conf := wasmedge.NewConfigure(wasmedge.WASI)  
	vm := wasmedge.NewVMWithConfig(conf)  
	  
	// 设置WASI选项  
	wasi := vm.GetImportModule(wasmedge.WASI)  
	wasi.InitWasi(  
		[]string{},      // 命令行参数  
		os.Environ(),    // 环境变量  
		[]string{".:."}, // 预打开目录  
	)  
	  
	// 加载WASM文件  
	if err := vm.LoadWasmFile(wasmFile); err != nil {  
		vm.Delete()  
		return fmt.Errorf("failed to load wasm file: %v", err)  
	}  
	  
	// 验证WASM模块  
	if err := vm.Validate(); err != nil {  
		vm.Delete()  
		return fmt.Errorf("failed to validate wasm module: %v", err)  
	}  
	  
	// 实例化WASM模块  
	if err := vm.Instantiate(); err != nil {  
		vm.Delete()  
		return fmt.Errorf("failed to instantiate wasm module: %v", err)  
	}  
	  
	// 创建实例  
	instance := &WasmInstance{  
		ID:      request.ID,  
		VM:      vm,  
		Status:  StatusRunning,  
		Bundle:  request.Bundle,  
		Created: time.Now(),  
		Pid:     w.generatePid(),  
	}  
	  
	w.instances[request.ID] = instance  
	  
	// 写入PID文件  
	if request.PidFile != "" {  
		if err := w.writePidFile(request.PidFile, instance.Pid); err != nil {  
			w.logger.Warnf("failed to write pid file: %v", err)  
		}  
	}  
	  
	w.logger.Infof("started wasm container %s", request.ID)  
	return nil  
}  
  
func (w *WasmContainerRuntime) RemoveContainer(ID string, force bool) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	// 清理VM资源  
	if instance.VM != nil {  
		instance.VM.Delete()  
	}  
	  
	delete(w.instances, ID)  
	w.logger.Infof("removed wasm container %s", ID)  
	return nil  
}  
  
func (w *WasmContainerRuntime) KillContainer(ID string, signal string, all bool) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	instance.mutex.Lock()  
	defer instance.mutex.Unlock()  
	  
	if instance.Status == StatusRunning {  
		instance.Status = StatusStopped  
		w.logger.Infof("killed wasm container %s with signal %s", ID, signal)  
	}  
	  
	return nil  
}  
  
func (w *WasmContainerRuntime) PauseContainer(ID string) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	instance.mutex.Lock()  
	defer instance.mutex.Unlock()  
	  
	if instance.Status == StatusRunning {  
		instance.Status = StatusPaused  
		w.logger.Infof("paused wasm container %s", ID)  
	}  
	  
	return nil  
}  
  
func (w *WasmContainerRuntime) ResumeContainer(ID string) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	instance.mutex.Lock()  
	defer instance.mutex.Unlock()  
	  
	if instance.Status == StatusPaused {  
		instance.Status = StatusRunning  
		w.logger.Infof("resumed wasm container %s", ID)  
	}  
	  
	return nil  
}  
  
func (w *WasmContainerRuntime) ListContainers() (list []*api.Container, err error) {  
	w.mutex.RLock()  
	defer w.mutex.RUnlock()  
	  
	for _, instance := range w.instances {  
		container := &api.Container{  
			ID:      instance.ID,  
			Pid:     instance.Pid,  
			Status:  instance.Status,  
			Bundle:  instance.Bundle,  
			Created: instance.Created,  
		}  
		list = append(list, container)  
	}  
	  
	return list, nil  
}  
  
func (w *WasmContainerRuntime) ContainerInfo(ID string) (container *api.Container, err error) {  
	w.mutex.RLock()  
	defer w.mutex.RUnlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return nil, runtimeErr.GetContainerInfoError{  
			ID:  ID,  
			Err: fmt.Errorf("container not found"),  
		}  
	}  
	  
	container = &api.Container{  
		ID:      instance.ID,  
		Pid:     instance.Pid,  
		Status:  instance.Status,  
		Bundle:  instance.Bundle,  
		Created: instance.Created,  
	}  
	  
	return container, nil  
}  
  
func (w *WasmContainerRuntime) UpdateContainer(ID string, request *api.UpdateContainerRequest) error {  
	w.mutex.RLock()  
	defer w.mutex.RUnlock()  
	  
	_, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	// todo WebAssembly容器的资源更新  
	// 目前简单返回成功  
	w.logger.Infof("updated wasm container %s resources", ID)  
	return nil  
}  
  
// 辅助方法  
func (w *WasmContainerRuntime) generatePid() int {  
	// 生成一个模拟的PID,实际应用中可能需要更复杂的逻辑  
	return int(time.Now().UnixNano() % 100000)  
}  
  
func (w *WasmContainerRuntime) writePidFile(pidFile string, pid int) error {  
	return os.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0644)  
}  
  
// 执行WASM函数的方法  
func (w *WasmContainerRuntime) ExecuteFunction(containerID, functionName string, args ...interface{}) ([]interface{}, error) {  
	w.mutex.RLock()  
	instance, exists := w.instances[containerID]  
	w.mutex.RUnlock()  
	  
	if !exists {  
		return nil, fmt.Errorf("container %s not found", containerID)  
	}  
	  
	instance.mutex.RLock()  
	defer instance.mutex.RUnlock()  
	  
	if instance.Status != StatusRunning {  
		return nil, fmt.Errorf("container %s is not running", containerID)  
	}  
	  
	return instance.VM.Execute(functionName, args...)  
}

3. 修改运行时选择逻辑

修改文件: pkg/funclet/runtime/container.go container.go:27-32

修改 NewContainerRuntime 函数:

func NewContainerRuntime(runtimeType string, cmd string, logger *logs.Logger) (cm api.ContainerManager, err error) {  
	switch runtimeType {  
	case api.RuntimeTypeRunc:  
		cm = runc.NewContainerRuntime(cmd, logger)  
	case api.RuntimeTypeWasm:  
		cm = wasmedge.NewContainerRuntime(logger)  
	default:  
		return nil, runtimeErr.ErrUnsupportedContainerRuntime{runtimeType}  
	}  
	return cm, nil  
}

4. 扩展运行时管理器参数

修改文件: pkg/funclet/runtime/manager.go manager.go:35-40

扩展 RuntimeManagerParameters 结构:

解释

type RuntimeManagerParameters struct {  
	ContainerNum int  
	RuntimeCmd   string  
	RuntimeType  string  // 新增:运行时类型  
	Option       *ResourceOption  
	Logger       *logs.Logger  
}

修改 NewRuntimeManager 函数:

func NewRuntimeManager(p *RuntimeManagerParameters) (rm RuntimeManagerInterface, err error) {  
	runtimeManager := Manager{}  
  
	resourceCtrl, err := NewResourceManager(p.Option, p.ContainerNum)  
	if err != nil {  
		return nil, err  
	}  
  
	// 使用配置的运行时类型,默认为runc  
	runtimeType := p.RuntimeType  
	if runtimeType == "" {  
		runtimeType = api.RuntimeTypeRunc  
	}  
  
	containerCtrl, err := NewContainerRuntime(runtimeType, p.RuntimeCmd, p.Logger)  
	if err != nil {  
		return nil, err  
	}  
  
	runtimeManager.ResourceManager = resourceCtrl  
	runtimeManager.ContainerManager = containerCtrl  
	return &runtimeManager, nil  
}

5. 修改 Funclet 配置和初始化

修改文件: pkg/funclet/funclet.go funclet.go:65-74

修改初始化逻辑:

p := runtime.RuntimeManagerParameters{  
	RuntimeCmd:   o.RuntimeCmd,  
	RuntimeType:  o.RuntimeType,  // 新增:从配置中获取运行时类型  
	ContainerNum: o.ContainerNum,  
	Option:       o.ResourceOption,  
	Logger:       logger,  
}

6. 更新依赖和配置

修改文件: go.mod

更新依赖:

module github.com/baidu/easyfaas  
  
go 1.19  
  
require (  
	github.com/second-state/WasmEdge-go v0.13.5  
	// ... 其他现有依赖  
)

7. 新增配置选项结构

新建文件: pkg/funclet/options/wasm.go

package options  
  
type WasmOptions struct {  
	RuntimeType string `json:"runtime_type" yaml:"runtime_type"`  
	WasmPath    string `json:"wasm_path" yaml:"wasm_path"`  
}  
  
// 扩展现有的FuncletOptions  
func (o *FuncletOptions) SetRuntimeType(runtimeType string) {  
	o.RuntimeType = runtimeType  
}

8. 示例使用代码

新建文件: examples/wasm-demo/main.go

package main  
  
import (  
	"context"  
	"fmt"  
	"log"  
	"os"  
	"path/filepath"  
	"time"  
  
	"github.com/baidu/easyfaas/pkg/funclet"  
	"github.com/baidu/easyfaas/pkg/funclet/options"  
	"github.com/baidu/easyfaas/pkg/funclet/runtime/api"  
	"github.com/baidu/easyfaas/pkg/funclet/runtime/wasmedge"  
)  
  
func main() {  
	// 配置WebAssembly运行时  
	opts := &options.FuncletOptions{  
		RuntimeType:    "wasmedge",  
		RuntimeCmd:     "",  // WebAssembly不需要外部运行时命令  
		ContainerNum:   5,  
		TmpPath:        "/tmp/easyfaas",  
		RunnerDataPath: "/var/lib/easyfaas",  
		RunnerSpecOption: &options.RunnerSpecOption{  
			RootfsPath: "/opt/easyfaas/rootfs",  
		},  
		ResourceOption: &options.ResourceOption{  
			Memory:   512 * 1024 * 1024, // 512MB  
			CPUQuota: 1000,              // 1 CPU  
		},  
		NetworkOption: &options.NetworkOption{  
			BridgeName: "easyfaas0",  
		},  
	}  
  
	// 初始化停止通道  
	stopCh := make(chan struct{})  
	finishCh := make(chan struct{})  
  
	// 初始化Funclet  
	f, err := funclet.InitFunclet(opts, stopCh, finishCh)  
	if err != nil {  
		log.Fatalf("Failed to initialize funclet: %v", err)  
	}  
  
	// 演示WebAssembly函数执行  
	if err := demonstrateWasmExecution(f); err != nil {  
		log.Fatalf("Failed to demonstrate WASM execution: %v", err)  
	}  
  
	fmt.Println("WebAssembly integration demo completed successfully!")  
}  
  
func demonstrateWasmExecution(f *funclet.Funclet) error {  
	// 准备WASM模块文件  
	wasmBundle := "/tmp/wasm-demo"  
	if err := prepareWasmBundle(wasmBundle); err != nil {  
		return fmt.Errorf("failed to prepare WASM bundle: %v", err)  
	}  
  
	// 创建容器请求  
	containerID := "wasm-demo-container"  
	createReq := &api.CreateContainerRequest{  
		ID:      containerID,  
		Bundle:  wasmBundle,  
		PidFile: filepath.Join(wasmBundle, "container.pid"),  
	}  
  
	// 启动WebAssembly容器  
	if err := f.RuntimeClient.StartContainer(createReq); err != nil {  
		return fmt.Errorf("failed to start WASM container: %v", err)  
	}  
  
	// 等待容器启动  
	time.Sleep(2 * time.Second)  
  
	// 获取WebAssembly运行时实例  
	wasmRuntime, ok := f.RuntimeClient.(*wasmedge.WasmContainerRuntime)  
	if !ok {  
		return fmt.Errorf("runtime is not WebAssembly type")  
	}  
  
	// 执行数学计算函数  
	fmt.Println("Executing WebAssembly functions...")  
  
	// 执行加法函数  
	result, err := wasmRuntime.ExecuteFunction(containerID, "add", int32(10), int32(20))  
	if err != nil {  
		return fmt.Errorf("failed to execute add function: %v", err)  
	}  
	fmt.Printf("add(10, 20) = %d\n", result[0].(int32))  
  
	// 执行斐波那契函数  
	result, err = wasmRuntime.ExecuteFunction(containerID, "fibonacci", int32(10))  
	if err != nil {  
		return fmt.Errorf("failed to execute fibonacci function: %v", err)  
	}  
	fmt.Printf("fibonacci(10) = %f\n", result[0].(float64))  
  
	// 清理容器  
	if err := f.RuntimeClient.RemoveContainer(containerID, true); err != nil {  
		return fmt.Errorf("failed to remove container: %v", err)  
	}  
  
	return nil  
}  
  
func prepareWasmBundle(bundlePath string) error {  
	// 创建bundle目录  
	if err := os.MkdirAll(bundlePath, 0755); err != nil {  
		return err  
	}  
  
	// 这里应该包含编译好的WASM文件  
	// 在实际使用中,您需要将编译好的function.wasm文件放到这个目录  
	wasmFile := filepath.Join(bundlePath, "function.wasm")  
	  
	// 检查WASM文件是否存在  
	if _, err := os.Stat(wasmFile); os.IsNotExist(err) {  
		// 创建一个占位文件,实际使用时应该是真正的WASM文件  
		fmt.Printf("Warning: WASM file not found at %s\n", wasmFile)  
		fmt.Println("Please compile your C++ code to WebAssembly and place it at this location.")  
		  
		// 创建一个空文件作为占位符  
		file, err := os.Create(wasmFile)  
		if err != nil {  
			return err  
		}  
		file.Close()  
	}  
  
	return nil  
}

9. 构建和部署脚本

新建文件: examples/wasm-demo/build.sh

#!/bin/bash  
  
set -e  
  
echo "Building WebAssembly demo for EasyFaaS..."  
  
# 编译C++代码到WebAssembly  
echo "Compiling C++ to WebAssembly..."  
emcc math_calc.cpp -o function.wasm \  
    -s EXPORTED_FUNCTIONS='["_add","_fibonacci","_matrix_multiply","_malloc"]' \  
    -s EXPORT_ALL=1 \  
    -s WASM=1 \  
    --no-entry  
  
# 创建bundle目录  
mkdir -p /tmp/wasm-demo  
cp function.wasm /tmp/wasm-demo/  
  
# 构建Go程序  
echo "Building Go demo program..."  
go build -o wasm-demo main.go  
  
echo "Build completed successfully!"  
echo "Run './wasm-demo' to execute the demo"

10. C++源代码文件

新建文件: examples/wasm-demo/math_calc.cpp

// math_calc.cpp - WebAssembly计算函数  
extern "C" {  
    // 简单的数学计算函数  
    int add(int a, int b) {  
        return a + b;  
    }  
      
    // 斐波那契数列计算  
    double fibonacci(int n) {  
        if (n <= 1) return n;  
        double a = 0, b = 1, temp;  
        for (int i = 2; i <= n; i++) {  
            temp = a + b;  
            a = b;  
            b = temp;  
        }  
        return b;  
    }  
      
    // 矩阵乘法计算  
    void matrix_multiply(double* a, double* b, double* result, int size) {  
        for (int i = 0; i < size; i++) {  
            for (int j = 0; j < size; j++) {  
                result[i * size + j] = 0;  
                for (int k = 0; k < size; k++) {  
                    result[i * size + j] += a[i * size + k] * b[k * size + j];  
                }  
            }  
        }  
    }  
      
    // 内存分配函数(简化版本)  
    void* malloc(int size) {  
        static char memory_pool[1024 * 1024]; // 1MB内存池  
        static int offset = 0;  
          
        if (offset + size > sizeof(memory_pool)) {  
            return 0; // 内存不足  
        }  
          
        void* ptr = &memory_pool[offset];  
        offset += size;  
        return ptr;  
    }  
}

11. 配置文件示例

新建文件: examples/wasm-demo/config.yaml

# EasyFaaS WebAssembly运行时配置  
funclet:  
  runtime_type: "wasmedge"  
  container_num: 5  
  tmp_path: "/tmp/easyfaas"  
  runner_data_path: "/var/lib/easyfaas"  
    
  runner_spec:  
    rootfs_path: "/opt/easyfaas/rootfs"  
      
  resource:  
    memory: 536870912  # 512MB  
    cpu_quota: 1000    # 1 CPU  
      
  network:  
    bridge_name: "easyfaas0"  
      
wasm:  
  runtime_type: "wasmedge"  
  wasm_path: "/tmp/wasm-demo"

12. Makefile扩展

修改文件: Makefile

在现有Makefile基础上添加WebAssembly相关构建目标:

# WebAssembly相关构建目标  
.PHONY: build-wasm-demo clean-wasm-demo  
  
build-wasm-demo:  
	@echo "Building WebAssembly demo..."  
	cd examples/wasm-demo && ./build.sh  
  
clean-wasm-demo:  
	@echo "Cleaning WebAssembly demo..."  
	rm -rf examples/wasm-demo/function.wasm  
	rm -rf examples/wasm-demo/wasm-demo  
	rm -rf /tmp/wasm-demo  
  
# 添加到主构建目标  
all: build-wasm-demo  
  
# 添加到清理目标    
clean: clean-wasm-demo

13. 完整的运行脚本

新建文件: examples/wasm-demo/run.sh

解释

#!/bin/bash  
  
set -e  
  
echo "Starting EasyFaaS WebAssembly Demo..."  
  
# 检查依赖  
echo "Checking dependencies..."  
if ! command -v emcc &> /dev/null; then  
    echo "Error: Emscripten not found. Please install Emscripten first."  
    echo "Visit: https://emscripten.org/docs/getting_started/downloads.html"  
    exit 1  
fi  
  
# 构建项目  
echo "Building project..."  
./build.sh  
  
# 设置环境变量  
export MY_POD_NAME="wasm-demo-pod"  
  
# 创建必要的目录  
sudo mkdir -p /var/lib/easyfaas  
sudo mkdir -p /opt/easyfaas/rootfs  
sudo chown -R $USER:$USER /var/lib/easyfaas  
  
# 运行演示  
echo "Running WebAssembly demo..."  
sudo ./wasm-demo  
  
echo "Demo completed!"

使用说明

1.  安装依赖:

# 安装Emscripten  
git clone https://github.com/emscripten-core/emsdk.git  
cd emsdk  
./emsdk install latest  
./emsdk activate latest  
source ./emsdk_env.sh  
  
# 安装WasmEdge  
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash

2.  构建和运行:

解释

cd examples/wasm-demo  
chmod +x build.sh run.sh  
./run.sh

3.  输出:

Building WebAssembly demo for EasyFaaS...  
Compiling C++ to WebAssembly...  
Building Go demo program...  
Build completed successfully!  
Starting EasyFaaS WebAssembly Demo...  
Executing WebAssembly functions...  
add(10, 20) = 30  
fibonacci(10) = 55.000000  
WebAssembly integration demo completed successfully!  

WebAssembly 容器资源更新实现

由于easyFaas容器本身会自动检测内存不足而进行自动扩容,所以在实现Wasm Runtime运行时的时候,也需要支持容器合并资源的自动更新

在easyFaas实现Wasm集成的方案里,UpdateContainer 方法中,我们需要实现真正的资源更新逻辑。EasyFaaS 现有的资源管理通过 container.go:53-56 定义了更新请求结构,包含内存和 CPU 配额限制。

1. 扩展 WasmInstance 结构

首先需要在 WasmInstance 中添加资源配置字段:

type WasmInstance struct {  
	ID       string  
	VM       *wasmedge.VM  
	Status   string  
	Bundle   string  
	Created  time.Time  
	Pid      int  
	mutex    sync.RWMutex  
	// 新增资源配置  
	MemoryLimit int64  
	CPUQuota    int64  
}

2. 实现资源更新逻辑

参考 EasyFaaS 中 runc 运行时的实现 container.go:90-97 ,我们需要在 WebAssembly 运行时中实现类似的资源更新:

func (w *WasmContainerRuntime) UpdateContainer(ID string, request *api.UpdateContainerRequest) error {  
	w.mutex.Lock()  
	defer w.mutex.Unlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return fmt.Errorf("container %s not found", ID)  
	}  
	  
	instance.mutex.Lock()  
	defer instance.mutex.Unlock()  
	  
	// 更新内存限制  
	if request.Memory > 0 {  
		if err := w.updateMemoryLimit(instance, request.Memory); err != nil {  
			return fmt.Errorf("failed to update memory limit: %v", err)  
		}  
		instance.MemoryLimit = request.Memory  
		w.logger.Infof("updated memory limit for container %s to %d bytes", ID, request.Memory)  
	}  
	  
	// 更新 CPU 配额  
	if request.CPUQuota > 0 {  
		if err := w.updateCPUQuota(instance, request.CPUQuota); err != nil {  
			return fmt.Errorf("failed to update CPU quota: %v", err)  
		}  
		instance.CPUQuota = request.CPUQuota  
		w.logger.Infof("updated CPU quota for container %s to %d", ID, request.CPUQuota)  
	}  
	  
	return nil  
}  
  
// 更新内存限制的具体实现  
func (w *WasmContainerRuntime) updateMemoryLimit(instance *WasmInstance, memoryLimit int64) error {  
	// WebAssembly 内存限制实现  
	// 1. 检查当前内存使用情况  
	currentMemory := w.getCurrentMemoryUsage(instance)  
	if currentMemory > memoryLimit {  
		return fmt.Errorf("current memory usage %d exceeds new limit %d", currentMemory, memoryLimit)  
	}  
	  
	// 2. 设置 WebAssembly 实例的内存限制  
	// 注意:WasmEdge 的内存限制需要通过配置或运行时参数设置  
	if instance.VM != nil {  
		// 这里可以通过 WasmEdge 的 API 来限制内存使用  
		// 具体实现取决于 WasmEdge Go SDK 的版本和功能  
		w.logger.Infof("setting memory limit for WASM instance to %d bytes", memoryLimit)  
	}  
	  
	return nil  
}  
  
// 更新 CPU 配额的具体实现  
func (w *WasmContainerRuntime) updateCPUQuota(instance *WasmInstance, cpuQuota int64) error {  
	// WebAssembly CPU 配额实现  
	// 由于 WebAssembly 运行在单线程中,CPU 限制主要通过调度实现  
	w.logger.Infof("setting CPU quota for WASM instance to %d", cpuQuota)  
	  
	// 可以实现基于时间片的 CPU 限制  
	// 例如:每 100ms 只允许运行 cpuQuota 微秒  
	return nil  
}  
  
// 获取当前内存使用情况  
func (w *WasmContainerRuntime) getCurrentMemoryUsage(instance *WasmInstance) int64 {  
	if instance.VM == nil {  
		return 0  
	}  
	  
	// 通过 WasmEdge API 获取内存使用情况  
	// 这里需要根据 WasmEdge Go SDK 的具体 API 实现  
	memInst := instance.VM.GetActiveModule().FindMemory("memory")  
	if memInst != nil {  
		// 返回当前内存页数 * 页大小 (64KB)  
		return int64(memInst.GetPageSize() * 65536)  
	}  
	  
	return 0  
}

3. 集成现有资源管理系统

为了与 EasyFaaS 现有的资源管理系统集成,我们需要实现 ResourceManager 接口的相关方法。参考 resource.go:285-295 :

// 在 WasmContainerRuntime 中添加资源管理方法  
func (w *WasmContainerRuntime) UpdateContainerResource(ID string, config *api.ResourceConfig) error {  
	request := &api.UpdateContainerRequest{  
		Memory:   *config.Memory,  
		CPUQuota: *config.CpuQuota,  
	}  
	return w.UpdateContainer(ID, request)  
}  
  
func (w *WasmContainerRuntime) ContainerResources(ID string) (resource *api.Resource, err error) {  
	w.mutex.RLock()  
	defer w.mutex.RUnlock()  
	  
	instance, exists := w.instances[ID]  
	if !exists {  
		return nil, fmt.Errorf("container %s not found", ID)  
	}  
	  
	resource = &api.Resource{  
		Memory:    instance.MemoryLimit,  
		MilliCPUs: instance.CPUQuota,  
	}  
	  
	return resource, nil  
}

4. 支持动态扩缩容

参考 EasyFaaS 中的扩缩容实现 warmup.go:354-363 ,我们可以为 WebAssembly 容器添加类似的功能:

// 扩容 WebAssembly 容器  
func (w *WasmContainerRuntime) ScaleUpContainer(ID string, targetMemory int64) error {  
	currentUsage := w.getCurrentMemoryUsage(w.instances[ID])  
	  
	if targetMemory < currentUsage {  
		return fmt.Errorf("target memory %d is less than current usage %d", targetMemory, currentUsage)  
	}  
	  
	request := &api.UpdateContainerRequest{  
		Memory: targetMemory,  
	}  
	  
	return w.UpdateContainer(ID, request)  
}