Webサーバーアーキテクチャ
所要時間
約45分
学べること
- WebサーバーがTCP接続をどう処理するか
- WebサーバーがHTTPSをどう処理するか
- アプリケーションとの連携方式(リバースプロキシ、組み込み等)
- Nginx、Apache、Tomcat等の役割の違い
- 本番環境での構成パターン
前提知識
- 09-tcp-fundamentals.md - TCP基礎
- 08-https-termination.md - HTTPS終端
- HTTP/HTTPSの基本
1. Webサーバーの役割
1.1 Webサーバーとは
┌─────────────────────────────────────────────────────────────┐
│ Webサーバーが担う責務 │
├─────────────────────────────────────────────────────────────┤
│ │
│ クライアント(ブラウザ、curl等) │
│ │ │
│ │ TCP接続 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Webサーバー │ │
│ │ │ │
│ │ 1. TCP接続の受付・管理 │ │
│ │ - ポートをLISTEN │ │
│ │ - 同時接続の管理 │ │
│ │ - Keep-Aliveの管理 │ │
│ │ │ │
│ │ 2. TLS/HTTPSの処理 │ │
│ │ - 証明書の読み込み │ │
│ │ - TLSハンドシェイク │ │
│ │ - 暗号化/復号 │ │
│ │ │ │
│ │ 3. HTTPリクエストの解析 │ │
│ │ - メソッド、パス、ヘッダーの解析 │ │
│ │ - ボディの読み取り │ │
│ │ │ │
│ │ 4. リクエストのルーティング │ │
│ │ - 静的ファイルの配信 │ │
│ │ - アプリケーションへの転送 │ │
│ │ │ │
│ │ 5. HTTPレスポンスの送信 │ │
│ │ - ヘッダー・ボディの送信 │ │
│ │ - 圧縮、キャッシュ制御 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ アプリケーション(Java, Python, Node.js等) │
│ │
└─── ──────────────────────────────────────────────────────────┘
1.2 代表的なWebサーバー
| サーバー | 特徴 | 主な用途 |
|---|---|---|
| Nginx | イベント駆動、高性能 | リバースプロキシ、静的ファイル配信 |
| Apache HTTP Server | プロセス/スレッドベース、モジュール豊富 | 汎用Webサーバー |
| Tomcat | Java Servlet コンテナ | Javaアプリケーション |
| Jetty | 軽量Java Servletコンテナ | 組み込み用途、マイクロサービス |
| Undertow | 軽量・高性能(WildFly内蔵) | Spring Boot デフォルト選択肢 |
| Node.js (http) | JavaScript、シングルスレッド+イベントループ | APIサーバー |
2. TCP接続の処理
2.1 接続受付の仕組み
┌─────────────────────────────────────────────────────────────┐
│ Webサーバーの接続受付 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【OSレベル】 │
│ │
│ 1. socket() - ソケット作成 │
│ 2. bind() - IPアドレス:ポートに紐付け │
│ 3. listen() - 接続待ち状態に(backlogキュー作成) │
│ 4. accept() - 接続を受け入れ、新しいソケットを返す │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Listen Socket (0.0.0.0:443) │ │
│ │ │ │ │
│ │ │ accept() │ │
│ │ ▼ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Client 1 │ │Client 2 │ │Client 3 │ ... │ │
│ │ │Socket │ │Socket │ │Socket │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ │ 各クライアント接続に対して個別のソケット │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【backlogキュー】 │
│ │
│ listen(fd, backlog) の backlog: │
│ - TCP接続確立待ち(SYN_RECEIVED)のキューサイズ │
│ - accept()されるのを待っている接続のキュー │
│ │
│ net.core.somaxconn (Linux) でシステム上限を設定 │
│ │
│ キュー溢れ時: │
│ - 新規接続がドロップされる │
│ - クライアントはタイムアウト or RST │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 接続処理のアーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│ 接続処理のモデル比較 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【プロセスベース(Apache prefork)】 │
│ │
│ Master Process │
│ │ │
│ ├── Worker Process 1 ── Client 1 │
│ ├── Worker Process 2 ── Client 2 │
│ ├── Worker Process 3 ── Client 3 │
│ └── ... │
│ │
│ 特徴: │
│ - 各接続に1プロセス │
│ - プロセス間でメモリ 分離(安定性高い) │
│ - メモリ消費大、コンテキストスイッチコスト │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【スレッドベース(Apache worker, Tomcat)】 │
│ │
│ Process │
│ │ │
│ ├── Thread 1 ── Client 1 │
│ ├── Thread 2 ── Client 2 │
│ ├── Thread 3 ── Client 3 │
│ └── ... │
│ │
│ 特徴: │
│ - 各接続に1スレッド │
│ - プロセスより軽量 │
│ - スレッドプールで上限管理 │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【イベント駆動(Nginx, Node.js)】 │
│ │
│ Worker Process │
│ │ │
│ └── Event Loop ─┬── Client 1 (fd) │
│ ├── Client 2 (fd) │
│ ├── Client 3 (fd) │
│ └── ... (数万接続可能) │
│ │
│ 特徴: │
│ - 1スレッドで多数の接続を処理 │
│ - epoll/kqueue で I/O 多重化 │
│ - ノンブロッキングI/O必須 │
│ - C10K問題を解決 │
│ │
└── ───────────────────────────────────────────────────────────┘
2.3 Nginx の Worker モデル
┌─────────────────────────────────────────────────────────────┐
│ Nginx のアーキテクチャ │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Master Process (root) │ │
│ │ - 設定ファイル読み込み │ │
│ │ - Worker の起動・監視 │ │
│ │ - ログファイルオープン │ │
│ │ - 特権ポート (80, 443) のバインド │ │
│ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ fork() │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ ... │
│ │ (nginx user) │ │ (nginx user) │ │ (nginx user) │ │
│ │ │ │ │ │ │ │
│ │ Event Loop │ │ Event Loop │ │ Event Loop │ │
│ │ ├ Client A │ │ ├ Client D │ │ ├ Client G │ │
│ │ ├ Client B │ │ ├ Client E │ │ ├ Client H │ │
│ │ └ Client C │ │ └ Client F │ │ └ Client I │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ worker_processes auto; # CPUコア数に合わせて自動設定 │
│ worker_connections 1024; # 1 Worker あたりの最大接続数 │
│ │
│ 最大同時接続数 = worker_processes × worker_connections │
│ 例: 4 × 1024 = 4096 同時接続 │
│ │
└─────────────────────────────────────────────────────────────┘
3. HTTPS の処理
3.1 WebサーバーでのTLS処理
┌─────────────────────────────────────────────────────────────┐
│ WebサーバーのTLS処理 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【起動時】 │
│ │
│ 1. 証明書ファイル読み込み │
│ ssl_certificate /path/to/cert.pem; │
│ ssl_certificate_key /path/to/key.pem; │
│ │
│ 2. 秘密鍵のメモリへの展開 │
│ - パスフレーズがあれば復号 │
│ - OpenSSL の SSL_CTX に設定 │
│ │
│ 3. TLS設定の初期化 │
│ - 使用するプロトコルバージョン │
│ - 暗号スイート │
│ - セッションキャッシュ │
│ │
│ ───────────────────────────────────────────────────────── │
│ │
│ 【接続時】 │
│ │
│ Client ────────────────────────────────► Nginx │
│ │
│ 1. TCP accept() │
│ 2. SSL_accept() - TLSハンドシェイク │
│ ├── ClientHello 受信 │
│ ├── ServerHello + 証明書 送信 │
│ ├── 鍵交換 │
│ └── Finished │
│ 3. SSL_read() - 暗号化データ読み取り → 復号 │
│ 4. HTTPリクエストとして解析 │
│ 5. 処理 │
│ 6. SSL_write() - レスポンス暗号化 → 送信 │
│ │
└─────────────────────────────────────────────────────────────┘
3.2 Nginx の TLS 設定例
server {
listen 443 ssl http2;
server_name example.com;
# 証明書
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# プロトコルバージョン
ssl_protocols TLSv1.2 TLSv1.3;
# 暗号スイート
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# セッション再利用(パフォーマンス向上)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # PFS のため無効化推奨
# OCSP Stapling(証明書失効確認の高速化)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
proxy_pass http://backend;
}
}
3.3 TLS処理のパフォーマンス影響
┌─────────────────────────────────────────────────────────────┐
│ TLS のパフォーマンス考慮点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 【CPU負荷】 │
│ │
│ TLSハンドシェイク: │
│ - RSA 2048bit 署名: 約 1ms │
│ - ECDHE 鍵交換: 約 0.5ms │
│ - 1秒あたり数千ハンドシェイクでCPU飽和の可能性 │
│ │
│ データ暗号化/復号: │
│ - AES-NI(CPU命令)対応なら高速 │