第1章 「Web サーバ」という言葉の混乱を解く
1.1 リバースプロキシとしての Web サーバ
「Web サーバ」という言葉は、文脈によってまったく異なるソフトウェアを指します。 この混乱はデプロイ時のトラブルシューティングを著しく困難にするため、本章の冒頭で用語を明確に整理していきます。
図1-1 リバースプロキシを中心とした Web サーバ構成
最も古典的な意味での「Web サーバ」は、Nginx や Apache(httpd)のようなソフトウェアを指します。 これらはクライアント(ブラウザ)からの TCP 接続を受け付け、HTTP リクエストをパースし、レスポンスを返すプログラムです。 もともとは静的ファイル(HTML、CSS、画像)を返すだけのソフトウェアでしたが、現代の Web アプリケーション構成では「リバースプロキシ」としての役割が主になっています。
注釈
「リバースプロキシ」とは、クライアントに対して唯一の窓口となるサーバのことです。 クライアントはリバースプロキシだけと通信し、リバースプロキシが背後のサーバ(アプリケーションサーバ)へリクエストを転送します。 「リバース(逆)」という名前は、クライアント側に設置される「フォワードプロキシ」の逆側(サーバ側)に置かれることから来ています。
リバースプロキシとしての Nginx は、クライアントに対して唯一の窓口となり、リクエストを背後のアプリケーションサーバ(Gunicorn や Uvicorn)に転送します。 その主な責務は以下の通りです。
TLS 終端(HTTPS の暗号化と復号)
静的ファイルの直接配信
リクエストのバッファリング
レスポンスの gzip 圧縮
アクセスログの記録
レート制限
複数のアプリケーションサーバへのロードバランシング
図1-2 リクエストが通る層と「Web サーバ」の2つの意味
警告
この構成図の中で「Web サーバはどれか」と聞かれたとき、Nginx を指す人もいれば Gunicorn を指す人もいます。 ドキュメントや障害報告で「Web サーバを再起動した」と言われても、どの層を再起動したのかが不明確になってしまいます。 チーム内では「Nginx」「Gunicorn」「アプリケーション」のように具体的な名前で呼ぶことを習慣にしましょう。
1.2 WSGI/ASGI サーバ
Gunicorn、uWSGI、Uvicorn、Daphne、Hypercorn などは、WSGI または ASGI の仕様に準拠してアプリケーションを呼び出すサーバです。 本書ではこれらを「WSGI サーバ」「ASGI サーバ」または総称して「アプリケーションサーバ」と呼んできました。
これらのサーバの主な責務は次の通りです。
TCP ソケットの管理
HTTP リクエストのパース
environ(WSGI)またはscope(ASGI)の構築アプリケーション callable の呼び出し
レスポンスのクライアントへの書き出し
ワーカープロセスの生成と管理
タイムアウト監視
グレースフルシャットダウン
# WSGI サーバ
gunicorn myproject.wsgi:application --workers 4 --bind 127.0.0.1:8000
# ASGI サーバ
uvicorn myproject.asgi:application --workers 4 --host 127.0.0.1 --port 8000
WSGI/ASGI サーバは HTTP を理解しリクエストをパースするため、「HTTP サーバ」でもあります。 実際、Gunicorn や Uvicorn はリバースプロキシなしで直接クライアントのリクエストを受けることも技術的には可能です。 しかし本番環境ではリバースプロキシを前段に置くのが標準構成です。 その理由は、Nginx が大量の同時接続の管理、スロークライアントからのリクエストバッファリング、TLS 処理、静的ファイル配信において、Gunicorn / Uvicorn よりもはるかに効率的だからです。
Gunicorn と Uvicorn の違いを整理すると次の通りです。
項目 |
Gunicorn |
Uvicorn |
|---|---|---|
対応仕様 |
WSGI |
ASGI |
処理モデル |
プリフォーク(マスター + 複数ワーカー) |
asyncio イベントループ |
得意な処理 |
同期処理、CPU バウンド |
非同期処理、I/O バウンド |
WebSocket |
対応なし(単体では) |
対応あり |
Gunicorn に uvicorn_worker.UvicornWorker を組み合わせることで、Gunicorn のプロセス管理能力と Uvicorn の非同期処理能力を両立させる構成も一般的です。現行版では uvicorn-worker パッケージ(pip install uvicorn-worker)を導入して使います。旧来は uvicorn.workers.UvicornWorker という表記が広く使われていましたが、これは Uvicorn 0.30 以降で非推奨です。
# Gunicorn + Uvicorn ワーカーの組み合わせ
gunicorn myproject.asgi:application \
--worker-class uvicorn_worker.UvicornWorker \
--workers 4 \
--bind 127.0.0.1:8000
この構成では Gunicorn がマスタープロセスとしてワーカーの生成、監視、再起動を担い、各ワーカーは Uvicorn のイベントループで ASGI アプリケーションを実行します。
1.3 開発サーバ
Django の python manage.py runserver と Uvicorn の uvicorn --reload は開発用のサーバです。
これらはコード変更時の自動リロード、デバッグ情報の表示、開発者の手元での動作確認を目的としています。
# Django 開発サーバ
python manage.py runserver 0.0.0.0:8000
# Uvicorn 開発モード
uvicorn main:app --reload --host 0.0.0.0 --port 8000
# FastAPI の起動(uvicorn を内部で使用)
fastapi dev main.py
危険
開発サーバは本番環境では絶対に使用してはいけません。 本番環境に不適切な理由を以下に示します。
ワーカー管理がない(1プロセスでクラッシュすると全体停止)
タイムアウト管理が弱い
TLS 終端がない
静的ファイル配信が最適化されていない
パフォーマンスが本番ワークロード向けに設計されていない
しかし開発サーバも HTTP をパースしてアプリケーションを呼び出すため、広い意味では「Web サーバ」です。 「ローカルで Web サーバを起動して」と言われたとき、開発サーバを指しているのか、本番用のサーバを指しているのかは文脈で判断します。
1.4 アプリケーションプロセス管理
本番環境では、WSGI/ASGI サーバのプロセスが永続的に動作し続ける必要があります。 サーバプロセスがクラッシュした場合の自動再起動、OS 起動時の自動起動、ログの管理などを担うのがプロセスマネージャです。
最も一般的なのは systemd(Linux)で、他に supervisord やコンテナ環境での Docker / Kubernetes があります。
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon for myproject
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/gunicorn \
myproject.wsgi:application \
--workers 4 \
--bind unix:/run/gunicorn/myproject.sock \
--timeout 30
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
注釈
プロセスマネージャは HTTP を理解しません。Gunicorn プロセスを起動、停止、再起動し、クラッシュ時に自動復旧するだけです。
障害時に「サーバを再起動して」と言われたとき、systemctl restart gunicorn なのか systemctl restart nginx なのか、あるいは OS 自体を再起動するのかで意味が変わります。
「どの層を再起動するのか」を常に意識するようにしましょう。
ここまでの4つの層を改めて整理します。
図1-3 デプロイを構成する4層とその連携
層 |
具体例 |
主な責務 |
|---|---|---|
プロセスマネージャ |
systemd, supervisord, Docker, Kubernetes |
プロセスの起動・監視・再起動 |
リバースプロキシ |
Nginx, Caddy, Traefik |
TLS, 静的配信, バッファ, 圧縮, ロードバランシング |
WSGI/ASGI サーバ |
Gunicorn, Uvicorn, uWSGI, Daphne |
HTTP パース, environ/scope 構築, ワーカー管理, アプリ呼び出し |
アプリケーション |
Django, FastAPI |
ルーティング, ビジネスロジック, ORM, 認証, レスポンス生成 |
重要
本書を通じて追いかけてきた TCP ソケット → HTTP パース → WSGI/ASGI インタフェース → フレームワーク内部という流れは、この4層構造の下から上へ向かう旅でした。
トラブルシューティングにおいて「どの層で問題が起きているか」を特定することが最初のステップです。
502 Bad Gateway: Nginx とアプリケーションサーバの境界で発生
500 Internal Server Error: アプリケーション層で発生
503 Service Unavailable: プロセスマネージャがアプリケーションサーバを起動できていないか、ワーカーが飽和している状態
次節では Gunicorn のアーキテクチャを掘り下げ、プリフォークモデルとワーカー管理の仕組みを追います。
1.5 Python 標準ライブラリの簡易サーバ
1.5.1 何ができるか
Python には標準ライブラリだけで HTTP サーバを起動できる仕組みが複数用意されています。 本書のVol.1「まずは 1 リクエストだけ処理するサーバを作る」〜Vol.1「WSGI の上に何が必要になるのか」で実際にこれらを使ってきましたが、ここで改めて本番環境との対比で位置づけを整理します。
最もシンプルなのは http.server モジュールです。
# コマンド一発で静的ファイルサーバが起動する
python -m http.server 8000
from http.server import HTTPServer, BaseHTTPRequestHandler
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b"Hello from http.server")
server = HTTPServer(("127.0.0.1", 8000), MyHandler)
server.serve_forever()
http.server は TCP ソケットのリッスン、HTTP リクエストのパース、レスポンスの書き出しをすべて担います。
Vol.1「HTTP は何をやりとりしているのか」で socket モジュールから手書きした TCP サーバ、Vol.1「まずは 1 リクエストだけ処理するサーバを作る」で HTTP パースを自作したサーバの延長線上にあり、それらの定型処理を標準ライブラリがまとめたものです。
WSGI アプリケーションを動かすための wsgiref.simple_server も標準ライブラリに含まれています。
from wsgiref.simple_server import make_server
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello from wsgiref"]
server = make_server("127.0.0.1", 8000, application)
server.serve_forever()
Vol.1「WSGI が生まれた背景」で WSGI の仕組みを学んだとき、まさにこの wsgiref.simple_server を使って application(environ, start_response) の動作を確認しました。
Django の runserver コマンドも内部的にこの wsgiref.simple_server を拡張して動作しています(Vol.2「どこまでが Django の責務で、どこからがサーバの責務か」)。
これらの標準ライブラリサーバが提供するものは次の通りです。
TCP ソケットのバインドとリッスン
HTTP/1.1 リクエストの基本的なパース
リクエストハンドラへのディスパッチ
レスポンスの書き出し
(
wsgirefの場合)environ辞書の構築と WSGI インタフェースの実装
Tip
標準ライブラリサーバの教育的価値は、HTTP リクエストがどのようにパースされ、environ がどのように構築され、application がどのように呼ばれるかを、余計な最適化やプロセス管理のコードに邪魔されずに追跡できる点にあります。
本書のVol.1〜Vol.1「WSGI の上に何が必要になるのか」で使ったのはまさにこの「最低限だけが動く」部分です。
1.5.2 何に向かないか
標準ライブラリサーバには、本番環境で必要な機能がほぼすべて欠けています。
図1-4 標準ライブラリサーバと Gunicorn のプロセス構成の対比
標準ライブラリサーバが本番に向かない理由を一つずつ見ていきましょう。
プロセス管理がない
http.server も wsgiref.simple_server も単一プロセスと単一スレッドで動作します(http.server には ThreadingHTTPServer がありますが、スレッド数の制御やワーカー管理はありません)。ワーカープロセスが1つしかないため、そのプロセスがクラッシュすればサービス全体が停止します。
# wsgiref は 1 プロセスで逐次処理
server = make_server("127.0.0.1", 8000, application)
server.serve_forever()
# ↑ このプロセスがクラッシュしたら終わり
# ↑ 同時に 1 リクエストしか処理できない
同時接続の処理能力が極めて限定的
デフォルトではリクエストを逐次処理するため、1つのリクエストが処理中は他のリクエストが待たされます。ブラウザが CSS、JavaScript、画像を並行してリクエストするだけで、ページの表示が著しく遅くなります。
タイムアウト管理が弱い
Gunicorn は --timeout でワーカーが応答しない場合にプロセスをキルし再起動しますが、wsgiref にはそのような仕組みがありません。無限ループやデッドロックが発生したビュー関数がプロセス全体を停止させます。
TLS(HTTPS)のサポートがない
本番環境では HTTPS が事実上必須ですが、標準ライブラリサーバには TLS 終端の機能がなく、証明書の管理も行えません。
パフォーマンスの最適化が施されていない
wsgiref は純粋な Python 実装かつ単一プロセスと逐次処理のため、本番のワークロードでは性能が出ません。
ただし Gunicorn が wsgiref を上回る主因は HTTP パーサーの速さではありません。Gunicorn の同期ワーカーの HTTP パースも純粋な Python 実装であり、その強みは複数ワーカープロセスによる並行処理にあります。
一方 Uvicorn は、C 実装の httptools(llhttp の Python バインディング)を既定で用いるため、パース自体も高速です。
グレースフルシャットダウンとリロードの仕組みがない
コードを更新してサーバを再起動する際、処理中のリクエストが中断されます。Gunicorn は SIGHUP シグナルで新しいワーカーを起動し、古いワーカーが処理中のリクエストを完了してから終了するグレースフルリロードを提供します。
これらの制約を表にまとめると次のようになります。
機能 |
wsgiref / http.server |
Gunicorn / Uvicorn |
|---|---|---|
プロセス管理 |
なし |
マスター + ワーカー |
同時接続処理 |
逐次(または限定的スレッド) |
マルチワーカー / イベントループ |
タイムアウト監視 |
なし |
–timeout でワーカーキル |
クラッシュ時の自動復旧 |
なし |
マスターがワーカーを再生成 |
グレースフルリロード |
なし |
SIGHUP / --reload |
TLS 終端 |
なし |
限定的(通常は Nginx に委譲) |
HTTP パーサー実装 |
Pure Python |
Gunicorn は Pure Python / Uvicorn は C(httptools・llhttp) |
静的ファイル最適化 |
なし |
なし(Nginx に委譲) |
注釈
Django の runserver が wsgiref をベースにしている理由は、開発環境では上記の制約がほとんど問題にならないからです。
開発者1人がブラウザからアクセスする程度であれば、同時接続数もリクエスト頻度も低く、クラッシュしてもすぐに再起動できます。
自動リロード機能の方がはるかに重要であり、Django はファイル監視による自動リロードを runserver に独自に実装しています。
本書の構成上、標準ライブラリサーバはVol.1「HTTP は何をやりとりしているのか」〜Vol.1「WSGI の上に何が必要になるのか」の「内部構造を理解するための道具」として使い、Vol.2「Django を WSGI 視点で見る」以降のフレームワーク内部の解説では「実際のアプリケーションが動く基盤」として Gunicorn / Uvicorn を前提にしてきました。
標準ライブラリサーバで学んだ HTTP パース、environ 構築、application 呼び出しの流れは、Gunicorn や Uvicorn の内部でも同じ構造で動いています。
違いは、その周囲にプロセス管理、タイムアウト監視、パフォーマンス最適化が追加されている点です。
次節では Gunicorn のプリフォークモデルを掘り下げ、マスタープロセスとワーカープロセスの関係を追います。
1.6 Gunicorn
1.6.1 WSGI サーバとしての役割
Gunicorn(Green Unicorn)は Python の WSGI サーバとして最も広く使われているソフトウェアです。 その責務は、前節で整理した「WSGI/ASGI サーバ」の層に位置し、Nginx などのリバースプロキシと Django などのアプリケーションフレームワークの間を橋渡しします。
gunicorn myproject.wsgi:application --bind 127.0.0.1:8000
この1行で Gunicorn は次の処理を実行します。
myproject.wsgiモジュールをインポートしてapplicationオブジェクト(WSGI callable)を取得するTCP ソケットを
127.0.0.1:8000にバインドしてリッスンを開始する接続を受け付けると HTTP リクエストをパースして
environ辞書を構築するapplication(environ, start_response)を呼び出す返されたイテラブルからレスポンスボディを取り出し、ステータスコードとヘッダーと合わせてクライアントに送信する
Tip
この流れは wsgiref.simple_server と本質的に同じです。
Vol.1「WSGI が生まれた背景」で make_server("127.0.0.1", 8000, application) としたのと同じインタフェースで、同じ environ が構築され、同じ application callable が呼ばれます。
違いは、Gunicorn がこの処理の周囲にプロセス管理、タイムアウト監視、パフォーマンス最適化を追加している点です。
1.6.2 pre-fork モデル
Gunicorn のアーキテクチャの核心はプリフォーク(pre-fork)モデルです。
図1-5 Gunicorn のプリフォークモデルにおけるマスターとワーカーの関係
マスタープロセスはリクエストの処理を一切行いません。 その責務はワーカープロセスの管理に限定されます。