Gunicorn

Pythonアプリケーション用のシンプルで使いやすいWSGI HTTPサーバー。最も文書化されたPython Webサーバー。簡単な設定と管理が特徴。

アプリケーションサーバーPythonWSGIHTTPサーバーFlaskDjangoウェブサーバー

アプリケーションサーバー

Gunicorn

概要

Gunicorn(Green Unicorn)は、Pythonアプリケーション用のシンプルで使いやすいWSGI HTTPサーバーです。最も文書化されたPython Webサーバーとして、簡単な設定と管理が特徴的です。最も広く使用されるPython Webサーバーであり、シンプルさを重視する場合に選択されることが多く、uWSGIより劣るパフォーマンスながら設定の簡単さで人気があります。

詳細

Gunicornは2010年にリリースされ、PythonのWSGI(Web Server Gateway Interface)アプリケーションを本番環境で実行するためのHTTPサーバーとして広く採用されています。Rubyの Unicorn サーバーからインスパイアされ、プリフォーク・ワーカーモデルを採用しています。Django、Flask、FastAPIなど主要なPythonウェブフレームワークと互換性があり、本番環境での安定性に定評があります。

主要な技術的特徴

  • プリフォーク・ワーカーモデル: マスタープロセスが複数のワーカープロセスを管理
  • WSGI準拠: Python標準のWebアプリケーションインターフェース
  • 柔軟な設定: コマンドライン、設定ファイル、環境変数による設定
  • 複数のワーカータイプ: 同期・非同期ワーカーサポート
  • グレースフルリスタート: 無停止でのアプリケーション更新
  • シンプルな監視: プロセス監視とシグナルハンドリング

用途

  • Django、Flask、FastAPIアプリケーション
  • REST API サーバー
  • マイクロサービス
  • Webアプリケーション本番環境
  • コンテナ化アプリケーション
  • 負荷分散環境のバックエンド

メリット・デメリット

メリット

  • 設定の簡単さ: 最小限の設定で稼働可能
  • 豊富なドキュメント: 詳細な公式ドキュメントとコミュニティサポート
  • 安定性: 長期間の本番運用実績
  • 柔軟性: 多様な設定オプションとワーカータイプ
  • 監視の容易さ: シンプルなプロセス構造
  • フレームワーク互換性: 主要Pythonフレームワークとの高い互換性

デメリット

  • パフォーマンス: uWSGIと比較して低いパフォーマンス
  • メモリ使用量: プロセスベースによる高いメモリ消費
  • スケーラビリティ: 大規模環境での制限
  • 静的ファイル配信: 静的ファイル配信には向かない
  • Windows非対応: Windows環境での使用不可

インストール・基本設定

前提条件

# Python バージョン確認(3.7以上推奨)
python --version
python3 --version

# pip 確認
pip --version
pip3 --version

インストール方法

pip インストール

# 基本インストール
pip install gunicorn

# 特定のバージョン
pip install gunicorn==21.2.0

# 非同期ワーカー用依存関係
pip install gunicorn[eventlet]  # Eventlet ワーカー
pip install gunicorn[gevent]    # Gevent ワーカー

# または個別インストール
pip install eventlet
pip install gevent

仮想環境での設定

# 仮想環境作成
python -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate   # Windows

# Gunicorn インストール
pip install gunicorn

# requirements.txt への追加
echo "gunicorn==21.2.0" >> requirements.txt

基本的なアプリケーション起動

最小限のWSGIアプリケーション

# app.py
def application(environ, start_response):
    """基本的なWSGIアプリケーション"""
    data = b'Hello, World!\n'
    status = '200 OK'
    response_headers = [
        ('Content-type', 'text/plain'),
        ('Content-Length', str(len(data)))
    ]
    start_response(status, response_headers)
    return iter([data])

Flaskアプリケーション例

# flask_app.py
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def hello():
    return jsonify({'message': 'Hello from Gunicorn!'})

@app.route('/health')
def health():
    return jsonify({'status': 'healthy'}), 200

if __name__ == '__main__':
    app.run(debug=True)

基本起動コマンド

# 基本起動
gunicorn app:application

# Flask アプリケーション
gunicorn flask_app:app

# ワーカー数指定
gunicorn --workers 4 flask_app:app

# ホストとポート指定
gunicorn --bind 0.0.0.0:8000 --workers 4 flask_app:app

設定とカスタマイズ

コマンドライン設定

# 包括的な設定例
gunicorn flask_app:app \
    --bind 0.0.0.0:8000 \
    --workers 4 \
    --worker-class sync \
    --worker-connections 1000 \
    --timeout 30 \
    --keepalive 2 \
    --max-requests 1000 \
    --max-requests-jitter 100 \
    --preload \
    --daemon \
    --pid /var/run/gunicorn.pid \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log \
    --log-level info

設定ファイル(gunicorn_config.py)

# gunicorn_config.py
import multiprocessing
import os

# サーバー設定
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
timeout = 30
keepalive = 2

# パフォーマンス設定
max_requests = 1000
max_requests_jitter = 100
preload_app = True

# ログ設定
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# プロセス設定
user = "www-data"
group = "www-data"
pid = "/var/run/gunicorn.pid"
daemon = False

# セキュリティ設定
limit_request_line = 4094
limit_request_fields = 100
limit_request_field_size = 8190

# ワーカー設定
worker_tmp_dir = "/dev/shm"  # RAM ディスク使用

# 環境変数設定
raw_env = [
    'DJANGO_SETTINGS_MODULE=myproject.settings.production',
    'SECRET_KEY=your-secret-key'
]

# サーバーフック
def when_ready(server):
    server.log.info("Server is ready. Spawning workers")

def worker_int(worker):
    worker.log.info("worker received INT or QUIT signal")

def pre_fork(server, worker):
    server.log.info("Worker spawned (pid: %s)", worker.pid)

def post_fork(server, worker):
    server.log.info("Worker spawned (pid: %s)", worker.pid)

環境変数による設定

# 環境変数設定
export GUNICORN_CMD_ARGS="--bind=0.0.0.0:8000 --workers=4"
gunicorn flask_app:app

# .env ファイル
echo "GUNICORN_CMD_ARGS=--bind=0.0.0.0:8000 --workers=4" > .env

# Docker環境での使用
docker run -e GUNICORN_CMD_ARGS="--bind=0.0.0.0:8000 --workers=4" myapp

ワーカータイプとパフォーマンス

同期ワーカー(sync)

# 基本的な同期ワーカー設定
bind = "0.0.0.0:8000"
workers = 4
worker_class = "sync"
worker_connections = 1000
timeout = 30

非同期ワーカー(gevent)

# Gevent ワーカー設定
bind = "0.0.0.0:8000"
workers = 2
worker_class = "gevent"
worker_connections = 1000
timeout = 30

# Gevent使用時の追加設定
import gevent.monkey
gevent.monkey.patch_all()

非同期ワーカー(eventlet)

# Eventlet ワーカー設定
bind = "0.0.0.0:8000"
workers = 2
worker_class = "eventlet"
worker_connections = 1000
timeout = 30

スレッドワーカー(gthread)

# スレッドワーカー設定
bind = "0.0.0.0:8000"
workers = 2
worker_class = "gthread"
threads = 4
worker_connections = 1000
timeout = 30

パフォーマンス最適化

# 高性能設定
import multiprocessing
import os

# CPU最適化
workers = min(multiprocessing.cpu_count() * 2 + 1, 8)
worker_class = "gevent"
worker_connections = 2000

# メモリ最適化
max_requests = 1000
max_requests_jitter = 100
preload_app = True

# システム最適化
worker_tmp_dir = "/dev/shm"  # RAM ディスク
keepalive = 2
timeout = 60

# 接続最適化
backlog = 2048

本番環境設定

Systemd サービス設定

# /etc/systemd/system/gunicorn.service
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
RuntimeDirectory=gunicorn
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          myapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Systemd ソケット設定

# /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target

Docker設定

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# システム依存関係
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Python依存関係
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# アプリケーションコピー
COPY . .

# 非rootユーザー作成
RUN useradd --create-home --shell /bin/bash app
RUN chown -R app:app /app
USER app

EXPOSE 8000

# ヘルスチェック
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

CMD ["gunicorn", "--config", "gunicorn_config.py", "app:app"]

docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SETTINGS_MODULE=myproject.settings.production
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/ssl
    depends_on:
      - web
    restart: unless-stopped

Nginx との連携

Nginx プロキシ設定

# /etc/nginx/sites-available/myapp
upstream app_server {
    server unix:/run/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com www.example.com;
    
    client_max_body_size 4G;
    keepalive_timeout 5;

    # 静的ファイル配信
    location /static/ {
        alias /var/www/myapp/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /var/www/myapp/media/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # アプリケーション
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_buffering off;
        proxy_pass http://app_server;
    }

    # エラーページ
    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/myapp/static/;
    }
}

ロードバランシング設定

upstream app_servers {
    server 127.0.0.1:8000 weight=1 fail_timeout=0;
    server 127.0.0.1:8001 weight=1 fail_timeout=0;
    server 127.0.0.1:8002 weight=1 fail_timeout=0;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_servers;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
}

監視・ログ・トラブルシューティング

ログ設定

# 詳細ログ設定
import logging
import sys

# ログフォーマット
log_format = '%(asctime)s [%(process)d] [%(levelname)s] %(message)s'
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s %(p)s'

# ログファイル
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"

# コンソール出力
if "--log-file" not in sys.argv:
    accesslog = "-"  # stdout
    errorlog = "-"   # stderr

統計・監視設定

# gunicorn_config.py での統計設定
import os

# StatsD設定
statsd_host = os.environ.get('STATSD_HOST', 'localhost:8125')
statsd_prefix = 'myapp.gunicorn'

# プロメテウス連携
def child_exit(server, worker):
    """ワーカー終了時の処理"""
    server.log.info(f"Worker {worker.pid} exited")

def when_ready(server):
    """サーバー準備完了時の処理"""
    server.log.info("Server is ready. Spawning workers")
    # カスタム監視ログ
    server.log.info(f"Workers: {server.cfg.workers}")
    server.log.info(f"Worker class: {server.cfg.worker_class}")

プロセス管理

# プロセス状況確認
ps aux | grep gunicorn

# シグナル送信
kill -HUP <master_pid>    # 設定リロード
kill -TTIN <master_pid>   # ワーカー追加
kill -TTOU <master_pid>   # ワーカー削除
kill -USR1 <master_pid>   # ログローテーション
kill -TERM <master_pid>   # グレースフル停止
kill -QUIT <master_pid>   # 即座停止

# PIDファイル使用
kill -HUP $(cat /var/run/gunicorn.pid)

ヘルスチェック

# health_check.py
#!/usr/bin/env python3
import sys
import requests
import time

def health_check():
    try:
        response = requests.get('http://localhost:8000/health', timeout=5)
        if response.status_code == 200:
            print("OK: Application is healthy")
            return 0
        else:
            print(f"ERROR: Health check failed with status {response.status_code}")
            return 1
    except requests.exceptions.RequestException as e:
        print(f"ERROR: Health check failed: {e}")
        return 1

if __name__ == '__main__':
    sys.exit(health_check())

自動化スクリプト

#!/bin/bash
# deploy.sh

set -e

APP_DIR="/var/www/myapp"
VENV_DIR="$APP_DIR/venv"
PID_FILE="/var/run/gunicorn.pid"

echo "Starting deployment..."

# 仮想環境activate
source $VENV_DIR/bin/activate

# 依存関係更新
pip install -r requirements.txt

# データベースマイグレーション
python manage.py migrate

# 静的ファイル収集
python manage.py collectstatic --noinput

# Gunicorn グレースフルリスタート
if [ -f $PID_FILE ]; then
    echo "Reloading Gunicorn..."
    kill -HUP $(cat $PID_FILE)
else
    echo "Starting Gunicorn..."
    systemctl start gunicorn
fi

echo "Deployment completed successfully"

参考ページ