- Published on
KubernetesでWebAssemblyワークロードを実行する:SpinKube・containerd Wasm Shim・runwasi 完全ガイド
- Authors
- Name
- はじめに
- WebAssemblyコア概念
- KubernetesにおけるWasm実行アーキテクチャ
- SpinKube詳細分析
- 実践デプロイガイド
- パフォーマンスベンチマーク
- プロダクション適用時の考慮事項
- 参考資料
- クイズ

はじめに
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上で実行する動きが本格化しました。SpinKube、containerd Wasm shim、runwasi などのプロジェクトが成熟し、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 1 | WASI Preview 2 (0.2.x) |
|---|---|---|
| インターフェース定義 | witx(テキスト形式) | WIT(Wasm Interface Type) |
| モジュールモデル | Core Module | Component 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/Spin → Wasm Module
→ containerd-shim-slight-v2 → Wasmtime/SpiderLightning → Wasm Module
→ containerd-shim-wasmedge-v1 → WasmEdge → Wasm 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つのコアコンポーネントで構成されています。
- Spin Operator: Kubernetesカスタムコントローラで、SpinApp CRDを監視・管理
- SpinApp CRD: Spinアプリケーションを宣言的に定義するカスタムリソース
- 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)ループを実行します。
- SpinApp検知: Watchを通じてSpinAppリソースの作成/変更/削除を検知
- 実行戦略決定:
spec.executorフィールドに基づきcontainerd-shim-spinまたはSpinKubeカスタムexecutorを選択 - Deployment作成: RuntimeClassが設定されたDeploymentを作成
- Service作成: HTTPトリガーがある場合、Kubernetes Serviceを自動作成
- オートスケーリング設定:
enableAutoscaling: trueの場合、HPA(Horizontal Pod Autoscaler)またはKEDA ScaledObjectを作成 - ステータス更新: 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 Server | 450ms | 1,200ms | 12MB |
| Docker (Distroless+Go) | HTTP Server | 380ms | 980ms | 8MB |
| gVisor | HTTP Server | 520ms | 1,500ms | 12MB |
| Spin (Wasm) | HTTP Handler | 1.2ms | 3.8ms | 680KB |
| WasmEdge | HTTP Handler | 2.1ms | 5.2ms | 720KB |
| Wasmtime (standalone) | HTTP Handler | 0.8ms | 2.5ms | 650KB |
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.4GB | 320MB | 20x |
| インスタンスあたりメモリ | 64MB | 3.2MB | 20x |
| インスタンスあたり起動時間 | 450ms | 1.2ms | 375x |
| イメージサイズ | 12MB | 680KB | 18x |
スループット(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/IoT | Wasm | 極小バイナリ、移植性 |
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
参考資料
- SpinKube公式ドキュメント
- Fermyon Spin公式ドキュメント
- containerd-wasm-shims GitHub
- WASI公式サイト
- Bytecode Alliance - Component Model
- Kubernetes RuntimeClassドキュメント
- WebAssembly Specification
- Solomon HykesのWASM関連ツイート
- CNCF Wasm Landscape
クイズ
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アプリケーションの作成とデプロイ