
2026/06/06 23:59
Zeroserve:eBPF を用いてスクリプト可能なゼロ設定 Web サーバー
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
ゼロサーブ(Zeroserve)は、最小限のセットアップで静的ファイルを配信できる軽量かつ高性能な HTTPS サーバーです。各サイトごとに単一のタールアーカイブのみを用いて動作を開始できます。改行・認証・レート制限・リバースプロキシなどの機能を実装するサンドボックス化されたミドルウェアとしてユーザー空間で動作する eBPF プログラムをサポートしています。パフォーマンスの主な特徴は、io_uring I/O インターフェースと即時コンパイル(Just-In-Time コンパイル)された eBPF スクリプトによる卓越したシングルスレッド速度です。Ryzen 7 3700X ベンチマークでは、小規模ファイルにおいて nginx を約 17% 上回り、Caddy よりも大幅に高性能であることが示されました。そのアーキテクチャはバイナリコードを共有する複数プロセスの並列実行によりスケールし、各インスタンスはシングルスレッドモデルの monoio ランタイムを使用します。BoringSSL を用いた高度な TLS 機能もサポートしており、TLS 1.3、暗号化された ClientHello(ECH)、SNI サーティフィケート選択、JA4 フィンガープリント、および ECH リレーモードを含みます。運用上の利点として、原子スワップ(SIGHUP)によるホットリロードやダウンタイムなしの瞬時の構成更新が可能です。eBPF プログラミングモデルは、ヘッダー操作やテンプレート置換を行うために共有されるパーリクエストメタデータマップを持つ、ファイル名順ソートされたスクリプトチェーンを使用します。ヘルパー関数はリクエスト検査・変異、暗号処理(SHA-256、HMAC、base64)、JSON 処理、トークンバケット方式のレート制限、AWS SigV4、および XChaCha20-Poly1305 クッキーを用いた OIDC ログインをカバーします。デフォルト設定では eBPF インスタンスあたりのメモリ使用量の上限が 256 KB で、他の接続を止めることを防ぐためにプリエンプト間隔は 2 ms に設定されています。この間隔を増やすことで動的レスポンスのスループットをさらに向上させることが可能です。リバースプロキシとしての性能では、プーリングされた io_uring コネクションを用いて小規模応答において最先端の速度を発揮し、大規模ボディにおいては nginx と競合するレベルのパフォーマンスを示します。全体として、このサーバーは Lua や Perl などの遅いインタプリターに依存せず、ユーザー空間内で直接低レイテンシーとより細かいセキュリティ制御を提供します。
本文
zeroserve:設定不要の高速 HTTPS サーバーとは?
zeroserve は、小さな高速かつ設定不要な HTTPS サーバー です。ウェブサイトのアーカイブファイル(tarball)を渡すだけで動作し、HTTP/2 および TLS 1.3 で提供されます。さらに、ホットリロード機能と極めて軽量なメモリフットプリントを備えています。
本稿の核心となる特徴は、アーカイブに eBPF プログラム を追加できる点です。このプログラムはすべてのリクエストごとに、ユーザー空間内でサンドボックス化されたミドルウェアとして実行され、以下の処理が可能になります。
- リクエストの改ざん・認証・レート制限
- 必要に応じてアップストリームアプリケーションへの逆プロキシ(ゲートウェイ)
✨ 主要な特徴
zeroserve は Nginx や Caddy の代替手段として設計されていますが、従来の設定言語による複雑さを排した独自の設計思想を持っています。
高速性と効率性
- 高速: 単一コアにおいて、ほとんどのワークロード(静的ファイル・スクリプト化ミドルウェア・小型代理レスポンス)で Nginx を上回ります。すべて HTTPS 経由です。
- 効率的な eBPF スクリプティング: スクリプトは JIT コンパイル によりネイティブコードに変換され、ユーザー空間でのサンドボックス化により、各リクエストあたりの実行コストが非常に安価です。
- プログラム即設定: eBPF プログラムそのものが設定となり、リクエストごとの処理フローを決定します。設定ファイルは存在しません。
- 通気 io_uring による I/O: ネットワークおよびディスクのすべての操作は高速な io_uring を介して実行されます。
- 内蔵モダン TLS: TLS 1.3、HTTP/2、Encrypted Client Hello (ECH)、SNI セルティフィケート選択、JA4 フィンガープリントなどを標準搭載しています。
- 運用のシンプルさ: 単一の tarball から提供でき、
信号でサイト内容や TLS 材料をホットリロードできます。SIGHUP
📌 設計思想の違い 従来のサーバー(Nginx/Caddy)は宣言型設定言語とスクリプト実行環境(Lua など)の 2 層に制御が分割され、追跡が難しくなります。 zeroserve はこれを統合し、eBPF プログラムのみでリクエストライフサイクルを一元管理するアプローチをとっています。
📦 単一の tarball で場所そのまま提供
ウェブサイト全体は単一の
tar ファイルです。ゼロースERV は読み込み時にこれをインデックス化し、パスとバイト範囲のマッピングを構築します。ディスク展開を行わずにアーカイブそのものからファイルを配信するため、ドキュメントルートが誤って暴露されるリスクがありません。
デプロイは単一の原子的なファイル置換で完了します。
パッケージと起動コマンド
ディレクトリをパッケージ化するには以下のコマンドを使用します。
# 静的ファイルを tarball へパッケージ化 zeroserve --pack ./public > site.tar # パッケージ化したサイトをサービス開始(0.0.0.0:8080) zeroserve --addr 0.0.0.0:8080 site.tar
ホットリロードの仕組み
新しいバージョンをデプロイする際は、「tarball を入れ替えて SIGHUP を送信する」という手順のみで済みます。
- サイト内容、スクリプト、TLS 材料が同じプロセス内で原子的に入れ替えられます。
- 接続の中断はありません。
# プロセスに再読み込みを促す killall -SIGHUP zeroserve
アーキテクチャと並行性
- すべての I/O は
ランタイムを介して io_uring を通じて処理されます。monoio - 各インスタンスは単一スレッドのエベントループです。
- 「単一スレッド=制限」というわけではありません。複数のインスタンスが 1 つのサーバーボックス上で心地よく共存できる形状(スケールアウト)に最適です。
💻 ユーザー空間で eBPF を使ってスクリプト化
zeroserve/scripts/ ディレクトリ下の .c ファイルは、パッケージ時に clang および llc によって eBPF オブジェクトに変換され、すべてのリクエストで実行されます。
セキュリティとパフォーマンスの担保
- ユーザー空間での動作: カーネルの BPF サブシステムや
の権限は関与しません (CAP_BPF
ランタイムが管理)。async-ebpf - 厳格なメモリ制約: ポインタケージにより、プログラムがアクセスすべきでないメモリへの読み書きを防ぎます。メモリアクセスはプログラムの専用アリーナに対してマスク処理されます。
- プリエンプタブル設計: 1 つのスクリプトが遅すぎるのを防ぐため、ランタイムは完全に割り込み可能です。タイマーが実行中に介入し、制御をエベントループに戻します。
プログラミングモデル
- ソート済みファイル名の順序で実行されるスクリプトのチェーンです。
- スクリプト内で
またはzs_respond
を呼び出すと、チェーンが短絡(早送り)されます。zs_reverse_proxy
例:ヘッダー注入と元データ公開
#include <zeroserve.h> ZS_ENTRY zs_u64 entry(void) { char peer[64]; // ピアアドレスを取得(失敗時は "unknown") if (zs_req_peer(peer, sizeof(peer)) <= 0) zs_strcpy(peer, "unknown"); // テンプレートパス公開:<zs-meta>visitor</zs-meta> に置換 zs_meta_set(ZS_STR("visitor"), ZS_STR(peer)); // レスポンスヘッダー(静的ファイル、zs_respond、proxied のすべてに適用) zs_meta_set(ZS_STR("zs.response.header.x-served-by"), ZS_STR("zeroserve-ebpf")); return 0; }
ヘルパー関数の活用範囲
- リクエスト検査と変異: メソッド、パス、クエリパラメータ、ヘッダー、ピアアドレスの読み取り・改ざん。
- 暗号化と符号化: SHA-256、HMAC-SHA256、base64、hex、
など。getrandom - JSON 処理: ボディ解析、ドキュメントツリー作成・変異、
によるレスポンス送信。zs_json_respond - レート制限: ピア IP や API キーなどをキーとしたトークンバケット(ホットリロード状態維持)。
- AWS SigV4: S3 などへの署名付き Authorization ヘッダー、プレサイン URL。
- OIDC ログイン: Authorization Code + PKCE フロー。サーバーステートレスで「Google でログイン」機能を実現可能(セッションは XChaCha20-Poly1305 クッキーにシール)。
動的エンドポイントの作成
ZS_ENTRY zs_u64 entry(void) { char path[64]; // パス "/health" のみ処理対象 zs_req_path(path, sizeof(path)); if (zs_strcmp(path, "/health") != 0) return 0; // Content-Type 設定と JSON レスポンス送信 zs_meta_set(ZS_STR("zs.response.header.content-type"), ZS_STR("application/json")); zs_respond(200, ZS_STR("{\"status\":\"ok\"}\n")); return 0; }
スクリプト実行の制限と制御
- メモリ制限: デフォルト 256 KB(超過は実行不可)。
- 時間スライシング: 長時間走るスクリプトをエグゼキュータから時間分割し、迷走を抑制。
- 無限ループ対策:自身のリクエストのみ停止しますが、プリエンプションタイマーによりサーバー全体への影響を最小化します。
🔒 TLS の完全性
単なる設定不要以上の機能を含みます:
- TLS 1.3 専用
- BoringSSL で終端
- ネイティブ Encrypted Client Hello (ECH) サポート(実際の SNI が平文として出現しない)
- ディレクトリからの SNI セルティフィケート選択
- スクリプト公開の JA4 クライアントフィンガープリント
- 透明な ECH リレーモード(未暗号化ハンドシェイクをアップストリームへ転送)
これほど多くのトランスポートセキュリティ機能を単一のバイナリに搭載するのは画期的です。
⚡ パフォーマンスベンチマーク
環境: Ryzen 7 3700X (8 コア) 条件:
- zeroserve は設計上単一スレッドのため、公平な比較としてコアあたりパフォーマンスで測定。
で CPU 固定(Nginx:taskset
, Caddy:worker_processes 1
)。GOMAXPROCS=1
を残りのコアから使用し、3 回実行の中央値を取得。wrk -t4 -c100- HTTP/2ではなく**HTTP/1.1 (TLS 1.3)**での測定(ハンドシェイクコストは Keep-alive で相殺)。
1. 小さな静的ファイル (174 B)
静的サイトの主食に対する比較です。zeroserve は約 17% 高速、p99(尾部遅延)でも優れています。
| サーバー | リクエスト/秒 | p99 |
|---|---|---|
| zeroserve | 36,681 | 5.4 ms |
| nginx | 31,226 | 7.8 ms |
| Caddy | 12,830 | 22 ms |
2. 大きな静的ファイル (100 KB)
すべて近づくが、zeroserve は io_uring の読み書きパスにより約 780 MB/s で僅かに優れています。TLS による暗号化コストがボトルネックとなり、その分だけ zeroserve が有利になります。
| サーバー | リクエスト/秒 | スループット | p99 |
|---|---|---|---|
| zeroserve | 8,000 | 782 MB/s | 22 ms |
| nginx | 7,600 | 773 MB/s | 28 ms |
| Caddy | 6,084 | 590 MB/s | 44 ms |
🆚 eBPF vs Lua(Nginx + LuaJIT)との比較
Nginx で一般的な
ngx_http_lua_module と同等の Lua スクリプトで再実装し、比較を行いました。
zeroserve のデフォルト設定ではスクリプトプリエンプションタイマーが 2 ミリ秒ですが、これを 10 ミリ秒に上げて調整すると eBPF は優位になります。
ヘッダー注入ミドルウェアケース
(静的ファイル配信は継続しつつ、ヘッダー追加のみを行う)
| エンジン | リクエスト/秒 | p99 |
|---|---|---|
| zeroserve eBPF (10 ms) | 43,709 | 5.1 ms |
| zeroserve eBPF (2 ms デフォルト) | 31,334 | 6.7 ms |
| nginx Lua | 28,653 | 8.4 ms |
全動的 JSON レスポンスケース
(レスポンス全体をスクリプトが生成)
| エンジン | リクエスト/秒 | p99 |
|---|---|---|
| zeroserve eBPF (10 ms) | 46,945 | 4.5 ms |
| nginx Lua | 41,231 | 6.4 ms |
| zeroserve eBPF (2 ms デフォルト) | 32,393 | 6.7 ms |
結論: 10ms の間隔では、チューニングされた eBPF は両ケースで勝利します。特にミドルウェアケースでは Nginx Lua より約 50% 高速 です。 注釈: デフォルト設定 (2ms) ではスループットが劣化するため、本番環境では
を調整(推奨 10ms)して使用します。--preempt-timer-interval-ms
🔄 逆プロキシとしての性能
アップストリームへのリクエスト転送における比較です。zeroserve は
zs_reverse_proxy() でスクリプト層で処理し、プール化された接続を維持します。
公平な比較のため、Nginx も明示的に keep-alive を有効化しました。
1. 小さなレスポンス (174 B) のプロキシ
(API 呼び出しなど) zeroserve はプールの活用によりリードしており、Nginx より約 22% 高速、Caddy は約 3.4 倍です。
| プロキシ | リクエスト/秒 | p50 | p99 |
|---|---|---|---|
| zeroserve | 26,486 | 3.3 ms | 8 ms |
| nginx | 21,761 | 4.2 ms | 10.5 ms |
| Caddy | 7,683 | 10.3 ms | 33 ms |
2. 大きなレスポンス (100 KB) のプロキシ
(静的ファイルのキャッシュミスや大型 API レスポンスなど) ここで Nginx のバッファリング性能が光り、リードが逆転します。大規模なデータ転送では Nginx が適していると言えます。
| プロキシ | リクエスト/秒 | スループット |
|---|---|---|
| nginx | 5,882 | 585 MB/s |
| Caddy | 4,285 | 406 MB/s |
| zeroserve | 3,631 | 359 MB/s |
💾 メモリ効率性
アイドル状態でのメモリ使用量(PSS)は以下の通りです。
- zeroserve: 約 15 MB
- Nginx: 約 6 MB
- Caddy: 約 60 MB
単一インスタンスでは Nginx より多少大きいですが、重要な点は**「スケーラビリティ」**です。 zeroserve はコードページを共有するため、追加プロセスごとに必要な追加メモリはワーキングセット以上に僅かです。複数コアでスケーリングする際のコスト効率は極めて高くなります。
🚀 まとめ
- ゼロ設定: tarball 1 つで HTTPS サーバーを起動可能。
- eBPF パワー: ユーザー空間での高速スクリプティングと厳格なセキュリティ。
- 高速性: 単一コアでも Nginx を凌駕する処理能力。
- オープンソース: GitHub で公開されており、誰でも試せる。
GitHub でコードをチェックし、実際に環境へ導入することを強くお勧めします!