Skip to content
Published on

KubernetesでWebAssemblyワークロードを実行する:SpinKube・containerd Wasm Shim・runwasi 完全ガイド

Authors
  • Name
    Twitter

Kubernetes WebAssembly

はじめに

Solomon Hykes(Docker創設者)は2019年にこう語りました。「もし2008年にWASM+WASIが存在していたなら、Dockerを作る必要はなかっただろう。」この発言は誇張ではなく、WebAssemblyが持つ潜在力を正確に言い当てた洞察です。

コンテナ技術はこの10年間でソフトウェアデプロイの標準となりました。しかしコンテナは本質的にLinuxカーネルのnamespaceとcgroupの上で動作する隔離されたプロセスであり、OS全体のユーザー空間(userland)をイメージに含める必要があります。最小限のAlpineベースイメージでも数十MBに達し、Cold Startには数百msから数秒かかります。

WebAssembly(Wasm) は根本的に異なるアプローチを提示します。Wasmモジュールは数KB〜数MBのサイズで、サンドボックス環境において1ms未満のCold Startが可能です。OSに依存しない移植性、capability-basedセキュリティモデル、そしてネイティブに近い実行速度を兼ね備えています。

2024年からCNCFエコシステムにおいてWasmワークロードをKubernetes上で実行する動きが本格化しました。SpinKubecontainerd Wasm shimrunwasi などのプロジェクトが成熟し、WasmワークロードをPodのように管理できる時代が開かれつつあります。本記事では、Wasmのコア概念からKubernetes統合アーキテクチャ、SpinKube詳細分析、実践デプロイ、パフォーマンスベンチマークまで包括的に解説します。

WebAssemblyコア概念

Wasmバイナリフォーマット

WebAssemblyは元々ブラウザでC/C++/Rustコードをネイティブに近い速度で実行するために設計されたバイナリフォーマットです。核となる特性は以下の通りです。

  • スタックベースの仮想マシン: レジスタではなくスタックベースの命令セットを使用
  • 型安全: すべての関数シグネチャとメモリアクセスが型検査を通過して初めて実行
  • 線形メモリ: 境界チェックが保証された連続メモリ空間でのみデータアクセス可能
  • 決定的実行: 同一の入力に対して常に同一の結果を保証(浮動小数点の一部例外あり)
// Rustで記述した簡単なWasmモジュールの例
// Cargo.tomlに [lib] crate-type = ["cdylib"] の設定が必要

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[no_mangle]
pub extern "C" fn fibonacci(n: i32) -> i64 {
    if n <= 1 {
        return n as i64;
    }
    let mut a: i64 = 0;
    let mut b: i64 = 1;
    for _ in 2..=n {
        let temp = b;
        b = a + b;
        a = temp;
    }
    b
}

ビルド後に生成される .wasm ファイルは数KB程度のサイズで、すべてのWasmランタイム(Wasmtime、Wasmer、WasmEdgeなど)で実行できます。

# Wasmターゲットでビルド
rustup target add wasm32-wasi
cargo build --target wasm32-wasi --release

# ビルド成果物のサイズ確認(通常は数KB〜数MB)
ls -lh target/wasm32-wasi/release/*.wasm

# Wasmtimeで直接実行
wasmtime target/wasm32-wasi/release/my_module.wasm

WASI (WebAssembly System Interface)

ブラウザ外でWasmを実行するには、ファイルシステム、ネットワーク、環境変数などのOSリソースへのアクセスが必要です。WASIはこれを実現するための標準システムインターフェースです。

WASIの核となる設計原則はCapability-Based Securityです。Wasmモジュールはデフォルトでは何もできず、ホストが明示的に付与した権限(capability)のみ使用できます。

# WASIでファイルシステムへのアクセス権限を付与する例
# --dirフラグで特定のディレクトリのみアクセスを許可
wasmtime --dir=/tmp/data::./data my_app.wasm

# ネットワークアクセスが必要な場合
wasmtime --tcplisten=0.0.0.0:8080 my_server.wasm

WASIは現在、2つの主要バージョンが共存しています。

特性WASI Preview 1WASI Preview 2 (0.2.x)
インターフェース定義witx(テキスト形式)WIT(Wasm Interface Type)
モジュールモデルCore ModuleComponent Model
HTTPサポートなしwasi:http/proxy
ソケットサポート限定的wasi:sockets
コンポーネント結合不可WITを通じた結合が可能

Component Model

Component ModelはWasmエコシステムの核となる発展方向です。従来のCore Wasmは数値型(i32、i64、f32、f64)のみを関数引数として交換でき、文字列や構造体のような高レベル型を渡すには複雑なバインディングコードが必要でした。

Component ModelはWIT(Wasm Interface Type) というインターフェース定義言語を通じてこの問題を解決します。

// WITインターフェース定義の例
package example:http-handler@1.0.0;

interface types {
    record http-request {
        method: string,
        uri: string,
        headers: list<tuple<string, string>>,
        body: option<list<u8>>,
    }

    record http-response {
        status: u16,
        headers: list<tuple<string, string>>,
        body: option<list<u8>>,
    }
}

world http-handler {
    import wasi:logging/logging;
    export handle-request: func(req: types.http-request) -> types.http-response;
}

このWIT定義を使用すれば、Rustで記述したHTTPハンドラをPythonで記述したミドルウェアと結合することが可能になります。各コンポーネントは独立してコンパイルされ、WITがインターフェース契約の役割を果たします。

KubernetesにおけるWasm実行アーキテクチャ

既存のコンテナランタイムスタック

Kubernetesのコンテナ実行フローをまず理解する必要があります。

kubelet → CRI → containerd → OCI Runtime (runc)Linux Container
  • kubelet: Pod specを受け取りコンテナ作成をリクエスト
  • CRI (Container Runtime Interface): kubeletとコンテナランタイム間の標準インターフェース
  • containerd: イメージpull、コンテナライフサイクル管理
  • runc: 実際のLinuxコンテナ(namespace + cgroup)を作成するOCIランタイム

containerd Wasm Shim

Wasmワークロードを実行するためにruncの位置にWasmランタイムを差し込むのがcontainerd Wasm shimの核となるアイデアです。

kubelet → CRI → containerd → containerd-shim-spin-v2 → Wasmtime/SpinWasm Module
                           → containerd-shim-slight-v2 → Wasmtime/SpiderLightningWasm Module
                           → containerd-shim-wasmedge-v1 → WasmEdgeWasm Module

containerdのshimアーキテクチャは、元々様々なOCIランタイムをプラグインのように差し替えられるよう設計されていました。Wasm shimはこの拡張ポイントを活用して、OCIイメージ内にWasmモジュールをパッケージングし、containerdを通じて実行することを可能にします。

runwasi

runwasiはBytecode Allianceが開発するプロジェクトで、containerd shimとWasmランタイムの間の抽象化レイヤーを提供します。

// runwasiのコアトレイト構造(簡略化)
pub trait Engine {
    fn name() -> &'static str;
    fn run_wasi(&self, ctx: &WasiCtx, module: &[u8]) -> Result<i32>;
}

// Wasmtimeエンジンの実装
pub struct WasmtimeEngine;
impl Engine for WasmtimeEngine {
    fn name() -> &'static str { "wasmtime" }
    fn run_wasi(&self, ctx: &WasiCtx, module: &[u8]) -> Result<i32> {
        // Wasmtimeを使用してWasmモジュールを実行
        let engine = wasmtime::Engine::default();
        let module = wasmtime::Module::new(&engine, module)?;
        // ...
    }
}

runwasiを通じて、様々なWasmランタイム(Wasmtime、WasmEdge、Wasmer)をcontainerd shimとして公開できます。

RuntimeClassによるWasmワークロードのスケジューリング

KubernetesはRuntimeClassリソースを通じて、Podが使用するランタイムを指定できます。Wasmワークロードは専用のRuntimeClassを定義して、Wasm shimがインストールされたノードでのみ実行されるようスケジューリングします。

# RuntimeClass定義
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: wasmtime-spin-v2
handler: spin
scheduling:
  nodeSelector:
    kubernetes.io/arch: wasm32-wasi
---
# WasmワークロードPod
apiVersion: v1
kind: Pod
metadata:
  name: wasm-hello
spec:
  runtimeClassName: wasmtime-spin-v2
  containers:
    - name: hello-wasm
      image: ghcr.io/example/hello-wasm:v1
      command: ['/hello.wasm']
  nodeSelector:
    kubernetes.io/arch: wasm32-wasi

SpinKube詳細分析

SpinKubeとは何か

SpinKubeはFermyonが主導するオープンソースプロジェクトで、Kubernetes上でFermyon Spinアプリケーションをネイティブに実行するための統合フレームワークです。CNCF Sandboxプロジェクトとして採択され、コミュニティベースのガバナンスの下で開発されています。

SpinKubeは3つのコアコンポーネントで構成されています。

  1. Spin Operator: Kubernetesカスタムコントローラで、SpinApp CRDを監視・管理
  2. SpinApp CRD: Spinアプリケーションを宣言的に定義するカスタムリソース
  3. containerd-shim-spin: Spinランタイムをcontainerdのshimとしてラップした実行エンジン

アーキテクチャ詳細

┌─────────────────────────────────────────────────────┐
Kubernetes Cluster│                                                       │
│  ┌──────────────┐       ┌────────────────────────┐  │
│  │ Spin Operator│API Server           │  │
│  │              │◄──────│                         │  │
│  │ - SpinApp    │       │ SpinApp CRD registered  │  │
│  │   Controller │       └────────────────────────┘  │
│  │ - Executor   │                                    │
│  │   Selection  │                                    │
│  └──────┬───────┘                                    │
│         │ Creates/Manages│         ▼                                            │
│  ┌──────────────┐       ┌────────────────────────┐  │
│  │  Deployment  │       │   Node (Wasm-capable)   │  │
│  │  or          │──────►│                         │  │
│  │  SpinAppExec │       │  containerd             │  │
│  └──────────────┘       │    └─ shim-spin-v2      │  │
│                         │        └─ Spin Runtime  │  │
│                         │            └─ .wasm     │  │
│                         └────────────────────────┘  │
└─────────────────────────────────────────────────────┘

SpinApp CRDの構造

SpinApp CRDは、Spinアプリケーションのデプロイを宣言的に定義します。

apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: hello-spin
  namespace: default
spec:
  image: ghcr.io/example/hello-spin:v1
  replicas: 3
  executor: containerd-shim-spin
  enableAutoscaling: true
  resources:
    limits:
      cpu: 100m
      memory: 128Mi
  variables:
    - name: DATABASE_URL
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: url
  runtime-config:
    key_value_stores:
      default:
        type: redis
        url: redis://redis-cluster:6379
    sqlite_databases:
      default:
        type: libsql
        url: https://my-turso-db.turso.io

Spin Operatorの動作原理

Spin Operatorは標準的なKubernetes Operatorパターンに従います。SpinAppリソースが作成されると、以下のようなリコンサイル(reconcile)ループを実行します。

  1. SpinApp検知: Watchを通じてSpinAppリソースの作成/変更/削除を検知
  2. 実行戦略決定: spec.executor フィールドに基づきcontainerd-shim-spinまたはSpinKubeカスタムexecutorを選択
  3. Deployment作成: RuntimeClassが設定されたDeploymentを作成
  4. Service作成: HTTPトリガーがある場合、Kubernetes Serviceを自動作成
  5. オートスケーリング設定: enableAutoscaling: true の場合、HPA(Horizontal Pod Autoscaler)またはKEDA ScaledObjectを作成
  6. ステータス更新: SpinAppのstatusフィールドに現在の状態を記録

実践デプロイガイド

ステップ1: containerd Wasm Shimのインストール

クラスタノードにcontainerd Wasm shimをインストールします。k3dを使用したローカル環境を基準に説明します。

# k3dクラスタ作成(Wasm shimが含まれたイメージを使用)
k3d cluster create wasm-cluster \
  --image ghcr.io/spinkube/containerd-shim-spin/k3d:v0.17.0 \
  --port "8081:80@loadbalancer" \
  --agents 2

# クラスタ状態確認
kubectl get nodes
kubectl get runtimeclass

プロダクション環境では、DaemonSetやNode Feature Discoveryを活用してWasm shimをインストールします。

# Node Feature DiscoveryによるWasm対応ノードのラベリング
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: wasm-shim-installer
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: wasm-shim-installer
  template:
    metadata:
      labels:
        app: wasm-shim-installer
    spec:
      hostPID: true
      containers:
        - name: installer
          image: ghcr.io/spinkube/containerd-shim-spin/node-installer:v0.17.0
          securityContext:
            privileged: true
          volumeMounts:
            - name: containerd-config
              mountPath: /etc/containerd
            - name: shim-binary
              mountPath: /opt/kwasm/bin
      volumes:
        - name: containerd-config
          hostPath:
            path: /etc/containerd
        - name: shim-binary
          hostPath:
            path: /opt/kwasm/bin

ステップ2: SpinKubeのインストール

# cert-managerのインストール(Spin OperatorのWebhookに必要)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.0/cert-manager.yaml

# SpinKube CRDsのインストール
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.crds.yaml

# RuntimeClassの設定
kubectl apply -f https://github.com/spinkube/spin-operator/releases/download/v0.4.0/spin-operator.runtime-class.yaml

# Spin OperatorをHelmでインストール
helm install spin-operator \
  --namespace spin-operator \
  --create-namespace \
  --version 0.4.0 \
  oci://ghcr.io/spinkube/charts/spin-operator

# SpinAppExecutorの作成
kubectl apply -f - <<EOF
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinAppExecutor
metadata:
  name: containerd-shim-spin
spec:
  createDeployment: true
  deploymentConfig:
    runtimeClassName: wasmtime-spin-v2
EOF

ステップ3: Spinアプリケーションの作成とデプロイ

# Spin CLIで新規プロジェクト作成
spin new -t http-rust hello-k8s --accept-defaults
cd hello-k8s
// src/lib.rs - Spin HTTPハンドラ
use spin_sdk::http::{IntoResponse, Request, Response};
use spin_sdk::http_component;

#[http_component]
fn handle_request(req: Request) -> anyhow::Result<impl IntoResponse> {
    let path = req.path();
    let method = req.method().to_string();

    println!("Received {method} request for {path}");

    let body = serde_json::json!({
        "message": "Hello from WebAssembly on Kubernetes!",
        "path": path,
        "method": method,
        "runtime": "SpinKube",
        "timestamp": chrono::Utc::now().to_rfc3339()
    });

    Ok(Response::builder()
        .status(200)
        .header("content-type", "application/json")
        .body(body.to_string())
        .build())
}
# ビルドしてOCIレジストリにプッシュ
spin build
spin registry push ghcr.io/myorg/hello-k8s:v1

# SpinAppとしてデプロイ
kubectl apply -f - <<EOF
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: hello-k8s
spec:
  image: ghcr.io/myorg/hello-k8s:v1
  replicas: 3
  executor: containerd-shim-spin
EOF

# デプロイ状態確認
kubectl get spinapp hello-k8s
kubectl get pods -l core.spinoperator.dev/app-name=hello-k8s

パフォーマンスベンチマーク

Cold Start比較

Cold StartはWasmがコンテナ比で最大の優位性を示す領域です。様々なワークロードに対する測定結果をまとめます。

ランタイムワークロードCold Start (p50)Cold Start (p99)イメージサイズ
Docker (Alpine+Go)HTTP Server450ms1,200ms12MB
Docker (Distroless+Go)HTTP Server380ms980ms8MB
gVisorHTTP Server520ms1,500ms12MB
Spin (Wasm)HTTP Handler1.2ms3.8ms680KB
WasmEdgeHTTP Handler2.1ms5.2ms720KB
Wasmtime (standalone)HTTP Handler0.8ms2.5ms650KB

WasmはCold Startにおいてコンテナ比で100〜300倍高速な起動時間を示します。これはWasmモジュールがOSの起動プロセスなしに即座に実行可能であるためです。

メモリフットプリント

# 100個の同時インスタンス基準のメモリ使用量比較(wrkベンチマーク)
# Docker Container: ~6.4GB (64MB per instance avg)
# Spin Wasm:        ~320MB (3.2MB per instance avg)
# メモリ効率: Wasmが約20倍少ないメモリ使用
メトリクスDocker Container (100個)Spin Wasm (100個)比率
合計メモリ6.4GB320MB20x
インスタンスあたりメモリ64MB3.2MB20x
インスタンスあたり起動時間450ms1.2ms375x
イメージサイズ12MB680KB18x

スループット(Throughput)ベンチマーク

JSONシリアライズ/デシリアライズHTTPエンドポイント基準で wrk を使用したベンチマーク結果です。

# ベンチマークコマンド
wrk -t12 -c400 -d30s http://localhost:8080/api/json

# Docker Container (Go HTTP Server)
# Requests/sec: 45,230
# Avg Latency: 8.8ms
# Transfer/sec: 12.3MB

# Spin Wasm (Rust HTTP Handler)
# Requests/sec: 38,750
# Avg Latency: 10.3ms
# Transfer/sec: 10.5MB

定常状態(warm)のスループットではコンテナが約15〜20%の優位性を示します。これはWasmランタイムのABI境界オーバーヘッドによるものです。しかしスケールアウトシナリオでは、Wasmの高速なCold Startが全体のスループットを逆転させます。

プロダクション適用時の考慮事項

現在の制限事項

Wasm on Kubernetesはまだ成熟段階には達していません。プロダクション適用前に必ず認識すべき制限事項をまとめます。

ネットワーキングの制限

# Wasm Podは現在、HostPort、NodePortバインディングに制限があります
# Service mesh(Istio、Linkerd)のサイドカーパターンが動作しません
# → Spinの内蔵HTTPトリガーを通じて回避可能

# 現在の推奨構成
apiVersion: v1
kind: Service
metadata:
  name: wasm-service
spec:
  type: ClusterIP # NodePort/LoadBalancerも可能だが制限の確認が必要
  selector:
    core.spinoperator.dev/app-name: hello-k8s
  ports:
    - port: 80
      targetPort: 80

ストレージの制限

  • PersistentVolumeマウントがサポートされていないか制限的
  • Spinの内蔵Key-Value Store(Redis、SQLite)を通じた状態保存を推奨
  • ファイルシステムアクセスはWASI capabilityにより制限

デバッグツール

  • kubectl exec によるシェルアクセス不可(OSがないため)
  • kubectl logs は正常に動作
  • プロファイリングツールが限定的(WASI可観測性インターフェースが開発中)
# Wasmワークロードのデバッグ基本コマンド
kubectl logs -l core.spinoperator.dev/app-name=hello-k8s -f
kubectl describe spinapp hello-k8s
kubectl get events --field-selector involvedObject.name=hello-k8s

コンテナとWasmの共存戦略

現実的にすべてのワークロードをWasmにマイグレーションすることは不可能であり、望ましくもありません。推奨される共存戦略は以下の通りです。

ワークロードタイプ推奨ランタイム理由
HTTP API (stateless)Wasm (Spin)超高速Cold Start、高い密度
イベントハンドラWasm (Spin)高速なスケールアウト/イン
データベースContainerファイルシステム/ネットワーク要件
ML推論Container (GPU)GPUアクセスが必要
レガシーアプリContainerマイグレーションコスト対比の利点不足
Edge/IoTWasm極小バイナリ、移植性

Wasmと既存ワークロードの混合デプロイクラスタ構成

# Node Pool分離戦略
# Pool 1: 一般コンテナワークロード
# Pool 2: Wasm Shimがインストールされたwasm専用ノード

# WasmノードにTaintを追加
kubectl taint nodes wasm-node-1 workload-type=wasm:NoSchedule

# SpinAppにTolerationを追加
apiVersion: core.spinoperator.dev/v1alpha1
kind: SpinApp
metadata:
  name: edge-handler
spec:
  image: ghcr.io/myorg/edge-handler:v1
  replicas: 5
  executor: containerd-shim-spin
  deploymentAnnotations:
    app.kubernetes.io/part-of: edge-system
  podSpec:
    tolerations:
      - key: workload-type
        operator: Equal
        value: wasm
        effect: NoSchedule
    nodeSelector:
      kubernetes.io/arch: wasm32-wasi

参考資料

クイズ

Q1: 「KubernetesでWebAssemblyワークロードを実行する:SpinKube・containerd Wasm Shim・runwasi 完全ガイド」の主なトピックは何ですか?

Kubernetes上でWebAssemblyワークロードをネイティブに実行する方法を解説します。Wasmバイナリフォーマットとwasi基礎からcontainerd Wasm shim、runwasi、SpinKubeアーキテクチャ、Fermyon Spinデプロイ、コンテナ比較のパフォーマンスベンチマークまで実践ガイドを提供します。

Q2: WebAssemblyコア概念とは何ですか? Wasmバイナリフォーマット WebAssemblyは元々ブラウザでC/C++/Rustコードをネイティブに近い速度で実行するために設計されたバイナリフォーマットです。核となる特性は以下の通りです。 スタックベースの仮想マシン: レジスタではなくスタックベースの命令セットを使用 型安全: すべての関数シグネチャとメモリアクセスが型検査を通過して初めて実行 線形メモリ: 境界チェックが保証された連続メモリ空間でのみデータアクセス可能 決定的実行: 同一の入力に対して常に同一の結果を保証(浮動小数点の一部例外あり) ビルド後に生成される .wasm ファイル...

Q3: KubernetesにおけるWasm実行アーキテクチャについて説明してください。 既存のコンテナランタイムスタック Kubernetesのコンテナ実行フローをまず理解する必要があります。 kubelet: Pod specを受け取りコンテナ作成をリクエスト CRI (Container Runtime Interface): kubeletとコンテナランタイム間の標準インターフェース containerd: イメージpull、コンテナライフサイクル管理 runc: 実際のLinuxコンテナ(namespace + cgroup)を作成するOCIランタイム containerd Wasm Shim Wasmワークロードを実行するためにr...

Q4: SpinKube詳細分析の主な特徴は何ですか? SpinKubeとは何か SpinKubeはFermyonが主導するオープンソースプロジェクトで、Kubernetes上でFermyon Spinアプリケーションをネイティブに実行するための統合フレームワークです。CNCF Sandboxプロジェクトとして採択され、コミュニティベースのガバナンスの下で開発されています。 SpinKubeは3つのコアコンポーネントで構成されています。

Q5: 実践デプロイガイドはどのように機能しますか? ステップ1: containerd Wasm Shimのインストール クラスタノードにcontainerd Wasm shimをインストールします。k3dを使用したローカル環境を基準に説明します。 プロダクション環境では、DaemonSetやNode Feature Discoveryを活用してWasm shimをインストールします。 ステップ2: SpinKubeのインストール ステップ3: Spinアプリケーションの作成とデプロイ