Webサーバーを何にするか、という議論はずっと「選ぶ」話でした。Apacheにするか、Nginxにするか。自動TLSが欲しければCaddy、HTTP/3ならH2O、JavaならTomcat、PythonならGunicorn。世の中には用途ごとに完成度の高い実装が揃っていて、我々エンジニアの仕事はその中から一番マシなものを選び、設定ファイルで自分の用途に寄せることでした。
ところが、AIと一緒に開発するようになって前提がひとつ変わりました。「無いなら作る」の作るのコストが、現実的な水準まで落ちたということです。
そこで実際に作ってみました。AWS・Docker・WordPress/Laravelに用途を絞った自作Webサーバー FIELD-WS 0.1 です。この記事では、何を作ったのか、何をあえて作らなかったのか、どう動かすのかを実装レベルで書きます。tar.gzを配布していますので、そのままEC2で動かせます。
そもそもWebサーバーにはこれだけの種類がある
まず現在地の確認です。ひとくちにサーバーといっても、レイヤーも役割も違うものが並んでいます。
| 種類 | 代表例 | 主な役割 |
|---|---|---|
| Webサーバー | Apache HTTP Server | HTTP受付、静的ファイル、PHP連携など |
| Webサーバー/リバースプロキシ | Nginx | HTTP、静的配信、リバースプロキシ |
| 高速Webサーバー | Caddy | HTTP/HTTPS、自動TLS、リバースプロキシ |
| 軽量Webサーバー | Lighttpd | 軽量なHTTPサーバー |
| 高性能Webサーバー | LiteSpeed / OpenLiteSpeed | Apache互換、高速化 |
| イベント駆動Webサーバー | H2O | HTTP/2・HTTP/3など |
| Javaアプリサーバー | Tomcat / Jetty / Undertow | Servlet・JSPを実行 |
| Node.js | Node HTTP / Fastify | アプリ自身がHTTPサーバーになる |
| Python | Gunicorn / Uvicorn | PythonアプリをHTTPで提供 |
| Rust | Hyper / Axum | 非常に高速なHTTPサーバーを構築できる |
| Go | net/http / Fiber | アプリ自身で高速HTTPサーバーを構築できる |
これだけあるのに、なぜ作るのか。理由は単純で、これらはすべて「誰にでも使えること」を前提に作られているからです。汎用であることは長所ですが、同時に、自分の用途では一度も使わない機能のために設定が複雑になり、分岐が増え、メモリを持っていかれる、ということでもあります。
我々の用途はかなり偏っています。AWS(ALB/CloudFrontでTLSは終端済み)・Docker・WordPress/Laravel・公開GETが大半。この4つに絞ってよいなら、汎用サーバーが持っている機能の大半は要りません。
AI時代の「作る」の現実的なコスト
これまで自作Webサーバーは、勉強用のおもちゃか、大企業の研究開発かのどちらかでした。epollのイベントループを書き、HTTPパーサを書き、キャッシュを書き、レートリミットを書き、テストを書く。個人や小さな会社が本業の片手間にやる作業量ではありません。
いま変わったのはここです。設計の判断は人間がやり、実装の手数はAIが引き受ける。この分担にすると、「用途を絞ったサーバー」は現実的な時間で形になります。実際、FIELD-WS 0.1の本体はC言語で236行です。外部ライブラリはゼロ、依存はlibcとpthreadだけです。
重要なのは、AIに「Webサーバーを作って」と投げたわけではないことです。人間側が決めたのは次のような判断でした。
- TLSはやらない(ALB/CloudFrontが終端しているので二重にやる意味がない)
- FastCGIはまだやらない(0.1では既存のApache版WordPressへプロキシする)
- 設定ファイル形式は作らない(環境変数だけにする。Docker前提なら十分)
- キャッシュはWordPressの都合を知っているものにする(後述)
この「作らないものを決める」作業こそが設計で、ここはAIに任せられません。逆にいえば、ここさえ決めれば実装は進みます。
FIELD-WSの位置づけ
概念としては、冒頭の表の「Webサーバー/リバースプロキシ」の行に並ぶものです。
| 種類 | 代表例 | 主な役割 |
|---|---|---|
| 高効率Webサーバー/リバプロ | FIELD-WS | HTTP受付、静的配信、リバースプロキシ、WordPress向けキャッシュ、レート制限 |
ブラウザからHTTPリクエストを受け、静的ファイルを直接返し、WordPressやLaravelへ転送し、応答をキャッシュし、Dockerコンテナとして動く。基本構造はApacheやNginxと同じです。
ただし成熟度はまったく違います。ここを曖昧にすると信用の話になるので、はっきり書いておきます。
| 製品 | 成熟度 | 特徴 |
|---|---|---|
| Apache | 非常に高い | 多機能・互換性重視 |
| Nginx | 非常に高い | 静的配信・リバースプロキシに強い |
| Caddy | 高い | 自動HTTPSが強い |
| H2O | 高い | HTTP/2・HTTP/3に強い |
| FIELD-WS | MVP | AWS・Docker・WordPress/Laravel用途へ特化 |
FIELD-WS 0.1は「思想を実測できるMVP」であって、Nginxの即時置き換えを保証する完成版ではありません。「Nginxと並ぶ製品」と名乗るには、機能追加だけでなく、脆弱性検査・異常系試験・長時間負荷試験・複数EC2での本番運用実績が要ります。そこはまだ通過していません。
目指している位置は、汎用の新しいWebサーバーではなく、こちらです。
AWS・Docker・WordPress・Laravelに特化し、Nginxより設定が簡単で、公開GETを高速に処理するWebサーバー
この定義なら、独立した製品として成立すると考えています。
実装したもの
0.1で動いているのは次の範囲です。
epollによるノンブロッキングI/Osendfileによる静的ファイルのzero-copy配信(カーネル空間からそのまま送る)- CPUコア数に応じたworkerプロセスと
SO_REUSEPORT(カーネルが接続を各workerへ分散する) - HTTP/1.1 keep-alive、GET、HEAD
/api/など任意のprefixでのリバースプロキシ- upstream応答のインメモリキャッシュ(容量上限・TTL・簡易LRU)
- WordPress管理画面・ログインCookie・WooCommerceカート・POSTのキャッシュ回避
- IP単位のtoken bucketによるレート制限
/health、Prometheus形式の/metrics、token付きのキャッシュpurge- パストラバーサル対策、16 MiBのupstream応答上限、セキュリティヘッダ
WordPress向けのキャッシュ回避は、汎用サーバーの設定では地味に事故るところです。ここをサーバー側が最初から知っているのがFIELD-WSの実利です。/wp-admin、/wp-login.php、POSTリクエスト、wordpress_logged_in_ Cookieを持つリクエスト、WooCommerceのカートは自動的にキャッシュを迂回します。さらに、upstreamがSet-Cookieを返した応答はキャッシュに入れません(ログイン状態の取り違えを起こさないため)。
記事を更新したときの全パージはこれだけです。
curl 'http://127.0.0.1:8080/__fieldws/purge?token=YOUR_TOKEN'
実装していないもの(あえて)
正直に列挙します。0.1時点で未実装なのは次です。
- HTTPSの直接処理(ALB/CloudFrontでTLS終端する前提)
- HTTP/2・HTTP/3
- FastCGIによるPHP-FPMへの直接接続
- WebSocket
- gzip・Brotli圧縮
- 詳細なアクセスログ
- graceful reload
- 細かい設定ルール全般
- 長期間の実運用検証
このうちTLSは「未実装」というより設計判断です。AWSに載せる時点でTLSはALBかCloudFrontが終端しており、その内側でもう一度TLSを張る必要はありません。証明書の更新も、暗号スイートの追従も、AWS側の仕事にしておくほうが安全です。
一方でFastCGIは、次の開発でやる予定のものです。PHP-FPMへ直接FastCGIで繋げば、構成からApacheを完全に外せます。0.1では公式のApache版WordPressイメージをそのまま使い、FIELD-WSは前段だけを担当しています。
Docker対応版の構成
Docker Composeで、FIELD-WS・WordPress・MySQLをまとめて起動できるようにしました。
ALB
↓ :8080
FIELD-WS
↓ 内部通信
WordPress
↓
MySQL 8.4
公開されるのはFIELD-WSの8080番だけで、WordPressとMySQLはComposeの内部ネットワークに閉じています。ホスト側にポートを出していないので、外から直接叩くことはできません。
FIELD-WSのコンテナ側もそれなりに絞めてあります。
read_only: true
tmpfs:
- /tmp:size=16m,mode=1777
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
ルートファイルシステムは読み取り専用、権限昇格は禁止、Linux capabilityは全部剥奪、実行ユーザーはuid 10001の非rootです。ヘルスチェックはバイナリ自身の--healthcheckが担当します(コンテナにcurlを入れずに済みます)。
使い方
1. ダウンロードして展開する
fieldws-0.1.0.tar.gz をダウンロード(12KB)
tar -xzf fieldws-0.1.0.tar.gz
cd fieldws
念のため、配布物のSHA-256はこちらです。
161d5bc509b5596ce4b073de450baddf804164e9dd73f3ebe48ba3d69cac6d10 fieldws-0.1.0.tar.gz
2. .envを作ってパスワードを変える
cp .env.example .env
nano .env
.envの秘密値3つを必ず変更してください。空のままだとComposeが起動を拒否します(:?指定にしてあります)。
# Generate with: openssl rand -hex 32
FIELDWS_ADMIN_TOKEN=replace-with-random-value
WORDPRESS_DB_PASSWORD=replace-with-random-value
MYSQL_ROOT_PASSWORD=replace-with-random-value
値はopenssl rand -hex 32で作るのが早いです。
3. 起動して確認する
docker compose up -d --build
docker compose ps
curl http://127.0.0.1:8080/health
okが返れば起動しています。あとはブラウザでhttp://EC2のIP:8080/を開くと、WordPressの初期設定画面が出ます。
本番では8080番をインターネットへ直接公開しないでください。 EC2のSecurity Groupで、ALBのSecurity Groupからのみ8080を許可する構成にします。
Docker無しで動かす場合
単体バイナリなので、ビルドして直接起動することもできます。
make
FIELDWS_ROOT="$PWD/www" FIELDWS_PORT=8080 ./fieldws
curl http://127.0.0.1:8080/health
EC2(Amazon Linux 2023)へsystemdサービスとして入れる場合は、同梱のスクリプトが入れてくれます。
chmod +x deploy/install-amazon-linux-2023.sh
sudo ./deploy/install-amazon-linux-2023.sh
curl http://127.0.0.1:8080/health
ALBのtarget groupはHTTPの8080、ヘルスチェックパスは/healthにします。設定変更後はsudo systemctl restart fieldws、ログはsudo journalctl -u fieldws -fで見られます。
設定は環境変数だけ
独自の設定ファイル形式は作りませんでした。Docker前提なら環境変数で足りますし、「設定ファイルの文法を覚える」というコストを利用者に払わせないためです。
| 環境変数 | 初期値 | 内容 |
|---|---|---|
FIELDWS_LISTEN | 0.0.0.0 | listenアドレス |
FIELDWS_PORT | 8080 | listenポート |
FIELDWS_ROOT | ./www | 静的ファイルのドキュメントルート |
FIELDWS_WORKERS | CPU数 | workerプロセス数 |
FIELDWS_UPSTREAM | 空 | host:port。空ならプロキシ無効 |
FIELDWS_PROXY_PREFIX | /api/ | プロキシ対象のprefix |
FIELDWS_CACHE_TTL | 30 | キャッシュ秒数 |
FIELDWS_CACHE_MB | 64 | プロセスごとのキャッシュ上限MiB |
FIELDWS_RATE | 300 | IPごとの毎秒補充token |
FIELDWS_BURST | 600 | IPごとの最大burst |
FIELDWS_ADMIN_TOKEN | 空 | purge用token。空ならpurgeを拒否 |
WordPressの前段として使うなら、この形になります。
FIELDWS_PROXY_PREFIX=/ \
FIELDWS_UPSTREAM=wordpress:80 \
FIELDWS_CACHE_TTL=30 \
FIELDWS_CACHE_MB=256 \
FIELDWS_ADMIN_TOKEN='十分に長いランダム値' \
./fieldws
動作の確認とベンチも同梱しています。
make test
./bench.py -c 64 -n 100000
本番投入の前に必要なこと
MVPだと書いた以上、何が足りないかも書いておきます。実運用へ上げる前に必要な次段階は、upstreamの完全非同期化、chunkedリクエスト/レスポンス対応、cache stampede防止、複数worker間の共有キャッシュ、graceful reload、アクセスログ、fuzzing、そしてTLS/HTTP/2/HTTP/3です。
いま安心して使えるのは、「ALBの内側で、公開GETが大半のWordPressサイトを、Dockerで動かす」という、まさに設計時に想定した範囲だけです。逆にいうと、その範囲であれば、設定は環境変数11個で済み、コンテナは非rootの読み取り専用で回り、キャッシュはWordPressの都合を最初から知っています。
サーバーは「選ぶ」から「作る」へ
言いたいことは、自作サーバーがNginxより優れている、という話ではまったくありません。用途を絞り込めば、自分たちの条件にちょうど合うものを作るという選択肢が、現実的なコストの側に入ってきたということです。
これはWebサーバーに限りません。社内ツール、変換バッチ、監視の仕組み、業務システムの一部分。「既製品を入れて、合わない部分を運用でカバーする」が当たり前だったところに、「用途を絞って作る」が並びました。判断の材料が増えたわけです。
そして、この作り方で一番時間を使うのは、コードを書く工程ではありません。何を作らないかを決める工程です。AIが実装を引き受けてくれる分、設計の解像度がそのまま成果物の質になります。ここは人間の仕事として、むしろ重くなったと感じています。
FIELD-WSは今後も開発を続けます。次はPHP-FPMへのFastCGI直結で、構成からApacheを外すところです。
それでは、みなさん効率の良いAIライフを。