随着智能穿戴设备,工控机以及一些终端设备的发展。在终端上做一些小型推理的需求迫在眉睫. 需要一个应用服务框架,用于在服务器、个人电脑和边缘设备上的 GPU 上运行 GenAI 模型(例如LLM、语音转文本、文本转图像和TTS).
WasmEdge介绍
WasmEdge 是一个轻量级、高性能且可扩展的 WebAssembly 运行时.从 CLI 或Docker运行独立的 Wasm 程序通过LlamaEdge与开源 LLM聊天 在Go、Rust或C应用程序 中嵌入 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)
}