Firecracker microVMスナップショット復元で実現する28ms起動のコード実行基盤
この記事でわかること
- Firecracker microVMのスナップショット作成・復元APIの使い方と、コールドブート比で最大39倍の起動高速化を実現する仕組み
- マルチテナントコード実行基盤におけるスナップショット復元アーキテクチャの設計パターン(File Backend / UFFD Backend)
- userfaultfd(UFFD)を用いた遅延メモリロードで、大容量メモリのVMでも高速復元を維持する手法
- 差分スナップショットとCopy-on-Write(CoW)によるストレージ効率化の実践
- スナップショット復元時のセキュリティ課題(エントロピー枯渇、VMGenID)と対策
対象読者
- 想定読者: インフラエンジニア・バックエンドエンジニアでコンテナやVM技術の基本を理解している方
-
必要な前提知識:
- Linuxの基本操作(シェル、ファイルシステム、プロセス管理)
- 仮想化の基礎概念(ハイパーバイザー、ゲストOS、ホストOS)
- REST APIの基本的な使い方(curlコマンド)
- KVM(Kernel-based Virtual Machine)の存在を知っている程度で可
結論・成果
Firecrackerのスナップショット復元を活用すると、microVMの起動時間をコールドブートの約1.1秒から28msまで短縮できます。ForgeVMプロジェクトの実装事例では、Dockerコンテナ起動(Alpine: 約180ms、Python 3.12: 約450ms)と比較しても大幅に高速であることが報告されています。さらに、2026年6月にAWSが発表したLambda MicroVMsは、まさにこのFirecrackerスナップショット技術を商用サービスとして提供するものです。
本記事では、Firecrackerのスナップショット機能を使ってマルチテナント対応のコード実行基盤を構築する手法を、APIの使い方からセキュリティ対策まで実践的に解説します。
Firecrackerとスナップショットの基礎を理解する
Firecrackerとは何か
Firecrackerは、AWSが開発したオープンソースのVMM(Virtual Machine Monitor)です。KVMを利用してハードウェアレベルの仮想化を提供しつつ、エミュレートするデバイスを最小限に絞ることで、高速な起動と低いメモリオーバーヘッドを実現しています。
Firecrackerがエミュレートするデバイスはわずか4つです。
| デバイス | 用途 |
|---|---|
| virtio-block | ディスクI/O |
| virtio-net | ネットワーク通信 |
| シリアルコンソール | デバッグ出力 |
| 1ボタンキーボード | VM停止シグナル |
通常のハイパーバイザー(QEMUなど)が数十のデバイスをエミュレートするのに対し、この最小構成がFirecrackerの起動速度を支えています。Firecrackerは毎月15兆回以上のAWS Lambda関数実行を支える基盤であり、AWS Fargateでも利用されています。
MLエンジニアへの補足: Firecrackerの位置づけは、Pythonにおけるマルチプロセシングの Process に近いイメージです。Dockerコンテナがホストカーネルを共有する「スレッド」的な分離であるのに対し、Firecrackerは独立したカーネルを持つ「プロセス」的な分離を、コンテナに近いオーバーヘッドで実現します。
スナップショットの仕組み
Firecrackerのスナップショットは、実行中のmicroVMの状態を3つのコンポーネントとしてディスクに保存する機能です。
- メモリファイル: ゲストメモリの全内容
- ステートファイル: CPU レジスタ状態(命令ポインタ、スタックポインタなど)とデバイス状態(virtioキュー、シリアルポートなど)
- ディスクファイル: rootfsのブロックデバイスイメージ(ユーザー管理)
復元時は、メモリファイルをmmapでマッピングし、CPUレジスタを復元し、デバイス状態を再構築することで、スナップショット取得時点の状態から実行を再開します。新しいVMをゼロからブートするのではなく、初期化済みの状態から再開するという点がポイントです。
コールドブートとスナップショット復元のレイテンシ比較
ForgeVMプロジェクトのベンチマークによると、各段階のレイテンシは以下の通りです。
コールドブート(約1.1秒)の内訳:
| 段階 | 所要時間 |
|---|---|
| Firecrackerプロセス初期化 | ~50ms |
| カーネルロード | ~100ms |
| カーネルブート + init実行 | ~500ms |
| ゲストエージェント起動 + 準備完了シグナル | ~200ms |
| 合計 | ~1,100ms |
スナップショット復元(約28ms)の内訳:
| 段階 | 所要時間 |
|---|---|
| Firecrackerプロセス起動 | ~5ms |
| メモリスナップショットmmap | ~8ms |
| CPU・デバイス状態復元 | ~10ms |
| vsock再接続 + 準備完了シグナル | ~5ms |
| 合計 | ~28ms |
他の分離技術との比較も見てみましょう。2026年のベンチマークでは、以下のような数値が報告されています。
| 技術 | p50レイテンシ | p99レイテンシ | メモリオーバーヘッド/テナント |
|---|---|---|---|
| V8 Isolates(Cloudflare Workers) | 0.4ms | 1.1ms | ~4MB |
| Firecracker スナップショット復元 | 3.2ms | 8.7ms | ~64MB |
| Dockerコンテナ(Alpine) | ~180ms | - | ~10MB |
| Firecracker コールドブート | ~110ms | ~340ms | ~64MB |
V8 Isolatesのほうがレイテンシ・密度ともに優れていますが、JavaScript/WebAssemblyに限定されます。Python、Java、Goなど任意の言語で、かつハードウェアレベルの分離が必要な場合は、Firecrackerスナップショットが現実的な選択肢です。
スナップショットAPIを使ったコード実行基盤を構築する
基本的なスナップショット作成と復元の手順
Firecrackerはすべての操作をUnixソケット経由のREST APIで提供します。スナップショットの作成と復元の基本的な流れを見ていきましょう。
ステップ1: VMの一時停止
# VMをPaused状態にする
curl --unix-socket /tmp/firecracker.socket -i \
-X PATCH 'http://localhost/vm' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"state": "Paused"}'
ステップ2: フルスナップショットの作成
# メモリとステートをファイルに保存
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/snapshot/create' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"snapshot_type": "Full",
"snapshot_path": "/snapshots/base/vmstate",
"mem_file_path": "/snapshots/base/memory"
}'
ステップ3: 別のFirecrackerプロセスでスナップショットを復元
# 新しいFirecrackerプロセスでスナップショットをロード
curl --unix-socket /tmp/firecracker-restore.socket -i \
-X PUT 'http://localhost/snapshot/load' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"snapshot_path": "/snapshots/base/vmstate",
"mem_backend": {
"backend_path": "/snapshots/base/memory",
"backend_type": "File"
},
"track_dirty_pages": true,
"resume_vm": false
}'
ステップ4: VMの再開
# 復元したVMを実行開始
curl --unix-socket /tmp/firecracker-restore.socket -i \
-X PATCH 'http://localhost/vm' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"state": "Resumed"}'
注意: スナップショットのロードは、Firecrackerプロセスの起動直後(VMの設定前)にのみ実行できます。すでにブート済みのVMに対してスナップショットをロードすることはできません。
マルチテナント対応のアーキテクチャ設計
実際のコード実行基盤では、複数のテナント(ユーザー)が同時にコードを実行します。スナップショットを活用したマルチテナントアーキテクチャを設計してみましょう。
ポイントは、ベーススナップショットを1つ作成し、各テナントの実行はCopy-on-Write(CoW)オーバーレイを使って分離することです。
以下はGoでの実装例です。ForgeVMのアーキテクチャを参考に、スナップショットの有無で起動方法を切り替えるパターンを示します。
// sandbox_manager.go
package sandbox
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
)
type SandboxManager struct {
snapshotDir string
workDir string
}
type Sandbox struct {
ID string
SocketPath string
Process *os.Process
}
func (m *SandboxManager) Spawn(ctx context.Context, image string) (*Sandbox, error) {
sandboxID := generateID()
sandboxDir := filepath.Join(m.workDir, sandboxID)
if err := os.MkdirAll(sandboxDir, 0700); err != nil {
return nil, fmt.Errorf("failed to create sandbox dir: %w", err)
}
snapshotPath := filepath.Join(m.snapshotDir, image, "vmstate")
if _, err := os.Stat(snapshotPath); err == nil {
return m.restoreFromSnapshot(ctx, sandboxID, sandboxDir, image)
}
return m.coldBoot(ctx, sandboxID, sandboxDir, image)
}
func (m *SandboxManager) restoreFromSnapshot(
ctx context.Context, id, dir, image string,
) (*Sandbox, error) {
socketPath := filepath.Join(dir, "firecracker.socket")
snapshotBase := filepath.Join(m.snapshotDir, image)
// CoWオーバーレイでrootfsをコピー(元のスナップショットを汚さない)
overlayPath := filepath.Join(dir, "rootfs.ext4")
if err := createCoWOverlay(
filepath.Join(snapshotBase, "rootfs.ext4"), overlayPath,
); err != nil {
return nil, err
}
// Firecrackerプロセスを起動
cmd := exec.CommandContext(ctx, "firecracker",
"--api-sock", socketPath,
)
if err := cmd.Start(); err != nil {
return nil, fmt.Errorf("failed to start firecracker: %w", err)
}
// スナップショットをロード(ここが28msで完了する部分)
if err := loadSnapshot(socketPath, snapshotBase); err != nil {
return nil, err
}
// VMを再開
if err := resumeVM(socketPath); err != nil {
return nil, err
}
return &Sandbox{ID: id, SocketPath: socketPath, Process: cmd.Process}, nil
}
// createCoWOverlay はqemu-imgでCoWオーバーレイを作成する
func createCoWOverlay(basePath, overlayPath string) error {
cmd := exec.Command("qemu-img", "create",
"-f", "qcow2",
"-b", basePath,
"-F", "raw",
overlayPath,
)
return cmd.Run()
}
なぜCoWオーバーレイを使うのか:
- 分離性: 各テナントの書き込みはオーバーレイに記録され、ベースのスナップショットは変更されません
- 効率性: ベースイメージを共有するため、テナント数が増えてもディスク使用量は最小限です
- 安全性: テナントAの操作がテナントBのファイルシステムに影響しません
注意: Firecrackerの公式ドキュメントでは、同一スナップショットから複数のVMを同時に復元するケースをセキュリティ上「安全ではない」と明記しています。これについては後述のセキュリティセクションで詳しく扱います。
ゲスト・ホスト間の通信設計
スナップショットから復元したVMに対してコードを送信し、結果を受け取るには、ゲスト・ホスト間の通信チャネルが必要です。Firecrackerではvsock(virtio socket)を使います。
vsockはネットワークインターフェースを経由せず、カーネル間で直接通信するチャネルです。ネットワーク設定が不要で、スナップショット復元後も即座に利用できます。
# guest_agent.py - ゲストVM内で動作するエージェント
# Python 3.12で動作確認
import json
import socket
import subprocess
import sys
import tempfile
from pathlib import Path
VSOCK_CID_HOST = 2 # ホストのCID
VSOCK_PORT = 9999
def handle_request(data: dict) -> dict:
"""コード実行リクエストを処理する"""
req_type = data.get("type")
if req_type == "exec":
return execute_code(
command=data["command"],
workdir=data.get("workdir", "/workspace"),
timeout=data.get("timeout", 30),
)
return {"type": "error", "message": f"unknown request type: {req_type}"}
def execute_code(command: str, workdir: str, timeout: int) -> dict:
"""コマンドを実行して結果を返す"""
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
timeout=timeout,
cwd=workdir,
)
return {
"type": "result",
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.returncode,
}
except subprocess.TimeoutExpired:
return {"type": "error", "message": f"timeout after {timeout}s"}
except Exception as e:
return {"type": "error", "message": str(e)}
def main():
# vsockソケットでホストからの接続を待機
sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
sock.bind((socket.VMADDR_CID_ANY, VSOCK_PORT))
sock.listen(1)
print("Guest agent ready", file=sys.stderr)
while True:
conn, _ = sock.accept()
try:
raw = conn.recv(65536)
request = json.loads(raw.decode())
response = handle_request(request)
conn.sendall(json.dumps(response).encode())
finally:
conn.close()
if __name__ == "__main__":
main()
# host_client.py - ホスト側からVMにコード実行を依頼するクライアント
# Python 3.12で動作確認
import json
import socket
VSOCK_PORT = 9999
def execute_in_sandbox(vm_cid: int, code: str, timeout: int = 30) -> dict:
"""指定されたVMでコードを実行する"""
sock = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
sock.settimeout(timeout + 5)
try:
sock.connect((vm_cid, VSOCK_PORT))
request = {
"type": "exec",
"command": f"python3 -c {json.dumps(code)}",
"timeout": timeout,
}
sock.sendall(json.dumps(request).encode())
response = sock.recv(65536)
return json.loads(response.decode())
finally:
sock.close()
# 使用例
result = execute_in_sandbox(
vm_cid=3, # VMのCID(Firecrackerの設定で指定)
code="print(sum(range(100)))",
)
print(result)
# {"type": "result", "stdout": "4950\n", "stderr": "", "exit_code": 0}
userfaultfdで大容量メモリの遅延ロードを実現する
なぜ遅延ロードが必要か
ベースとなるスナップショットのメモリサイズが大きくなると(例: Python + NumPy + pandas環境で512MB〜1GB)、復元時にメモリファイル全体をロードする時間が無視できなくなります。メモリ256MBのコピーだけでも測定可能な時間がかかると報告されています。
この問題を解決するのが**userfaultfd(UFFD)**です。UFfDはLinuxカーネルの機能で、ページフォルトの処理をカーネル空間からユーザー空間に委譲します。Firecrackerでは、メモリファイルを最初から全ロードするのではなく、VMが実際にアクセスしたページだけをオンデマンドでロードする遅延ロードを実現します。
UFfDバックエンドの設定方法
Firecrackerでは、スナップショットロード時にメモリバックエンドとしてUFfDを指定できます。
# UFfDハンドラのUnixソケットを指定してスナップショットをロード
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/snapshot/load' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"snapshot_path": "/snapshots/base/vmstate",
"mem_backend": {
"backend_path": "/tmp/uffd-handler.sock",
"backend_type": "Uffd"
},
"track_dirty_pages": true,
"resume_vm": false
}'
backend_typeを"File"から"Uffd"に変更し、backend_pathにUFfDハンドラが待機しているUnixドメインソケットのパスを指定します。
UFfDハンドラのプロセスは、Firecrackerとは別プロセスとして事前に起動しておく必要があります。Firecrackerの公式リポジトリにはon_demand_handler.rsという参考実装があります。
以下は、UFfDハンドラの基本的な動作フローをPythonの疑似コードで示したものです。
# uffd_handler_pseudo.py - UFfDハンドラの動作概念(疑似コード)
# 実際のプロダクション実装はRust/C/Goで行うことを推奨
import mmap
import os
import struct
def uffd_handler_loop(uffd_fd: int, memory_file_path: str, mem_regions: list):
"""
UFfDハンドラのメインループ(概念説明用)
1. Firecrackerからuffdファイルディスクリプタを受け取る
2. メモリファイルをprivate mmapで読み込み準備
3. ページフォルトイベントを監視
4. フォルト発生時に該当ページをUFFDIO_COPYで書き込み
"""
# メモリファイルをprivate mmapでマッピング
mem_fd = os.open(memory_file_path, os.O_RDONLY)
mem_size = os.fstat(mem_fd).st_size
mem_map = mmap.mmap(mem_fd, mem_size, access=mmap.ACCESS_READ)
while True:
# uffdからページフォルトイベントを読み取り
event_data = os.read(uffd_fd, 32)
event_type, flags, address = parse_uffd_event(event_data)
if event_type == UFFD_EVENT_PAGEFAULT:
# フォルトが発生したアドレスに対応する
# メモリファイル上のオフセットを計算
offset = calculate_offset(address, mem_regions)
# 該当ページのデータをメモリファイルから取得
page_data = mem_map[offset:offset + PAGE_SIZE]
# UFFDIO_COPYでFirecrackerのメモリ空間に書き込み
uffdio_copy(uffd_fd, address, page_data)
elif event_type == UFFD_EVENT_REMOVE:
# バルーンデフレーション時のイベント
# 該当ページをゼロで埋める
uffdio_zeropage(uffd_fd, address)
File BackendとUFfD Backendの使い分け:
| 特性 | File Backend | UFfD Backend |
|---|---|---|
| メモリロード方式 | カーネルが自動でページフォルト処理 | ユーザー空間のハンドラが処理 |
| 実装の複雑さ | 低(設定のみ) | 高(ハンドラプロセスが必要) |
| ロード制御 | OS任せ | プリフェッチ・優先順位制御が可能 |
| 適したケース | メモリ256MB以下の小規模VM | メモリ512MB以上の大規模VM |
| 障害時の挙動 | OSが処理 | ハンドラクラッシュでFirecrackerがハング |
注意: UFfDハンドラがクラッシュすると、Firecrackerプロセスは無期限にハングします。プロダクション環境では、ハンドラにタイムアウトを実装し、
SO_PEERCREDでFirecrackerのPIDを確認する設計が推奨されています。
BuildBuddyのチャンク化スナップショット戦略
BuildBuddyは、CI/CDランナーの高速化にFirecrackerスナップショットを活用しています。100GBを超えるモノリシックなスナップショットを扱うため、メモリとディスクをチャンクに分割する独自のアーキテクチャを採用しています。
チャンク化の仕組み:
- スナップショットのメモリとディスクを小さなチャンクに分割
- 各チャンクにハッシュベースのメタデータを付与
- チャンクをリモートキャッシュに圧縮保存
- UFfDハンドラがページフォルト時に必要なチャンクだけをダウンロード・解凍
この方式のメリットは、ホスト間でチャンクを共有できる点です。同じランタイム環境のスナップショットであれば、大部分のチャンクが共通するため、ネットワーク転送量を大幅に削減できます。
BuildBuddyの報告によると、この手法でCI実行のワーカー初期化時間(従来GitHub Actionsで約3.5分)を削減し、メディアンCI実行時間を30秒に短縮しています。
スナップショット復元のセキュリティ課題と対策を実装する
同一スナップショットからの複数復元問題
マルチテナント環境でスナップショットを使う上で、最も重要なセキュリティ課題は一意性の喪失です。Firecrackerの公式ドキュメントでは、同一スナップショットから複数のVMを復元するケースを明確に「安全ではない」と記載しています。
何が問題になるか:
- 乱数の重複: スナップショット時点のエントロピープールが複製されるため、複数のVMが同じ乱数列を生成する可能性がある
- UUIDの衝突: VM内で生成されるUUIDが全く同じになる可能性がある
- 暗号鍵の重複: TLSセッションキーやnonceが重複し、暗号の安全性が崩壊する
VMGenIDによるカーネルレベルの対策
FirecrackerはVMGenID(Virtual Machine Generation Identifier)デバイスを実装しています。復元時に、Firecrackerが16バイトの暗号学的乱数を新たに生成し、ゲストカーネルに通知します。
Linux 5.18以降のカーネルでは、VMGenIDの変更を検知してCSPRNG(暗号学的安全な擬似乱数生成器)を自動的に再シードします。これにより、/dev/urandomから取得する乱数の一意性はカーネルレベルで保証されます。
# ゲストVM内でVMGenIDの確認
# Linux 5.18+ではカーネルが自動的にCSPRNGを再シードする
cat /proc/sys/kernel/random/boot_id
# 復元前: 550e8400-e29b-41d4-a716-446655440000
# 復元後: 6ba7b810-9dad-11d1-80b4-00c04fd430c8(新しいIDに変わる)
# エントロピーデバイスの追加(Firecracker設定)
# Firecrackerの設定時にentropyデバイスを有効化することで
# ゲストカーネルの追加エントロピーソースとなる
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/entropy' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{}'
アプリケーション層での対策
VMGenIDはカーネルの乱数生成器を再シードしますが、アプリケーション層でキャッシュされた値(TLSセッションID、UUID、トークンなど)は自動的にはリフレッシュされません。
以下は、スナップショット復元後にアプリケーション層で一意性を保証するためのチェックリストです。
# snapshot_aware_app.py - スナップショット復元を考慮したアプリケーション設計
# Python 3.12で動作確認
import hashlib
import os
import secrets
import time
class SnapshotAwareApp:
"""スナップショット復元後に状態をリフレッシュするアプリケーション基底クラス"""
def __init__(self):
self._boot_id = self._read_boot_id()
self._session_token = secrets.token_hex(32)
self._init_time = time.monotonic()
def _read_boot_id(self) -> str:
"""カーネルのboot_idを読み取る(VMGenID変更で更新される)"""
with open("/proc/sys/kernel/random/boot_id") as f:
return f.read().strip()
def _check_snapshot_restore(self) -> bool:
"""スナップショットから復元されたかどうかを検知する"""
current_boot_id = self._read_boot_id()
if current_boot_id != self._boot_id:
return True
return False
def ensure_fresh_state(self):
"""復元検知時にキャッシュされた秘密情報をリフレッシュする"""
if self._check_snapshot_restore():
self._boot_id = self._read_boot_id()
# セッショントークンを再生成
self._session_token = secrets.token_hex(32)
# TLS接続の再確立やキャッシュのクリアもここで行う
self._on_restore_detected()
def _on_restore_detected(self):
"""サブクラスで復元後の処理をオーバーライドする"""
pass
マルチテナント環境でのセキュリティチェックリスト:
| 対策項目 | 実装レベル | 対応方法 |
|---|---|---|
| CSPRNG再シード | カーネル(自動) | VMGenID + Linux 5.18+ |
| エントロピーデバイス | Firecracker設定 |
/entropyエンドポイントで有効化 |
| アプリケーション秘密情報 | アプリケーション | boot_id監視 + トークン再生成 |
| TLSセッション | アプリケーション | 復元時にTLS接続を再確立 |
| UID/GID分離 | ホストOS | 各VMプロセスに固有のUID/GIDを割り当て |
| スナップショットファイル保護 | ホストOS | ファイル暗号化 + アクセス制御 |
| ディスククォータ | ホストOS | cgroups v2でI/O制限 |
差分スナップショットでストレージを効率化する
長時間実行されるセッション(AIエージェントのインタラクティブセッションなど)では、定期的にスナップショットを取得してsuspend/resumeを実現したい場合があります。毎回フルスナップショットを取ると、メモリサイズ分のストレージを消費します。
差分スナップショット(Diff Snapshot)は、前回のスナップショット以降に変更されたメモリページだけを保存する機能です。
# 1. VM設定時にdirty page trackingを有効化
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/machine-config' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"vcpu_count": 2,
"mem_size_mib": 512,
"track_dirty_pages": true
}'
# 2. フルスナップショットを作成(ベースライン)
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/snapshot/create' \
-H 'Content-Type: application/json' \
-d '{
"snapshot_type": "Full",
"snapshot_path": "/snapshots/session-001/vmstate",
"mem_file_path": "/snapshots/session-001/memory-full"
}'
# 3. VMを再開して処理を続行
curl --unix-socket /tmp/firecracker.socket -i \
-X PATCH 'http://localhost/vm' \
-H 'Content-Type: application/json' \
-d '{"state": "Resumed"}'
# (ゲスト内で処理が実行される)
# 4. 差分スナップショットを作成(変更ページのみ保存)
curl --unix-socket /tmp/firecracker.socket -i \
-X PATCH 'http://localhost/vm' \
-H 'Content-Type: application/json' \
-d '{"state": "Paused"}'
curl --unix-socket /tmp/firecracker.socket -i \
-X PUT 'http://localhost/snapshot/create' \
-H 'Content-Type: application/json' \
-d '{
"snapshot_type": "Diff",
"snapshot_path": "/snapshots/session-001/vmstate-diff",
"mem_file_path": "/snapshots/session-001/memory-diff"
}'
差分ファイルは変更されたページのみを含むスパースファイルとして保存されます。復元時にはベースのフルスナップショットに差分を適用(リベース)します。
# 差分スナップショットをベースにマージ(snapshot-editorツール使用)
snapshot-editor edit-memory rebase \
--memory-path /snapshots/session-001/memory-full \
--diff-path /snapshots/session-001/memory-diff
制約: dirty page trackingを有効にすると、KVMがメモリ書き込みを監視するため、若干のCPUオーバーヘッドが発生します。ただし、Firecrackerの公式ドキュメントによると、huge pagesの最適化効果が相殺される程度であり、通常のワークロードでは無視できるレベルとされています。
よくある問題と解決方法
| 問題 | 原因 | 解決方法 |
|---|---|---|
| スナップショット復元が遅い(100ms超) | cgroups v1環境で実行している | cgroups v2が有効なホストで実行する(公式推奨) |
| 復元後にVM内のネットワークが繋がらない | TAP インターフェースが事前作成されていない | スナップショット作成時と同一名のTAPデバイスを復元前に作成する |
| 復元後にゲスト内の乱数が重複する | エントロピープールがスナップショット時点のまま | Linux 5.18+カーネルを使用し、VMGenIDによる自動再シードを利用する |
| UFfDハンドラクラッシュでFirecrackerがハングする | ハンドラプロセスの異常終了 | ハンドラにwatchdogタイマーを実装し、タイムアウト時にFirecrackerのPIDにシグナルを送信する |
| ディスク使用量が増え続ける | フルスナップショットの蓄積 | 差分スナップショットを活用し、定期的にベースをリベースして古い差分を削除する |
snapshot/loadで "Invalid snapshot state" エラー |
ブート済みのFirecrackerプロセスにロードしようとしている | 新しいFirecrackerプロセスを起動し、VM設定前にスナップショットをロードする |
まとめと次のステップ
まとめ:
- Firecrackerのスナップショット復元により、microVMの起動時間をコールドブートの約1.1秒から28msに短縮できます(ForgeVMの報告値)
- マルチテナント環境では、ベーススナップショット + CoWオーバーレイの組み合わせで分離性と効率性を両立できます
- 大容量メモリ環境ではuserfaultfd(UFfD)による遅延メモリロードが復元速度の維持に有効です
- セキュリティ面では**VMGenID + Linux 5.18+**でカーネルレベルのCSPRNG再シードが自動化されますが、アプリケーション層のトークン再生成は別途設計が必要です
- 2026年6月に発表されたAWS Lambda MicroVMsは、この技術スタックを商用サービスとして提供しており、Firecrackerスナップショットの信頼性と実用性を裏付けています
次にやるべきこと:
-
Firecracker公式リポジトリをクローンし、
getting-started.mdに沿ってローカル環境でmicroVMを起動・スナップショット取得を試す - AWS Lambda MicroVMsを試し、マネージドサービスとしてのスナップショット復元を体験する(対応リージョン: us-east-1, us-east-2, us-west-2, eu-west-1, ap-northeast-1)
- 自前で構築する場合は、E2Bのオープンソースインフラを参考に、UFfDハンドラとスナップショット管理を実装する
参考
- Firecracker公式リポジトリ - Snapshot support
- Firecracker公式リポジトリ - Handling page faults on snapshot resume
- How I built sandboxes that boot in 28ms using Firecracker snapshots - DEV Community
- Micro-VM Snapshots vs. V8 Isolates in 2026 - TechBytes
- AWS Lambda MicroVMs 公式ブログ
- AWS Lambda MicroVMs ドキュメント
- Snapshot, Chunk, Clone: Fast Runners at Scale - BuildBuddy
- Restoring Uniqueness in MicroVM Snapshots - arXiv
- Firecracker公式リポジトリ - Random for clones
注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。