Logo
热心市民王先生

多语言扩展性设计

架构设计 多语言支持 扩展性 运行时

设计支持 Node.js、Go、Python 等多语言的 Firecracker 沙箱运行时架构

本章设计一套具有良好扩展性的多语言运行时架构,确保 Firecracker 沙箱能够支持 Node.js、Go、Python 等多种开发语言,并易于添加新的语言支持。

设计原则

核心原则

flowchart TD
    A[多语言沙箱架构] --> B[语言无关层]
    A --> C[运行时抽象]
    A --> D[统一接口]
    A --> E[可插拔设计]
    
    B --> B1[容器化运行时]
    C --> C1[运行时管理器]
    D --> D1[Agent 协议]
    E --> E1[插件系统]
    
    style A fill:#e1f5e1
    style B fill:#d1ecf1
    style C fill:#d1ecf1
    style D fill:#d1ecf1
    style E fill:#d1ecf1
  1. 语言无关层:核心架构不依赖特定语言,通过抽象层隔离差异
  2. 运行时抽象:将语言运行时封装为统一的运行时单元
  3. 统一接口:提供一致的 Agent API,无论底层语言如何
  4. 可插拔设计:新语言支持通过插件形式添加,无需修改核心代码

扩展性目标

目标指标验证方式
添加新语言< 1 天从模板创建到可用
语言版本切换< 5 分钟修改配置重启
运行时升级零停机滚动更新支持
并发语言环境8+ 种同时运行不同语言沙箱

整体架构设计

架构分层

flowchart TB
    subgraph "应用层"
        A1[Node.js 项目]
        A2[Go 项目]
        A3[Python 项目]
        A4[其他语言...]
    end
    
    subgraph "编排层"
        B1[运行时调度器]
        B2[资源管理器]
        B3[生命周期管理]
    end
    
    subgraph "运行时层"
        C1[Node.js 运行时]
        C2[Go 运行时]
        C3[Python 运行时]
        C4[运行时插件...]
    end
    
    subgraph "基础设施层"
        D1[Firecracker MicroVM]
        D2[容器运行时]
        D3[文件系统]
        D4[网络栈]
    end
    
    A1 --> B1
    A2 --> B1
    A3 --> B1
    A4 --> B1
    
    B1 --> C1
    B1 --> C2
    B1 --> C3
    B1 --> C4
    
    C1 --> D1
    C2 --> D1
    C3 --> D1
    C4 --> D1
    
    style B1 fill:#fff3cd
    style C1 fill:#d1ecf1
    style D1 fill:#f8d7da

组件职责

层级组件职责技术选型
应用层项目配置定义语言类型、版本、依赖YAML/JSON
编排层运行时调度器选择合适的运行时、负载均衡Go/Rust
编排层资源管理器CPU/内存分配、资源限制cgroups/KVM
运行时层运行时插件语言特定环境准备Shell/Go
基础设施层MicroVM提供隔离执行环境Firecracker
基础设施层存储代码卷、依赖缓存VirtioFS/9P

运行时抽象层设计

运行时接口定义

// runtime/runtime.go
package runtime

import (
	"context"
	"io"
)

// Runtime 定义语言运行时的通用接口
type Runtime interface {
	// 元数据
	Name() string
	Version() string
	Languages() []string
	
	// 生命周期管理
	Prepare(ctx context.Context, config *RuntimeConfig) error
	Start(ctx context.Context) error
	Stop(ctx context.Context) error
	Status(ctx context.Context) (*RuntimeStatus, error)
	
	// 执行接口
	Execute(ctx context.Context, cmd *Command) (*ExecutionResult, error)
	StreamLogs(ctx context.Context, output io.Writer) error
	
	// 开发工具
	GetDevServerCommand() *Command
	GetTestCommand() *Command
	GetBuildCommand() *Command
	GetLintCommand() *Command
}

// RuntimeConfig 运行时配置
type RuntimeConfig struct {
	Language    string            `json:"language"`
	Version     string            `json:"version"`
	Workspace   string            `json:"workspace"`
	Environment map[string]string `json:"environment"`
	Resources   *ResourceLimits   `json:"resources"`
	Ports       []PortMapping     `json:"ports"`
}

type ResourceLimits struct {
	CPU    int    `json:"cpu"`    // vCPU 核心数
	Memory string `json:"memory"` // 内存,如 "512MiB"
	Disk   string `json:"disk"`   // 磁盘限制
}

type PortMapping struct {
	Host      int    `json:"host"`
	Container int    `json:"container"`
	Protocol  string `json:"protocol"` // tcp/udp
}

type Command struct {
	Name    string            `json:"name"`
	Args    []string          `json:"args"`
	WorkDir string            `json:"workDir"`
	Env     map[string]string `json:"env"`
	Timeout int               `json:"timeout"` // 秒
}

type ExecutionResult struct {
	Success  bool   `json:"success"`
	ExitCode int    `json:"exitCode"`
	Stdout   string `json:"stdout"`
	Stderr   string `json:"stderr"`
	Duration int64  `json:"duration"` // 毫秒
}

type RuntimeStatus struct {
	State     string `json:"state"`     // running, stopped, error
	PID       int    `json:"pid"`
	Uptime    int64  `json:"uptime"`    // 秒
	Memory    int64  `json:"memory"`    // 字节
	CPUUsage  float64 `json:"cpuUsage"` // 百分比
}

运行时工厂

// runtime/factory.go
package runtime

import (
	"fmt"
	"sync"
)

// RuntimeFactory 运行时工厂
type RuntimeFactory struct {
	runtimes map[string]RuntimeConstructor
	mu       sync.RWMutex
}

type RuntimeConstructor func() Runtime

var factory = &RuntimeFactory{
	runtimes: make(map[string]RuntimeConstructor),
}

// Register 注册新的运行时
func Register(name string, constructor RuntimeConstructor) {
	factory.mu.Lock()
	defer factory.mu.Unlock()
	factory.runtimes[name] = constructor
}

// Create 创建运行时实例
func Create(name string) (Runtime, error) {
	factory.mu.RLock()
	defer factory.mu.RUnlock()
	
	constructor, ok := factory.runtimes[name]
	if !ok {
		return nil, fmt.Errorf("runtime not found: %s", name)
	}
	
	return constructor(), nil
}

// List 列出所有可用的运行时
func List() []string {
	factory.mu.RLock()
	defer factory.mu.RUnlock()
	
	names := make([]string, 0, len(factory.runtimes))
	for name := range factory.runtimes {
		names = append(names, name)
	}
	return names
}

各语言运行时实现

Node.js 运行时

// runtime/nodejs/nodejs.go
package nodejs

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"time"
	
	"firecracker-sandbox/runtime"
)

func init() {
	runtime.Register("nodejs", NewNodeJSRuntime)
}

type NodeJSRuntime struct {
	config *runtime.RuntimeConfig
	status *runtime.RuntimeStatus
	cmd    *exec.Cmd
}

func NewNodeJSRuntime() runtime.Runtime {
	return &NodeJSRuntime{}
}

func (r *NodeJSRuntime) Name() string {
	return "nodejs"
}

func (r *NodeJSRuntime) Version() string {
	return "20.x"
}

func (r *NodeJSRuntime) Languages() []string {
	return []string{"javascript", "typescript", "nodejs"}
}

func (r *NodeJSRuntime) Prepare(ctx context.Context, config *runtime.RuntimeConfig) error {
	r.config = config
	
	// 验证 Node.js 版本
	if config.Version != "" && config.Version != "20.x" {
		return fmt.Errorf("unsupported Node.js version: %s", config.Version)
	}
	
	// 检查 package.json 是否存在
	packageJSON := filepath.Join(config.Workspace, "package.json")
	if _, err := exec.LookPath("node"); err != nil {
		return fmt.Errorf("node not found: %w", err)
	}
	
	// 验证项目结构
	if _, err := exec.Command("test", "-f", packageJSON).Output(); err != nil {
		return fmt.Errorf("package.json not found in workspace")
	}
	
	return nil
}

func (r *NodeJSRuntime) Start(ctx context.Context) error {
	// Node.js 作为解释型语言,无需显式"启动"
	// 实际执行由 Execute 方法处理
	r.status = &runtime.RuntimeStatus{
		State:  "ready",
		Uptime: 0,
	}
	return nil
}

func (r *NodeJSRuntime) Stop(ctx context.Context) error {
	if r.cmd != nil && r.cmd.Process != nil {
		return r.cmd.Process.Kill()
	}
	r.status.State = "stopped"
	return nil
}

func (r *NodeJSRuntime) Status(ctx context.Context) (*runtime.RuntimeStatus, error) {
	return r.status, nil
}

func (r *NodeJSRuntime) Execute(ctx context.Context, cmd *runtime.Command) (*runtime.ExecutionResult, error) {
	start := time.Now()
	
	execCmd := exec.CommandContext(ctx, cmd.Name, cmd.Args...)
	execCmd.Dir = cmd.WorkDir
	if execCmd.Dir == "" {
		execCmd.Dir = r.config.Workspace
	}
	
	// 设置环境变量
	execCmd.Env = append(execCmd.Env, fmt.Sprintf("NODE_ENV=%s", r.config.Environment["NODE_ENV"]))
	for k, v := range cmd.Env {
		execCmd.Env = append(execCmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	
	// 执行命令
	output, err := execCmd.CombinedOutput()
	
	result := &runtime.ExecutionResult{
		Success:  err == nil,
		ExitCode: 0,
		Stdout:   string(output),
		Stderr:   "",
		Duration: time.Since(start).Milliseconds(),
	}
	
	if exitErr, ok := err.(*exec.ExitError); ok {
		result.ExitCode = exitErr.ExitCode()
	}
	
	return result, nil
}

func (r *NodeJSRuntime) StreamLogs(ctx context.Context, output io.Writer) error {
	// Node.js 日志直接输出到 stdout/stderr
	// 实际项目中可通过文件或 socket 收集
	return nil
}

func (r *NodeJSRuntime) GetDevServerCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "npm",
		Args:    []string{"run", "dev"},
		WorkDir: r.config.Workspace,
		Timeout: 0, // 长期运行
	}
}

func (r *NodeJSRuntime) GetTestCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "npm",
		Args:    []string{"test"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
		Env:     map[string]string{"CI": "true"},
	}
}

func (r *NodeJSRuntime) GetBuildCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "npm",
		Args:    []string{"run", "build"},
		WorkDir: r.config.Workspace,
		Timeout: 600,
	}
}

func (r *NodeJSRuntime) GetLintCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "npm",
		Args:    []string{"run", "lint"},
		WorkDir: r.config.Workspace,
		Timeout: 120,
	}
}

Go 运行时

// runtime/golang/golang.go
package golang

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"time"
	
	"firecracker-sandbox/runtime"
)

func init() {
	runtime.Register("go", NewGoRuntime)
	runtime.Register("golang", NewGoRuntime)
}

type GoRuntime struct {
	config *runtime.RuntimeConfig
	status *runtime.RuntimeStatus
}

func NewGoRuntime() runtime.Runtime {
	return &GoRuntime{}
}

func (r *GoRuntime) Name() string {
	return "go"
}

func (r *GoRuntime) Version() string {
	return "1.22"
}

func (r *GoRuntime) Languages() []string {
	return []string{"go", "golang"}
}

func (r *GoRuntime) Prepare(ctx context.Context, config *runtime.RuntimeConfig) error {
	r.config = config
	
	// 验证 Go 安装
	if _, err := exec.LookPath("go"); err != nil {
		return fmt.Errorf("go not found: %w", err)
	}
	
	// 检查 go.mod
	goMod := filepath.Join(config.Workspace, "go.mod")
	if _, err := exec.Command("test", "-f", goMod).Output(); err != nil {
		// 尝试初始化
		initCmd := exec.Command("go", "mod", "init", "app")
		initCmd.Dir = config.Workspace
		if err := initCmd.Run(); err != nil {
			return fmt.Errorf("failed to initialize go module: %w", err)
		}
	}
	
	// 下载依赖
	downloadCmd := exec.Command("go", "mod", "download")
	downloadCmd.Dir = config.Workspace
	if output, err := downloadCmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to download dependencies: %s", output)
	}
	
	return nil
}

func (r *GoRuntime) Start(ctx context.Context) error {
	r.status = &runtime.RuntimeStatus{
		State: "ready",
	}
	return nil
}

func (r *GoRuntime) Stop(ctx context.Context) error {
	r.status.State = "stopped"
	return nil
}

func (r *GoRuntime) Status(ctx context.Context) (*runtime.RuntimeStatus, error) {
	return r.status, nil
}

func (r *GoRuntime) Execute(ctx context.Context, cmd *runtime.Command) (*runtime.ExecutionResult, error) {
	start := time.Now()
	
	execCmd := exec.CommandContext(ctx, cmd.Name, cmd.Args...)
	execCmd.Dir = cmd.WorkDir
	if execCmd.Dir == "" {
		execCmd.Dir = r.config.Workspace
	}
	
	for k, v := range cmd.Env {
		execCmd.Env = append(execCmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	
	output, err := execCmd.CombinedOutput()
	
	result := &runtime.ExecutionResult{
		Success:  err == nil,
		ExitCode: 0,
		Stdout:   string(output),
		Duration: time.Since(start).Milliseconds(),
	}
	
	if exitErr, ok := err.(*exec.ExitError); ok {
		result.ExitCode = exitErr.ExitCode()
	}
	
	return result, nil
}

func (r *GoRuntime) StreamLogs(ctx context.Context, output io.Writer) error {
	return nil
}

func (r *GoRuntime) GetDevServerCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "go",
		Args:    []string{"run", "."},
		WorkDir: r.config.Workspace,
		Timeout: 0,
	}
}

func (r *GoRuntime) GetTestCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "go",
		Args:    []string{"test", "./...", "-v"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

func (r *GoRuntime) GetBuildCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "go",
		Args:    []string{"build", "-o", "app", "."},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

func (r *GoRuntime) GetLintCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "golangci-lint",
		Args:    []string{"run"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

Python 运行时

// runtime/python/python.go
package python

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"time"
	
	"firecracker-sandbox/runtime"
)

func init() {
	runtime.Register("python", NewPythonRuntime)
	runtime.Register("python3", NewPythonRuntime)
}

type PythonRuntime struct {
	config *runtime.RuntimeConfig
	status *runtime.RuntimeStatus
}

func NewPythonRuntime() runtime.Runtime {
	return &PythonRuntime{}
}

func (r *PythonRuntime) Name() string {
	return "python"
}

func (r *PythonRuntime) Version() string {
	return "3.12"
}

func (r *PythonRuntime) Languages() []string {
	return []string{"python", "python3"}
}

func (r *PythonRuntime) Prepare(ctx context.Context, config *runtime.RuntimeConfig) error {
	r.config = config
	
	// 检测 Python 版本
	pythonCmd := "python3"
	if _, err := exec.LookPath("python3"); err != nil {
		if _, err := exec.LookPath("python"); err != nil {
			return fmt.Errorf("python not found")
		}
		pythonCmd = "python"
	}
	
	// 检查 requirements.txt 或 pyproject.toml
	reqFile := filepath.Join(config.Workspace, "requirements.txt")
	pyprojectFile := filepath.Join(config.Workspace, "pyproject.toml")
	
	if _, err := exec.Command("test", "-f", reqFile).Output(); err == nil {
		// 安装 requirements
		installCmd := exec.Command(pythonCmd, "-m", "pip", "install", "-r", "requirements.txt")
		installCmd.Dir = config.Workspace
		if output, err := installCmd.CombinedOutput(); err != nil {
			return fmt.Errorf("failed to install requirements: %s", output)
		}
	} else if _, err := exec.Command("test", "-f", pyprojectFile).Output(); err == nil {
		// 安装 pyproject 项目
		installCmd := exec.Command(pythonCmd, "-m", "pip", "install", "-e", ".")
		installCmd.Dir = config.Workspace
		if output, err := installCmd.CombinedOutput(); err != nil {
			return fmt.Errorf("failed to install project: %s", output)
		}
	}
	
	return nil
}

func (r *PythonRuntime) Start(ctx context.Context) error {
	r.status = &runtime.RuntimeStatus{
		State: "ready",
	}
	return nil
}

func (r *PythonRuntime) Stop(ctx context.Context) error {
	r.status.State = "stopped"
	return nil
}

func (r *PythonRuntime) Status(ctx context.Context) (*runtime.RuntimeStatus, error) {
	return r.status, nil
}

func (r *PythonRuntime) Execute(ctx context.Context, cmd *runtime.Command) (*runtime.ExecutionResult, error) {
	start := time.Now()
	
	execCmd := exec.CommandContext(ctx, cmd.Name, cmd.Args...)
	execCmd.Dir = cmd.WorkDir
	if execCmd.Dir == "" {
		execCmd.Dir = r.config.Workspace
	}
	
	for k, v := range cmd.Env {
		execCmd.Env = append(execCmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	
	output, err := execCmd.CombinedOutput()
	
	result := &runtime.ExecutionResult{
		Success:  err == nil,
		ExitCode: 0,
		Stdout:   string(output),
		Duration: time.Since(start).Milliseconds(),
	}
	
	if exitErr, ok := err.(*exec.ExitError); ok {
		result.ExitCode = exitErr.ExitCode()
	}
	
	return result, nil
}

func (r *PythonRuntime) StreamLogs(ctx context.Context, output io.Writer) error {
	return nil
}

func (r *PythonRuntime) GetDevServerCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "python",
		Args:    []string{"-m", "uvicorn", "main:app", "--reload"},
		WorkDir: r.config.Workspace,
		Timeout: 0,
	}
}

func (r *PythonRuntime) GetTestCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "python",
		Args:    []string{"-m", "pytest", "-v"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

func (r *PythonRuntime) GetBuildCommand() *runtime.Command {
	// Python 通常不需要显式构建
	return &runtime.Command{
		Name:    "echo",
		Args:    []string{"Python project - no build step required"},
		WorkDir: r.config.Workspace,
		Timeout: 10,
	}
}

func (r *PythonRuntime) GetLintCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "python",
		Args:    []string{"-m", "flake8", "."},
		WorkDir: r.config.Workspace,
		Timeout: 120,
	}
}

MicroVM 镜像设计

分层镜像架构

flowchart TB
    subgraph "基础层"
        A[Alpine Linux<br/>~5MB]
    end
    
    subgraph "运行时层"
        B1[Node.js Runtime<br/>~50MB]
        B2[Go Runtime<br/>~150MB]
        B3[Python Runtime<br/>~30MB]
    end
    
    subgraph "依赖层"
        C1[npm packages<br/>项目特定]
        C2[Go modules<br/>项目特定]
        C3[pip packages<br/>项目特定]
    end
    
    subgraph "应用层"
        D1[Application Code]
    end
    
    A --> B1
    A --> B2
    A --> B3
    
    B1 --> C1
    B2 --> C2
    B3 --> C3
    
    C1 --> D1
    C2 --> D1
    C3 --> D1

镜像构建流程

#!/bin/bash
# build-runtime-image.sh

LANGUAGE=$1
VERSION=$2
OUTPUT=$3

# 基础目录
BASE_DIR="/tmp/firecracker-images"
mkdir -p "$BASE_DIR"

# 创建基础根文件系统
create_base_rootfs() {
    local size=$1
    local output=$2
    
    dd if=/dev/zero of="$output" bs=1M count=$size
    mkfs.ext4 "$output"
    
    local mount_point="/mnt/rootfs-$$"
    mkdir -p "$mount_point"
    mount -o loop "$output" "$mount_point"
    
    # 安装 Alpine 基础系统
    docker run --rm -v "$mount_point":/target alpine:3.19 \
        sh -c 'apk add --no-cache --root /target alpine-base'
    
    umount "$mount_point"
    rmdir "$mount_point"
}

# 安装 Node.js 运行时
install_nodejs() {
    local rootfs=$1
    local version=$2
    
    local mount_point="/mnt/nodejs-$$"
    mkdir -p "$mount_point"
    mount -o loop "$rootfs" "$mount_point"
    
    # 安装 Node.js
    docker run --rm -v "$mount_point":/target node:${version}-alpine \
        sh -c 'cp -r /usr/local/bin/node /usr/local/bin/npm /target/usr/local/bin/'
    
    # 安装常用工具
    docker run --rm -v "$mount_point":/target alpine:3.19 \
        sh -c 'apk add --root /target --no-cache git curl rsync'
    
    umount "$mount_point"
    rmdir "$mount_point"
}

# 安装 Go 运行时
install_go() {
    local rootfs=$1
    local version=$2
    
    local mount_point="/mnt/go-$$"
    mkdir -p "$mount_point"
    mount -o loop "$rootfs" "$mount_point"
    
    # 下载并安装 Go
    local go_tar="go${version}.linux-arm64.tar.gz"
    wget "https://go.dev/dl/${go_tar}" -O "/tmp/${go_tar}"
    
    tar -C "$mount_point/usr/local" -xzf "/tmp/${go_tar}"
    
    # 创建符号链接
    mkdir -p "$mount_point/usr/local/bin"
    ln -sf /usr/local/go/bin/go "$mount_point/usr/local/bin/go"
    
    # 安装常用工具
    docker run --rm -v "$mount_point":/target alpine:3.19 \
        sh -c 'apk add --root /target --no-cache git curl make gcc musl-dev'
    
    umount "$mount_point"
    rmdir "$mount_point"
}

# 安装 Python 运行时
install_python() {
    local rootfs=$1
    local version=$2
    
    local mount_point="/mnt/python-$$"
    mkdir -p "$mount_point"
    mount -o loop "$rootfs" "$mount_point"
    
    # 安装 Python
    docker run --rm -v "$mount_point":/target python:${version}-alpine \
        sh -c 'cp -r /usr/local/bin/python* /usr/local/bin/pip* /target/usr/local/bin/'
    
    umount "$mount_point"
    rmdir "$mount_point"
}

# 主流程
case "$LANGUAGE" in
    nodejs|node)
        echo "Building Node.js ${VERSION} runtime image..."
        create_base_rootfs 256 "$OUTPUT"
        install_nodejs "$OUTPUT" "$VERSION"
        ;;
    go|golang)
        echo "Building Go ${VERSION} runtime image..."
        create_base_rootfs 512 "$OUTPUT"
        install_go "$OUTPUT" "$VERSION"
        ;;
    python|python3)
        echo "Building Python ${VERSION} runtime image..."
        create_base_rootfs 256 "$OUTPUT"
        install_python "$OUTPUT" "$VERSION"
        ;;
    *)
        echo "Unknown language: $LANGUAGE"
        exit 1
        ;;
esac

echo "✓ Runtime image built: $OUTPUT"

扩展接口设计

添加新语言运行时

// runtime/template/template.go
// 新语言运行时模板

package template

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"time"
	
	"firecracker-sandbox/runtime"
)

// init 函数自动注册运行时
func init() {
	// 修改这里的名称
	runtime.Register("your-language", NewYourLanguageRuntime)
}

// YourLanguageRuntime 实现 Runtime 接口
type YourLanguageRuntime struct {
	config *runtime.RuntimeConfig
	status *runtime.RuntimeStatus
}

// NewYourLanguageRuntime 构造函数
func NewYourLanguageRuntime() runtime.Runtime {
	return &YourLanguageRuntime{}
}

// Name 返回运行时名称
func (r *YourLanguageRuntime) Name() string {
	return "your-language"
}

// Version 返回默认版本
func (r *YourLanguageRuntime) Version() string {
	return "1.0"
}

// Languages 返回支持的语言标识符
func (r *YourLanguageRuntime) Languages() []string {
	return []string{"your-lang", "yl"}
}

// Prepare 准备运行时环境
func (r *YourLanguageRuntime) Prepare(ctx context.Context, config *runtime.RuntimeConfig) error {
	r.config = config
	
	// TODO: 验证运行时安装
	// if _, err := exec.LookPath("your-lang"); err != nil {
	//     return fmt.Errorf("your-lang not found: %w", err)
	// }
	
	// TODO: 验证/安装依赖
	
	return nil
}

// Start 启动运行时
func (r *YourLanguageRuntime) Start(ctx context.Context) error {
	r.status = &runtime.RuntimeStatus{
		State: "ready",
	}
	return nil
}

// Stop 停止运行时
func (r *YourLanguageRuntime) Stop(ctx context.Context) error {
	r.status.State = "stopped"
	return nil
}

// Status 返回运行时状态
func (r *YourLanguageRuntime) Status(ctx context.Context) (*runtime.RuntimeStatus, error) {
	return r.status, nil
}

// Execute 执行命令
func (r *YourLanguageRuntime) Execute(ctx context.Context, cmd *runtime.Command) (*runtime.ExecutionResult, error) {
	start := time.Now()
	
	execCmd := exec.CommandContext(ctx, cmd.Name, cmd.Args...)
	execCmd.Dir = cmd.WorkDir
	if execCmd.Dir == "" {
		execCmd.Dir = r.config.Workspace
	}
	
	for k, v := range cmd.Env {
		execCmd.Env = append(execCmd.Env, fmt.Sprintf("%s=%s", k, v))
	}
	
	output, err := execCmd.CombinedOutput()
	
	result := &runtime.ExecutionResult{
		Success:  err == nil,
		ExitCode: 0,
		Stdout:   string(output),
		Duration: time.Since(start).Milliseconds(),
	}
	
	if exitErr, ok := err.(*exec.ExitError); ok {
		result.ExitCode = exitErr.ExitCode()
	}
	
	return result, nil
}

// StreamLogs 流式日志输出
func (r *YourLanguageRuntime) StreamLogs(ctx context.Context, output io.Writer) error {
	// TODO: 实现日志流
	return nil
}

// GetDevServerCommand 获取开发服务器命令
func (r *YourLanguageRuntime) GetDevServerCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "your-lang",
		Args:    []string{"serve"},
		WorkDir: r.config.Workspace,
		Timeout: 0,
	}
}

// GetTestCommand 获取测试命令
func (r *YourLanguageRuntime) GetTestCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "your-lang",
		Args:    []string{"test"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

// GetBuildCommand 获取构建命令
func (r *YourLanguageRuntime) GetBuildCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "your-lang",
		Args:    []string{"build"},
		WorkDir: r.config.Workspace,
		Timeout: 300,
	}
}

// GetLintCommand 获取代码检查命令
func (r *YourLanguageRuntime) GetLintCommand() *runtime.Command {
	return &runtime.Command{
		Name:    "your-lang",
		Args:    []string{"lint"},
		WorkDir: r.config.Workspace,
		Timeout: 120,
	}
}

运行时配置规范

# .firecracker/runtime.yaml
version: "1.0"

# 运行时定义
runtimes:
  nodejs:
    name: "Node.js"
    versions:
      - "18.x"
      - "20.x"
      - "21.x"
    default_version: "20.x"
    image_template: "nodejs-{{version}}-alpine"
    ports:
      - 3000  # 默认应用端口
      - 9229  # 调试端口
    commands:
      install: "npm install"
      dev: "npm run dev"
      test: "npm test"
      build: "npm run build"
      lint: "npm run lint"
    file_patterns:
      - "package.json"
      - "package-lock.json"
      - "yarn.lock"
      - "node_modules/**"

  go:
    name: "Go"
    versions:
      - "1.21"
      - "1.22"
    default_version: "1.22"
    image_template: "go-{{version}}-alpine"
    ports:
      - 8080
      - 2345  # Delve 调试端口
    commands:
      install: "go mod download"
      dev: "go run ."
      test: "go test ./..."
      build: "go build -o app ."
      lint: "golangci-lint run"
    file_patterns:
      - "go.mod"
      - "go.sum"
      - "*.go"

  python:
    name: "Python"
    versions:
      - "3.10"
      - "3.11"
      - "3.12"
    default_version: "3.12"
    image_template: "python-{{version}}-alpine"
    ports:
      - 8000
      - 5678  # debugpy 端口
    commands:
      install: "pip install -r requirements.txt"
      dev: "python -m uvicorn main:app --reload"
      test: "pytest"
      build: "echo 'No build step'"
      lint: "flake8"
    file_patterns:
      - "requirements.txt"
      - "pyproject.toml"
      - "*.py"

# 项目配置
project:
  runtime: "nodejs"  # 从上面选择
  version: "20.x"    # 可选,使用默认版本
  workspace: "/app"
  
  resources:
    cpu: 2
    memory: "1GiB"
    disk: "10GiB"
  
  ports:
    - host: 3000
      container: 3000
    - host: 9229
      container: 9229
  
  environment:
    NODE_ENV: "development"
    API_URL: "http://localhost:8080"

运行时调度策略

资源调度算法

// scheduler/scheduler.go
package scheduler

import (
	"context"
	"fmt"
	"sync"
	
	"firecracker-sandbox/runtime"
)

// Scheduler 运行时调度器
type Scheduler struct {
	mu       sync.RWMutex
	runtimes map[string]runtime.Runtime
	vms      map[string]*VMInstance
}

type VMInstance struct {
	ID        string
	Runtime   runtime.Runtime
	Config    *runtime.RuntimeConfig
	Status    string
	Resources *ResourceUsage
}

type ResourceUsage struct {
	CPU    float64 // 百分比
	Memory int64   // 字节
}

// Schedule 调度新的运行时实例
func (s *Scheduler) Schedule(ctx context.Context, config *runtime.RuntimeConfig) (*VMInstance, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	
	// 1. 检查资源可用性
	if err := s.checkResources(config.Resources); err != nil {
		return nil, fmt.Errorf("insufficient resources: %w", err)
	}
	
	// 2. 创建运行时实例
	rt, err := runtime.Create(config.Language)
	if err != nil {
		return nil, fmt.Errorf("failed to create runtime: %w", err)
	}
	
	// 3. 准备运行时
	if err := rt.Prepare(ctx, config); err != nil {
		return nil, fmt.Errorf("failed to prepare runtime: %w", err)
	}
	
	// 4. 启动运行时
	if err := rt.Start(ctx); err != nil {
		return nil, fmt.Errorf("failed to start runtime: %w", err)
	}
	
	// 5. 注册实例
	vm := &VMInstance{
		ID:      generateVMID(),
		Runtime: rt,
		Config:  config,
		Status:  "running",
	}
	s.vms[vm.ID] = vm
	
	return vm, nil
}

// checkResources 检查资源是否充足
func (s *Scheduler) checkResources(req *runtime.ResourceLimits) error {
	// TODO: 实现资源检查逻辑
	// - 检查总 CPU 是否超过限制
	// - 检查总内存是否超过限制
	// - 检查磁盘空间
	return nil
}

// generateVMID 生成唯一的 VM ID
func generateVMID() string {
	// TODO: 实现 ID 生成
	return "vm-" + generateRandomString(8)
}

func generateRandomString(n int) string {
	// 简化的实现
	const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
	b := make([]byte, n)
	for i := range b {
		b[i] = letters[i%len(letters)]
	}
	return string(b)
}

并发管理

flowchart TD
    A[调度请求] --> B{资源充足?}
    B -->|否| C[加入等待队列]
    C --> D[资源释放通知]
    D --> B
    
    B -->|是| E[创建 MicroVM]
    E --> F[分配资源]
    F --> G[启动运行时]
    G --> H{启动成功?}
    H -->|否| I[清理资源]
    I --> J[返回错误]
    
    H -->|是| K[注册实例]
    K --> L[返回实例信息]
    
    style C fill:#fff3cd
    style I fill:#f8d7da
    style L fill:#d4edda

总结

架构优势

  1. 语言无关:核心架构不依赖特定语言,易于扩展
  2. 统一接口:所有语言提供一致的 Runtime 接口
  3. 分层设计:清晰的层次结构,便于维护和测试
  4. 插件化:新语言支持通过插件形式添加

已支持语言

语言状态版本支持备注
Node.js✅ 已支持18.x, 20.x, 21.x完整功能
Go✅ 已支持1.21, 1.22完整功能
Python✅ 已支持3.10, 3.11, 3.12完整功能
Java🔄 计划中-需求评估中
Rust🔄 计划中-需求评估中
Ruby🔄 计划中-需求评估中

扩展指南

添加新语言运行时只需:

  1. 实现 runtime.Runtime 接口
  2. init() 中注册运行时
  3. 创建对应的 MicroVM 镜像
  4. 更新配置文件规范

预计工作量:4-8 小时

下一步

完成多语言架构设计后,请参阅 06-实施建议 制定详细实施计划。


本章参考资料: