WASMエッジゲートウェイ実践:GoでプラグインベースAPIゲートウェイを構築する5つのコア設計

边缘计算

APIゲートウェイに機能を追加するだけでリリースと再起動が必要

深夜2時、オンラインゲートウェイに急なレート制限ポリシー変更の要件が発生——KongプラグインはLuaでしか書けず、APISIXプラグインはOpenRestyでしか動かず、EnvoyネイティブFilterはC++でバイナリ全体を再コンパイルする必要があることに気づく。プラグイン言語の制限、ホットアップデート不可、セキュリティ隔離の不十分さ — 機能変更のたびにフルリリースが必要で、ゲートウェイチームが全社のボトルネックになっている。

2026年、WASMエッジゲートウェイがこの状況を一変させる。GoホストプロセスがWASMプラグインをロードし、TinyGo/Rust/AssemblyScriptから選択可能、プラグインはサンドボックス内で実行され脱出不可能、ホットリロードでゼロダウンタイム、Envoy WASM Filterとのシームレスな統合。本ガイドでは5つのコア設計を、ゲートウェイアーキテクチャからGoホストランタイム、WASMプラグイン開発からEnvoy Filter統合、エッジデプロイからホットリロードまで、完全なコード付きで解説する。


WASMエッジゲートウェイコア概念

概念 説明
WASMゲートウェイ WebAssemblyプラグインシステムベースのAPIゲートウェイ、プラグインは多言語開発、サンドボックス隔離、ホットロード可能
WASMプラグイン .wasmバイナリにコンパイルされたゲートウェイ拡張モジュール、サンドボックス内で実行、認証・レート制限・変換等をサポート
Envoy WASM Filter EnvoyプロキシのWASMフィルター、Proxy-WASM SDKで開発、Go/C++/Rustに組み込み可能
Proxy-WASM Envoyが定義するWASMプロキシインターフェース仕様、onHttpRequest/onHttpResponse等のライフサイクルフックを定義
エッジルーティング CDN/エッジノードで実行されるAPIルーティングルール、リクエストを最寄りのバックエンドサービスにディスパッチ
プラグインサンドボックス WASMリニアメモリ分離モデル、プラグインはホストプロセスメモリやシステムリソースにアクセス不可
ホットリロード 実行時にWASMプラグインモジュールを置き換え、ゲートウェイプロセスの再起動不要、リクエストゼロロス

WASMエッジゲートウェイアーキテクチャ

┌──────────────────────────────────────────────────────┐
│                   Edge Gateway (Go)                   │
│  ┌──────────┐  ┌───────────┐  ┌──────────────────┐  │
│  │  Route   │  │  Plugin   │  │  Plugin Chain    │  │
│  │  Matcher │  │  Loader   │  │  ┌──┐┌──┐┌──┐   │  │
│  └────┬─────┘  └─────┬─────┘  │  │Au││RL││Co│   │  │
│       │              │        │  │th││im││RS│   │  │
│  ┌────▼──────────────▼────┐   │  └──┘└──┘└──┘   │  │
│  │    Wazero Runtime      │   └────────┬─────────┘  │
│  │  ┌──────┐ ┌──────┐    │            │            │
│  │  │Auth  │ │Rate  │    │  ┌─────────▼──────────┐ │
│  │  │WASM  │ │Limit │    │  │  Reverse Proxy     │ │
│  │  │Plugin│ │WASM  │    │  │  (Upstream)        │ │
│  │  └──────┘ └──────┘    │  └────────────────────┘ │
│  └───────────────────────┘                          │
└──────────────────────────────────────────────────────┘

問題分析:従来のAPIゲートウェイの5つの限界

  1. プラグイン言語の制限:KongはLuaのみ、APISIXはLua/Goのみ(コンパイル必要)、EnvoyネイティブFilterはC++の再コンパイルが必要 — チームの技術スタックがゲートウェイに束縛される
  2. ホットアップデート不可:プラグインロジックの変更にはゲートウェイプロセスの再起動や設定の再読み込みが必要 — リスクが高くメンテナンスウィンドウが長い
  3. セキュリティ隔離の不十分さ:Luaプラグインがホストプロセスメモリに直接アクセス — 悪意のある/バグのあるプラグイン1つでゲートウェイインスタンス全体をクラッシュさせる可能性
  4. 拡張コストが高い:新しい認証方式やカスタムレート制限の追加にはゲートウェイソースコードの深い理解が必要 — 開発サイクルが長く、回帰テストの範囲が広い
  5. マルチゲートウェイの不統一:Kong/APISIX/Envoy/Nginxがそれぞれ独自のプラグイン体系を持ち — クロスゲートウェイポリシーの再利用が不可能、保守コストが倍増

設計1:WASMゲートウェイアーキテクチャ設計

WASMゲートウェイの核心的なアイデアはゲートウェイ機能をプラグイン可能なWASMモジュールに分解すること。Goホストがルーティングディスパッチとプラグインオーケストレーションを担当し、WASMプラグインが具体的なビジネスロジックを担当する。

package gateway

type GatewayConfig struct {
    ListenAddr    string            `json:"listenAddr"`
    Routes        []RouteConfig     `json:"routes"`
    PluginChain   []PluginConfig    `json:"pluginChain"`
    UpstreamPool  map[string]string `json:"upstreamPool"`
}

type RouteConfig struct {
    Path        string   `json:"path"`
    Methods     []string `json:"methods"`
    PluginChain []string `json:"pluginChain"`
    Upstream    string   `json:"upstream"`
    Rewrite     string   `json:"rewrite,omitempty"`
}

type PluginConfig struct {
    Name    string `json:"name"`
    WasmURL string `json:"wasmUrl"`
    Config  []byte `json:"config"`
}

type EdgeGateway struct {
    config   GatewayConfig
    runtime  *WASMRuntime
    routes   *RouteMatcher
    plugins  *PluginChainManager
    proxy    *ReverseProxy
}

func NewEdgeGateway(cfg GatewayConfig) (*EdgeGateway, error) {
    rt, err := NewWASMRuntime()
    if err != nil {
        return nil, fmt.Errorf("init wasm runtime: %w", err)
    }

    pcm, err := NewPluginChainManager(rt, cfg.PluginChain)
    if err != nil {
        return nil, fmt.Errorf("init plugin chain: %w", err)
    }

    return &EdgeGateway{
        config:  cfg,
        runtime: rt,
        routes:  NewRouteMatcher(cfg.Routes),
        plugins: pcm,
        proxy:   NewReverseProxy(cfg.UpstreamPool),
    }, nil
}

設計2:Goホストランタイム実装

wazeroランタイムを使用してWASMプラグインをロードし、各プラグインは独立したサンドボックスで実行され、エクスポート関数を通じてリクエスト/レスポンスインターセプトを実装する。

package gateway

import (
    "context"
    "net/http"

    "github.com/tetratelabs/wazero"
    "github.com/tetratelabs/wazero/api"
    "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
)

type WASMPlugin struct {
    Name    string
    runtime wazero.Runtime
    module  api.Module
}

type WASMRuntime struct {
    runtime wazero.Runtime
    ctx     context.Context
    plugins map[string]*WASMPlugin
}

func NewWASMRuntime() (*WASMRuntime, error) {
    ctx := context.Background()
    rt := wazero.NewRuntime(ctx)

    _, err := rt.NewHostModuleBuilder("env").
        NewFunctionBuilder().
        WithFunc(func(m api.Module, offset uint32, size uint32) {
            buf := make([]byte, size)
            m.Memory().Read(offset, buf)
        }).
        Export("log").
        NewFunctionBuilder().
        WithFunc(func(m api.Module, statusCode uint32, bodyOff uint32, bodyLen uint32) {
            buf := make([]byte, bodyLen)
            m.Memory().Read(bodyOff, buf)
        }).
        Export("sendLocalReply").
        Instantiate(ctx)
    if err != nil {
        return nil, err
    }

    wasi_snapshot_preview1.MustInstantiate(ctx, rt)

    return &WASMRuntime{
        runtime: rt,
        ctx:     ctx,
        plugins: make(map[string]*WASMPlugin),
    }, nil
}

func (wr *WASMRuntime) LoadPlugin(name string, wasmBytes []byte) (*WASMPlugin, error) {
    mod, err := wr.runtime.Instantiate(wr.ctx, wasmBytes)
    if err != nil {
        return nil, fmt.Errorf("instantiate plugin %s: %w", name, err)
    }

    plugin := &WASMPlugin{
        Name:    name,
        runtime: wr.runtime,
        module:  mod,
    }
    wr.plugins[name] = plugin
    return plugin, nil
}

func (p *WASMPlugin) ExecuteRequestFilter(req *http.Request) (*http.Response, bool) {
    ctx := context.Background()
    fn := p.module.ExportedFunction("onHttpRequest")
    if fn == nil {
        return nil, true
    }

    results, err := fn.Call(ctx)
    if err != nil {
        return nil, true
    }

    return nil, results[0] == 0
}

func (p *WASMPlugin) ExecuteResponseFilter(resp *http.Response) bool {
    ctx := context.Background()
    fn := p.module.ExportedFunction("onHttpResponse")
    if fn == nil {
        return true
    }

    results, err := fn.Call(ctx)
    if err != nil {
        return true
    }

    return results[0] == 0
}

設計3:WASMプラグイン開発

TinyGoを使用して認証とレート制限プラグインを作成し、WASIターゲットにコンパイル、onHttpRequestonHttpResponseのライフサイクルフックをエクスポートする。

package main

import "unsafe"

var memoryBuffer []byte

//export allocate
func allocate(size int32) unsafe.Pointer {
    memoryBuffer = make([]byte, size)
    return unsafe.Pointer(&memoryBuffer[0])
}

//export onHttpRequest
func onHttpRequest() int32 {
    headers := getHeaders()
    token := getHeader(headers, "Authorization")
    if token == "" {
        sendLocalReply(401, "Unauthorized")
        return 1
    }
    if !validateToken(token) {
        sendLocalReply(403, "Forbidden")
        return 1
    }
    return 0
}

//export onHttpResponse
func onHttpResponse() int32 {
    addHeader("X-Response-Time", getCurrentTime())
    addHeader("X-Gateway-Version", "1.0.0")
    return 0
}

func validateToken(token string) bool {
    if len(token) < 10 {
        return false
    }
    return true
}

func getHeaders() map[string]string {
    return make(map[string]string)
}

func getHeader(headers map[string]string, key string) string {
    return headers[key]
}

func sendLocalReply(statusCode int32, body string) {}

func addHeader(key string, value string) {}

func getCurrentTime() string {
    return "2026-06-18T00:00:00Z"
}

func main() {}

レート制限プラグインの例:

package main

import "unsafe"

var requestCount int32 = 0
var maxRequests int32 = 1000

//export onHttpRequest
func onHttpRequest() int32 {
    requestCount++
    if requestCount > maxRequests {
        sendLocalReply(429, "Too Many Requests")
        return 1
    }
    addHeader("X-RateLimit-Remaining", int32ToStr(maxRequests-requestCount))
    return 0
}

func int32ToStr(n int32) string {
    if n == 0 {
        return "0"
    }
    var buf [12]byte
    pos := len(buf)
    for n > 0 {
        pos--
        buf[pos] = byte('0' + n%10)
        n /= 10
    }
    return string(buf[pos:])
}

func sendLocalReply(code int32, body string) {}
func addHeader(key, val string) {}

func main() {}

ビルドコマンド:

tinygo build -o auth-plugin.wasm -target=wasi -no-debug -scheduler=none .
tinygo build -o ratelimit-plugin.wasm -target=wasi -no-debug -scheduler=none .

設計4:Envoy WASM Filter統合

Proxy-WASM SDKを通じてEnvoy互換のWASM Filterを開発し、Envoy/Istioサービスメッシュとのシームレスな統合を実現する。

package main

import (
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)

func main() {
    proxywasm.SetVMContext(&vmContext{})
}

type vmContext struct {
    types.DefaultVMContext
}

func (v *vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    return &pluginContext{}
}

type pluginContext struct {
    types.DefaultPluginContext
    maxRequests int32
}

func (p *pluginContext) OnPluginStart(configurationSize int) types.OnPluginStartStatus {
    data, err := proxywasm.GetPluginConfiguration(configurationSize)
    if err != nil && err != types.ErrorStatusNotFound {
        proxywasm.LogCriticalf("error reading config: %v", err)
        return types.OnPluginStartStatusFailed
    }
    if len(data) > 0 {
        p.maxRequests = parseInt32(data)
    }
    return types.OnPluginStartStatusOK
}

func (p *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    return &httpContext{maxRequests: p.maxRequests}
}

type httpContext struct {
    types.DefaultHttpContext
    maxRequests  int32
    requestCount int32
}

func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action {
    ctx.requestCount++
    if ctx.requestCount > ctx.maxRequests {
        proxywasm.SendLocalReply(429, "rate limited", nil, 0, "")
        return types.ActionPause
    }

    token, err := proxywasm.GetHttpRequestHeader("authorization")
    if err != nil || token == "" {
        proxywasm.SendLocalReply(401, "unauthorized", nil, 0, "")
        return types.ActionPause
    }

    proxywasm.AddHttpRequestHeader("x-wasm-filter", "envoy-auth-v1")
    return types.ActionContinue
}

func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    proxywasm.AddHttpResponseHeader("x-wasm-processed", "true")
    return types.ActionContinue
}

func parseInt32(data []byte) int32 {
    var result int32
    for _, b := range data {
        if b >= '0' && b <= '9' {
            result = result*10 + int32(b-'0')
        }
    }
    return result
}

Envoy設定統合:

name: envoy.filters.http.wasm
typed_config:
  "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
  config:
    name: auth-filter
    root_id: auth-filter
    vm_config:
      vm_id: auth-filter-vm
      runtime: envoy.wasm.runtime.v8
      code:
        local:
          filename: /etc/envoy/auth-filter.wasm
    configuration:
      "@type": type.googleapis.com/google.protobuf.StringValue
      value: "1000"

設計5:エッジデプロイとホットリロード

プラグインのリモートロードとバージョン管理を実装し、S3/OSSからのWASMモジュール取得をサポート、ゼロダウンタイムでのバージョンロールバックを実現する。

package gateway

import (
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
    "sync"
    "sync/atomic"
    "time"
)

type PluginVersion struct {
    Name     string `json:"name"`
    Version  string `json:"version"`
    SHA256   string `json:"sha256"`
    WasmURL  string `json:"wasmUrl"`
    LoadedAt int64  `json:"loadedAt"`
}

type PluginRegistry struct {
    runtime    *WASMRuntime
    versions   map[string]*PluginVersion
    current    sync.Map
    mu         sync.RWMutex
    httpClient *http.Client
}

func NewPluginRegistry(rt *WASMRuntime) *PluginRegistry {
    return &PluginRegistry{
        runtime:  rt,
        versions: make(map[string]*PluginVersion),
        httpClient: &http.Client{
            Timeout: 30 * time.Second,
        },
    }
}

func (pr *PluginRegistry) LoadFromRemote(name string, version PluginVersion) error {
    resp, err := pr.httpClient.Get(version.WasmURL)
    if err != nil {
        return fmt.Errorf("fetch wasm from %s: %w", version.WasmURL, err)
    }
    defer resp.Body.Close()

    var buf bytes.Buffer
    if _, err := io.Copy(&buf, resp.Body); err != nil {
        return fmt.Errorf("read wasm body: %w", err)
    }

    wasmBytes := buf.Bytes()
    hash := sha256.Sum256(wasmBytes)
    checksum := hex.EncodeToString(hash[:])
    if checksum != version.SHA256 {
        return fmt.Errorf("checksum mismatch: expected %s, got %s", version.SHA256, checksum)
    }

    plugin, err := pr.runtime.LoadPlugin(name, wasmBytes)
    if err != nil {
        return fmt.Errorf("load plugin %s: %w", name, err)
    }

    pr.mu.Lock()
    old, exists := pr.versions[name]
    pr.versions[name] = &version
    pr.mu.Unlock()

    pr.current.Store(name, plugin)

    if exists {
        fmt.Printf("plugin %s upgraded: %s -> %s\n", name, old.Version, version.Version)
    } else {
        fmt.Printf("plugin %s loaded: v%s\n", name, version.Version)
    }

    return nil
}

type PluginChainManager struct {
    registry *PluginRegistry
    chains   map[string][]string
    mu       sync.RWMutex
}

func NewPluginChainManager(rt *WASMRuntime, configs []PluginConfig) (*PluginChainManager, error) {
    registry := NewPluginRegistry(rt)
    pcm := &PluginChainManager{
        registry: registry,
        chains:   make(map[string][]string),
    }

    for _, cfg := range configs {
        version := PluginVersion{
            Name:    cfg.Name,
            Version: "1.0.0",
            WasmURL: cfg.WasmURL,
        }
        if err := registry.LoadFromRemote(cfg.Name, version); err != nil {
            return nil, fmt.Errorf("load plugin %s: %w", cfg.Name, err)
        }
    }

    return pcm, nil
}

func (pcm *PluginChainManager) ExecuteChain(chainName string, req *http.Request) (*http.Response, bool) {
    pcm.mu.RLock()
    chain, exists := pcm.chains[chainName]
    pcm.mu.RUnlock()

    if !exists {
        return nil, true
    }

    for _, pluginName := range chain {
        plugin, ok := pcm.registry.current.Load(pluginName)
        if !ok {
            continue
        }
        wasmPlugin := plugin.(*WASMPlugin)
        resp, allowed := wasmPlugin.ExecuteRequestFilter(req)
        if !allowed {
            return resp, false
        }
    }

    return nil, true
}

ゲートウェイルーティング + プラグインチェーン完全例:

package main

import (
    "log"
    "net/http"

    "github.com/yourorg/edge-gateway/gateway"
)

func main() {
    cfg := gateway.GatewayConfig{
        ListenAddr: ":8080",
        Routes: []gateway.RouteConfig{
            {
                Path:        "/api/v1/users",
                Methods:     []string{"GET", "POST"},
                PluginChain: []string{"auth-plugin", "ratelimit-plugin"},
                Upstream:    "user-service",
            },
            {
                Path:        "/api/v1/orders",
                Methods:     []string{"GET", "POST", "PUT"},
                PluginChain: []string{"auth-plugin", "ratelimit-plugin", "cors-plugin"},
                Upstream:    "order-service",
            },
        },
        PluginChain: []gateway.PluginConfig{
            {Name: "auth-plugin", WasmURL: "https://cdn.example.com/plugins/auth-v1.2.0.wasm"},
            {Name: "ratelimit-plugin", WasmURL: "https://cdn.example.com/plugins/ratelimit-v1.1.0.wasm"},
            {Name: "cors-plugin", WasmURL: "https://cdn.example.com/plugins/cors-v1.0.0.wasm"},
        },
        UpstreamPool: map[string]string{
            "user-service":  "http://127.0.0.1:8081",
            "order-service": "http://127.0.0.1:8082",
        },
    }

    gw, err := gateway.NewEdgeGateway(cfg)
    if err != nil {
        log.Fatalf("create gateway: %v", err)
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/", gw.HandleRequest)

    log.Printf("WASM Edge Gateway listening on %s", cfg.ListenAddr)
    log.Fatal(http.ListenAndServe(cfg.ListenAddr, mux))
}

よくある落とし穴:5つの共通トラップ

落とし穴1:WASMプラグインがHTTP接続を直接操作

// ❌ 間違い:WASMサンドボックス内でTCP接続を確立できない
conn, _ := net.Dial("tcp", "backend:8080")

// ✅ 正しい:ホスト提供のHost Functionを通じてネットワークリクエストをプロキシ
results, _ := hostNetDial.Call(ctx, uint64(offset), uint64(len(host)), uint64(port))

落とし穴2:プラグイン間の状態共有によるデータ競合

// ❌ 間違い:複数のWASMプラグインインスタンスがグローバル変数を共有
var tokenCache = make(map[string]bool)

// ✅ 正しい:各プラグインインスタンスは独立したメモリを使用、状態はホスト経由で共有
// ホスト側でKV Store Host Functionを提供

落とし穴3:WASMプラグイン実行タイムアウトの無視

// ❌ 間違い:プラグインの戻りを無限に待機、悪意のある/無限ループプラグインがリクエスト全体をブロック
results, err := fn.Call(ctx)

// ✅ 正しい:context.WithTimeoutを使用して実行時間を制御
timeoutCtx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
defer cancel()
results, err := fn.Call(timeoutCtx)

落とし穴4:ホットリロード時の実行中リクエストの未処理

// ❌ 間違い:古いモジュールを直接アンロード、実行中のリクエストが解放済みメモリにアクセス
oldModule.Close(ctx)

// ✅ 正しい:参照カウント + グレースフルウェイト、全リクエスト完了後にアンロード
atomic.AddInt32(&refCount, 1)
defer atomic.AddInt32(&refCount, -1)
// ホットリロード時、refCountが0になるまで待機してからアンロード

落とし穴5:TinyGoビルドでWASIターゲットを未指定

# ❌ 間違い:-wasmターゲットを使用するとWASIインポートが欠落
tinygo build -o plugin.wasm -target=wasm .

# ✅ 正しい:wasiターゲットを使用して標準システムコールをサポート
tinygo build -o plugin.wasm -target=wasi -no-debug -scheduler=none .

エラートラブルシューティング:10の共通エラー

# エラーメッセージ 原因 解決方法
1 out of bounds memory access ホストがWASMメモリオフセットの境界外に読み書き allocate戻り値とデータ長計算を確認
2 function export not found: onHttpRequest WASMモジュールがライフサイクルフックをエクスポートしていない TinyGoコンパイル時に//export onHttpRequestをエクスポート
3 import not found: wasi_snapshot_preview1 WASIモジュールが初期化されていない wasi_snapshot_preview1.MustInstantiateを呼び出し
4 instantiation error: memory size exceeds limit プラグインの初期メモリが制限を超過 WithMemoryLimitPagesを増やすかメモリ使用量を最適化
5 context deadline exceeded in plugin execution プラグイン実行がタイムアウト context.WithTimeoutを使用しプラグインロジックを確認
6 checksum mismatch リモートロードしたWASMファイルの検証失敗 ファイルの完全性を確認、WASMモジュールを再アップロード
7 plugin version conflict 同名プラグインのバージョン競合 セマンティックバージョニングを使用し一意性を確保
8 goroutine stack overflow in wasm TinyGoデフォルトスケジューラによるスタックオーバーフロー コンパイル時に-scheduler=noneを追加
9 wasm validation error: invalid section WASMバイナリファイルが破損 WASMモジュールを再コンパイル、ビルドコマンドを確認
10 hot reload: old module still referenced ホットリロード時に古いモジュールの参照が未解放 リクエスト参照カウントがゼロになるまでアンロードを待機

高度な最適化テクニック

1. WASMプラグイン事前コンパイルキャッシュ

type CompiledCache struct {
    cache map[string]wazero.CompiledModule
    mu    sync.RWMutex
}

func (cc *CompiledCache) GetOrCompile(ctx context.Context, rt wazero.Runtime, key string, wasmBytes []byte) (wazero.CompiledModule, error) {
    cc.mu.RLock()
    if compiled, ok := cc.cache[key]; ok {
        cc.mu.RUnlock()
        return compiled, nil
    }
    cc.mu.RUnlock()

    compiled, err := rt.CompileModule(ctx, wasmBytes)
    if err != nil {
        return nil, err
    }

    cc.mu.Lock()
    cc.cache[key] = compiled
    cc.mu.Unlock()

    return compiled, nil
}

2. プラグインチェーン並列実行

func (pcm *PluginChainManager) ExecuteChainParallel(chainName string, req *http.Request) (*http.Response, bool) {
    pcm.mu.RLock()
    chain := pcm.chains[chainName]
    pcm.mu.RUnlock()

    type result struct {
        resp    *http.Response
        allowed bool
    }
    ch := make(chan result, len(chain))

    for _, pluginName := range chain {
        go func(name string) {
            plugin, _ := pcm.registry.current.Load(name)
            if plugin == nil {
                ch <- result{nil, true}
                return
            }
            resp, allowed := plugin.(*WASMPlugin).ExecuteRequestFilter(req)
            ch <- result{resp, allowed}
        }(pluginName)
    }

    for i := 0; i < len(chain); i++ {
        r := <-ch
        if !r.allowed {
            return r.resp, false
        }
    }
    return nil, true
}

3. プラグインメトリクス収集

type PluginMetrics struct {
    Name        string
    ExecCount   atomic.Int64
    ErrorCount  atomic.Int64
    TotalTimeNs atomic.Int64
}

func (m *PluginMetrics) Record(execTime time.Duration, hasError bool) {
    m.ExecCount.Add(1)
    m.TotalTimeNs.Add(execTime.Nanoseconds())
    if hasError {
        m.ErrorCount.Add(1)
    }
}

func (m *PluginMetrics) AvgTime() time.Duration {
    count := m.ExecCount.Load()
    if count == 0 {
        return 0
    }
    return time.Duration(m.TotalTimeNs.Load() / count)
}

4. プラグイン設定ホットインジェクション

func (wr *WASMRuntime) InjectConfig(pluginName string, configJSON []byte) error {
    plugin, ok := wr.plugins[pluginName]
    if !ok {
        return fmt.Errorf("plugin %s not found", pluginName)
    }

    fn := plugin.module.ExportedFunction("onConfigUpdate")
    if fn == nil {
        return nil
    }

    offset, err := writeString(plugin.module, string(configJSON))
    if err != nil {
        return err
    }

    _, err = fn.Call(wr.ctx, uint64(offset), uint64(len(configJSON)))
    return err
}

5. エッジノードプラグイン配信

type EdgeDistributor struct {
    registry   *PluginRegistry
    nodes      []string
    httpClient *http.Client
}

func (ed *EdgeDistributor) PushToEdge(pluginName string, version PluginVersion) error {
    for _, node := range ed.nodes {
        url := fmt.Sprintf("http://%s/api/v1/plugins/load", node)
        payload, _ := json.Marshal(map[string]interface{}{
            "name":    pluginName,
            "version": version.Version,
            "wasmUrl": version.WasmURL,
            "sha256":  version.SHA256,
        })

        resp, err := ed.httpClient.Post(url, "application/json", bytes.NewReader(payload))
        if err != nil {
            fmt.Printf("push to %s failed: %v\n", node, err)
            continue
        }
        resp.Body.Close()
        fmt.Printf("pushed %s v%s to %s\n", pluginName, version.Version, node)
    }
    return nil
}

比較分析:WASMゲートウェイ vs Kong vs APISIX vs Envoyネイティブ

次元 WASMゲートウェイ(Go) Kong APISIX Envoyネイティブ
プラグイン言語 Go/TinyGo/Rust/AS Lua Lua/Go(外部) C++
ホットリロード WASMモジュールホットロード 再起動/DB同期が必要 etcd watch バイナリ再コンパイルが必要
セキュリティ隔離 WASMサンドボックス強隔離 Lua共有プロセスメモリ Lua共有プロセスメモリ プロセスレベル隔離
プラグインサイズ 100KB-2MB Luaスクリプト<50KB Luaスクリプト<50KB バイナリ全体の再コンパイルが必要
実行パフォーマンス ネイティブに近い(90-95%) LuaJIT高性能 LuaJIT高性能 ネイティブC++最速
マルチゲートウェイ再利用 WASMモジュール汎用 Kongのみ APISIXのみ Envoyのみ
学習曲線 WASM+Goの知識が必要 Luaはシンプル Luaはシンプル C++のハードルが高い
エコシステム成熟度 急成長(2026) 成熟(500+プラグイン) 成熟(100+プラグイン) 成熟(Istioエコシステム)

オンラインツール推薦

WASMエッジゲートウェイの開発時に、以下のオンラインツールが生産性を大幅に向上させる:

  1. JSONフォーマッター — ゲートウェイプラグイン設定やルーティングルールJSONのデバッグ時に素早くフォーマットと検証
  2. ハッシュ計算ツール — WASMモジュールのSHA256チェックサムを計算し、リモートロード時のプラグイン整合性を検証
  3. Curl→コード変換ツール — curlリクエストをGo HTTPコードに変換し、ゲートウェイテストケースを素早く作成

まとめと展望

WASMエッジゲートウェイはKongやEnvoyを置き換えるものではなく、それらの上に言語非依存、セキュア隔離、ホットリロード可能なプラグイン抽象化レイヤーを提供する。2026年のトレンドは:Envoyがデータプレーンプロキシ、WASM Filterがポータブルなプラグインレイヤー、Goホストが軽量ゲートウェイオーケストレーター。この3つの組み合わせで、Envoyの本番レベルの安定性 + WASMのプラグイン柔軟性 + Goの開発効率が得られる。次のステップとして、WASM Component ModelとWASI Preview 3の標準化進展に注目 — これらがクロス言語プラグインの相互運用性問題を完全に解決する。


さらなる学習

  1. Proxy-WASM ABI Specification — Envoy WASM Filterインターフェース仕様
  2. Wazero Runtime Documentation — 純粋なGo WASMランタイム公式ドキュメント
  3. Envoy WASM Filter Design — Envoy WASMフィルター設計ドキュメント
  4. WebAssembly Component Model — WASMコンポーネントモデル提案
  5. TinyGo WASI Target Guide — TinyGo WASIコンパイルガイド

ブラウザローカルツールを無料で試す →

#WASM边缘网关#Go WASM网关#WASI代理#边缘API网关#WASM插件网关#2026#Envoy WASM Filter