02-4. 配置文件範例

Gunicorn 實戰配置範例與最佳實踐

🎯 本章重點

通過實際的配置文件範例,學習如何為不同場景配置 Gunicorn。

⭐ 2025 年新專案建議

結論:Django 3.0+ 新專案,請直接使用 ASGI (範例3)

為什麼?

  • ✅ 性能更好(比 WSGI 快 10-35 倍)
  • ✅ 向後兼容同步代碼(不需要改現有代碼)
  • ✅ 支援 WebSocket、SSE 等現代功能
  • ✅ Django 官方推薦的未來方向
  • ✅ 一個 worker 可處理數千並發

什麼時候才用 WSGI (範例2)?

  • ⚠️ 只有 Django 2.x 遺留系統無法升級時
  • ⚠️ 極少數第三方套件不支援 ASGI(非常罕見)

📊 Workers 數量計算說明

在配置文件中你會看到不同的 workers 計算方式,這裡先統一說明:

multiprocessing.cpu_count() 是什麼?

import multiprocessing

# 自動取得 CPU 核心數
workers = multiprocessing.cpu_count()

# 例如:
# - 4 核 CPU → workers = 4
# - 8 核 CPU → workers = 8
# - 16 核 CPU → workers = 16

不同 Worker 類型的數量公式

Worker 類型Workers 數量公式原因
Sync (WSGI)CPU * 2 + 1需要處理 I/O 等待,需要更多 workers
Uvicorn (ASGI)CPU異步處理,一個 worker 可處理數千並發
GeventCPU * 2 + 1協程模型,需要多個 workers 分散負載
GthreadCPU * 2 + 1多線程,需要更多進程配合

範例對比

# 假設 4 核 CPU

# 範例 2:Sync Worker (WSGI)
workers = multiprocessing.cpu_count() * 2 + 1  # = 9

# 範例 3:Uvicorn Worker (ASGI) ⭐ 推薦
workers = multiprocessing.cpu_count()          # = 4

# 範例 4:大型應用 (ASGI)
workers = multiprocessing.cpu_count() * 2      # = 8

為什麼 ASGI 只需要 CPU 核心數?

  • ASGI (異步) 一個 worker 可以同時處理數千個請求
  • WSGI (同步) 一個 worker 同時只能處理一個請求
  • 所以 ASGI 需要的 workers 更少

詳細講解請參考: 02-1. Workers 數量計算


📂 配置文件的兩種方式

方式一:Python 配置文件 (推薦)

# 使用 Python 配置文件
gunicorn -c gunicorn.conf.py myapp.wsgi:application

優點:

  • 可以使用 Python 語法進行動態配置
  • 可以使用條件判斷、函數等
  • 支持 Hook Functions
  • 配置更靈活

方式二:命令行參數

# 直接使用命令行參數
gunicorn myapp.wsgi:application \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000 \
    --timeout 120

缺點:

  • 配置參數多時不易管理
  • 不支持 Hook Functions
  • 難以進行條件配置

🏗️ 配置文件範例

範例 1:開發環境配置

適用場景:本地開發、快速測試

# gunicorn.dev.conf.py
import multiprocessing

# 基本配置
bind = "127.0.0.1:8000"
workers = 1  # 開發環境只需要一個 worker
worker_class = "sync"  # 使用最簡單的同步 worker
reload = True  # 開啟自動重載(代碼變更時自動重啟)
reload_extra_files = [
    'templates/',
    'static/',
]

# 超時配置
timeout = 300  # 開發時可能需要除錯,設定較長的超時時間
graceful_timeout = 30
keepalive = 5

# 日誌配置
loglevel = "debug"  # 開發環境使用 debug 級別
accesslog = "-"  # 輸出到 stdout
errorlog = "-"   # 輸出到 stderr
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# 其他配置
preload_app = False  # 不預載入,方便除錯

使用方式:

gunicorn -c gunicorn.dev.conf.py myapp.wsgi:application

特點說明:

  • ✅ 單 worker 避免多進程除錯困擾
  • ✅ 自動重載提高開發效率
  • ✅ Debug 日誌級別方便追蹤問題
  • ✅ 較長的超時時間方便除錯
  • ⚠️ 不適合生產環境使用

範例 2:遺留系統生產環境 (WSGI - Django 2.x)

適用場景:Django 2.x 遺留系統、不支援 ASGI 的舊專案

⚠️ 注意:Django 3.0+ 新專案請優先使用範例3 (ASGI)

# gunicorn.prod.conf.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
graceful_timeout = 30
keepalive = 5

# 防止記憶體洩漏
max_requests = 1000
max_requests_jitter = 100

# 日誌配置
loglevel = "info"
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
logconfig_dict = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'generic': {
            'format': '%(asctime)s [%(process)d] [%(levelname)s] %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
    },
    'handlers': {
        'error_file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'generic',
            'filename': '/var/log/gunicorn/error.log',
            'maxBytes': 100 * 1024 * 1024,  # 100MB
            'backupCount': 10,
        },
    },
    'root': {
        'level': 'INFO',
        'handlers': ['error_file'],
    },
}

# 安全配置
limit_request_line = 4096
limit_request_fields = 100
limit_request_field_size = 8190

# 性能優化
preload_app = True
worker_tmp_dir = "/dev/shm"

# Hooks
def post_fork(server, worker):
    """Worker fork 後關閉所有資料庫連接"""
    from django import db
    db.connections.close_all()
    server.log.info(f"Worker {worker.pid} spawned")

def worker_exit(server, worker):
    """Worker 退出時記錄"""
    server.log.info(f"Worker {worker.pid} exited")

def on_starting(server):
    """Server 啟動時的初始化"""
    server.log.info("Gunicorn server is starting")

def on_reload(server):
    """Server reload 時的處理"""
    server.log.info("Gunicorn server is reloading")

使用方式:

# 建立日誌目錄
sudo mkdir -p /var/log/gunicorn
sudo chown -R $USER:$USER /var/log/gunicorn

# 啟動服務
gunicorn -c gunicorn.prod.conf.py myapp.wsgi:application

適合場景:

  • ⚠️ Django 2.x 遺留系統(無法升級至 3.0+)
  • 傳統的同步請求-響應模式
  • 不需要 WebSocket 支援
  • 流量在 100-1000 QPS 之間

不推薦的情況:

  • ❌ Django 3.0+ 新專案(請使用範例3)
  • ❌ 需要高並發處理(請使用範例3)

範例 3:現代化生產環境 (ASGI + Async) ⭐ 推薦

適用場景:Django 3.0+ 新專案、高並發、WebSocket、現代應用

2025 年推薦配置:適用於小型到中型生產環境

# gunicorn.async.conf.py
import multiprocessing
import os

# 基本配置
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count()
worker_class = "uvicorn.workers.UvicornWorker"  # 使用 Uvicorn Worker
worker_connections = 1000

# 超時配置
timeout = 120  # ASGI 應用建議設定較長的超時時間
graceful_timeout = 30
keepalive = 20  # 較長的 keepalive 適合持久連接

# 防止記憶體洩漏
max_requests = 2000
max_requests_jitter = 200

# 日誌配置
loglevel = "info"
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# 安全配置
limit_request_line = 8190  # WebSocket 可能需要較長的請求行
limit_request_fields = 100
limit_request_field_size = 8190

# 性能優化
preload_app = True
worker_tmp_dir = "/dev/shm"

# Uvicorn 特定配置(通過環境變數)
raw_env = [
    'WEB_CONCURRENCY=4',
]

# Hooks
def post_fork(server, worker):
    """Worker fork 後的初始化"""
    from django import db
    db.connections.close_all()
    server.log.info(f"Async Worker {worker.pid} spawned")

def worker_exit(server, worker):
    """Worker 退出時的清理"""
    server.log.info(f"Async Worker {worker.pid} exited")

def on_starting(server):
    """Server 啟動時初始化 Redis、Cache 等"""
    server.log.info("ASGI Gunicorn server is starting")
    # 可以在這裡初始化全域資源
    # 例如:預熱 Redis 連接池

def when_ready(server):
    """Server 準備就緒"""
    server.log.info("Server is ready. Spawning workers")

def pre_request(worker, req):
    """請求前記錄(開發/除錯用)"""
    worker.log.debug(f"{req.method} {req.path}")

def post_request(worker, req, environ, resp):
    """請求後記錄(可用於監控)"""
    # 可以在這裡記錄請求時間、狀態碼等
    pass

使用方式:

# 安裝依賴
pip install uvicorn[standard] gunicorn

# 啟動服務
gunicorn -c gunicorn.async.conf.py myapp.asgi:application

適合場景:

  • ✅ Django 3.0+ 所有新專案(優先推薦)
  • ✅ 小型到中型生產環境(< 5000 QPS)
  • I/O 密集型應用(API、微服務)
  • 需要 WebSocket 支援
  • 需要長連接的應用

優點:

  • 高並發處理能力(比 sync worker 快 10-35 倍)
  • 向後兼容同步代碼
  • 支援異步和 WebSocket
  • 記憶體使用更少

範例 4:大型高並發環境 (混合型)

適用場景:超大型應用、複雜業務邏輯、混合 CPU 和 I/O 密集型

# gunicorn.enterprise.conf.py
import multiprocessing
import os

# === 基本配置 ===
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 2000  # 增加並發連接數

# === 超時配置 ===
timeout = 180
graceful_timeout = 60
keepalive = 30

# === 防止記憶體洩漏 ===
max_requests = 5000
max_requests_jitter = 500

# === 連接配置 ===
backlog = 2048  # 增加連接隊列

# === 日誌配置 ===
loglevel = "warning"  # 生產環境使用 warning 減少日誌量
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"

# 結構化日誌格式(便於解析)
access_log_format = json.dumps({
    'remote_ip': '%(h)s',
    'request_id': '%({X-Request-Id}i)s',
    'response_code': '%(s)s',
    'request_method': '%(m)s',
    'request_path': '%(U)s',
    'request_querystring': '%(q)s',
    'request_time': '%(D)s',
    'response_length': '%(B)s',
})

# 進階日誌配置(整合 ELK)
logconfig_dict = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'json': {
            '()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
            'format': '%(asctime)s %(name)s %(levelname)s %(message)s',
        },
    },
    'handlers': {
        'error_file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'json',
            'filename': '/var/log/gunicorn/error.log',
            'maxBytes': 500 * 1024 * 1024,  # 500MB
            'backupCount': 30,
        },
        'slow_request_file': {
            'class': 'logging.handlers.RotatingFileHandler',
            'formatter': 'json',
            'filename': '/var/log/gunicorn/slow.log',
            'maxBytes': 200 * 1024 * 1024,  # 200MB
            'backupCount': 10,
        },
    },
    'root': {
        'level': 'WARNING',
        'handlers': ['error_file'],
    },
    'loggers': {
        'slow': {
            'level': 'INFO',
            'handlers': ['slow_request_file'],
            'propagate': False,
        },
    },
}

# === 安全配置 ===
limit_request_line = 8190
limit_request_fields = 150
limit_request_field_size = 8190

# === 性能優化 ===
preload_app = True
worker_tmp_dir = "/dev/shm"

# === Prometheus 監控配置 ===
statsd_host = "localhost:9125"
statsd_prefix = "gunicorn"

# === Hooks ===
import time
import logging

slow_logger = logging.getLogger('slow')

def post_fork(server, worker):
    """Worker 初始化"""
    from django import db
    from django.core.cache import cache

    # 關閉所有資料庫連接
    db.connections.close_all()

    # 測試 cache 連接
    try:
        cache.get('health_check')
    except Exception as e:
        server.log.error(f"Cache connection failed: {e}")

    server.log.info(f"Worker {worker.pid} ready")

def pre_request(worker, req):
    """請求開始時記錄時間"""
    worker._request_start_time = time.time()

def post_request(worker, req, environ, resp):
    """請求結束時檢查慢請求"""
    request_time = time.time() - worker._request_start_time

    # 記錄超過 1 秒的慢請求
    if request_time > 1.0:
        slow_logger.info({
            'request_time': request_time,
            'method': req.method,
            'path': req.path,
            'status': resp.status_code,
            'worker_pid': worker.pid,
        })

def worker_exit(server, worker):
    """Worker 退出時清理"""
    server.log.info(f"Worker {worker.pid} exiting")

def worker_abort(worker):
    """Worker 被強制終止時"""
    worker.log.warning(f"Worker {worker.pid} aborted (timeout or crash)")

def on_starting(server):
    """Server 啟動時的全域初始化"""
    server.log.info("Enterprise Gunicorn server starting")

    # 預熱 Redis 連接
    # 初始化監控連接
    # 等等...

def when_ready(server):
    """Server 準備好接收請求"""
    server.log.info("Server is ready for production traffic")

def on_reload(server):
    """熱重載時"""
    server.log.info("Server is reloading configuration")
    # 可以在這裡重新載入配置、更新快取等

def on_exit(server):
    """Server 關閉時清理"""
    server.log.info("Server is shutting down")
    # 清理全域資源

使用方式:

# 安裝額外依賴
pip install uvicorn[standard] gunicorn python-json-logger

# 建立必要目錄
sudo mkdir -p /var/log/gunicorn
sudo chown -R $USER:$USER /var/log/gunicorn

# 啟動服務
gunicorn -c gunicorn.enterprise.conf.py myapp.asgi:application

適合場景:

  • 超大型應用(10000+ QPS)
  • 需要詳細監控和日誌
  • 複雜的業務邏輯
  • 需要慢請求追蹤
  • 整合 ELK、Prometheus 等監控系統

特點:

  • ✅ 結構化日誌(JSON 格式)
  • ✅ 慢請求追蹤
  • ✅ 完整的 Hook Functions
  • ✅ 監控整合
  • ✅ 高並發支援

🔧 Systemd 整合範例

在生產環境中,通常使用 Systemd 管理 Gunicorn 服務。

Systemd Service 文件

# /etc/systemd/system/myapp-gunicorn.service
[Unit]
Description=Gunicorn daemon for Django myapp
After=network.target

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/gunicorn -c /opt/myapp/gunicorn.conf.py myapp.asgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=60
PrivateTmp=true
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

使用方式:

# 重新載入 systemd
sudo systemctl daemon-reload

# 啟動服務
sudo systemctl start myapp-gunicorn

# 設定開機自動啟動
sudo systemctl enable myapp-gunicorn

# 查看狀態
sudo systemctl status myapp-gunicorn

# 查看日誌
sudo journalctl -u myapp-gunicorn -f

# 熱重載
sudo systemctl reload myapp-gunicorn

# 重啟服務
sudo systemctl restart myapp-gunicorn

🐳 Docker 整合範例

Dockerfile

FROM python:3.11-slim

WORKDIR /app

# 安裝依賴
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用代碼
COPY . .

# 建立日誌目錄
RUN mkdir -p /var/log/gunicorn

# 暴露端口
EXPOSE 8000

# 啟動 Gunicorn
CMD ["gunicorn", "-c", "gunicorn.conf.py", "myapp.asgi:application"]

docker-compose.yml

version: '3.8'

services:
  web:
    build: .
    command: gunicorn -c gunicorn.conf.py myapp.asgi:application
    volumes:
      - .:/app
      - logs:/var/log/gunicorn
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SETTINGS_MODULE=myapp.settings.production
    depends_on:
      - db
      - redis
    restart: unless-stopped

  db:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=myapp
      - POSTGRES_PASSWORD=secret
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web
    restart: unless-stopped

volumes:
  postgres_data:
  logs:

📊 配置選擇決策樹

是新專案嗎?
├─ 是 → 使用 範例3 (ASGI + Uvicorn Worker)
└─ 否 → Django 版本?
    ├─ Django 3.0+ → 需要 WebSocket?
    │   ├─ 是 → 範例3 (ASGI)
    │   └─ 否 → 流量大小?
    │       ├─ < 1000 QPS → 範例2 (WSGI)
    │       └─ > 1000 QPS → 範例3 (ASGI) 或 範例4
    └─ Django < 3.0 → 範例2 (WSGI)

生產環境規模?
├─ 小型 (< 1000 QPS)
│   ├─ Django 3.0+ → 範例3 (ASGI,推薦)
│   └─ Django 2.x → 範例2 (WSGI)
├─ 中型 (1000-5000 QPS) → 範例3 (ASGI)
└─ 大型 (> 5000 QPS) → 範例4 (ASGI + 監控)

🎯 總結

快速選擇指南

場景推薦配置Worker 類型Workers 數量適用場景
本地開發範例1sync1快速開發、除錯
遺留系統 (Django 2.x)範例2syncCPU * 2 + 1舊專案、不支援 ASGI
現代生產(推薦)範例3uvicornCPUDjango 3.0+、新專案
超大型生產範例4uvicornCPU * 2企業級、複雜監控

配置文件管理建議

  1. 環境分離

    • gunicorn.dev.conf.py - 開發環境
    • gunicorn.staging.conf.py - 測試環境
    • gunicorn.prod.conf.py - 生產環境
  2. 版本控制

    • 配置文件應納入版本控制
    • 敏感信息使用環境變數
    • 建立配置範本文件
  3. 監控和日誌

    • 使用結構化日誌(JSON)
    • 整合 ELK 或 Prometheus
    • 設定日誌輪轉避免磁碟滿
  4. 持續優化

    • 定期檢查慢請求日誌
    • 根據實際流量調整 workers 數量
    • 監控記憶體使用,調整 max_requests

💡 面試常見問題

Q1:如何選擇合適的配置文件?

答: 根據以下因素選擇:

  1. 應用類型

    • Django 3.0+ 新專案 → ASGI (範例3)
    • Django 2.x 遺留系統 → WSGI (範例2)
  2. 流量規模

    • 開發環境 → 範例1
    • 小型生產 (< 1000 QPS)
      • Django 3.0+ → 範例3 (推薦)
      • Django 2.x → 範例2
    • 中型生產 (1000-5000 QPS) → 範例3
    • 大型生產 (> 5000 QPS) → 範例4
  3. 功能需求

    • 需要 WebSocket → 必須使用 ASGI (範例3/4)
    • 傳統 HTTP + Django 3.0+ → 優先 ASGI (範例3,向後兼容)
    • 傳統 HTTP + Django 2.x → WSGI (範例2)
  4. 維運需求

    • 需要詳細監控、慢請求追蹤 → 範例4
    • 簡單部署 (Django 3.0+) → 範例3 (推薦)
    • 簡單部署 (Django 2.x) → 範例2

Q2:preload_app 什麼時候該開啟?

答:

開啟的情況(preload_app = True

  • ✅ 生產環境(節省記憶體)
  • ✅ 應用啟動慢(提高啟動速度)
  • ✅ 有大量共享資料
  • ✅ 配合 Copy-on-Write 優化記憶體

不開啟的情況(preload_app = False

  • ✅ 開發環境(方便除錯)
  • ✅ 應用有全域狀態且不線程安全
  • ✅ 需要每個 worker 獨立初始化

注意事項

  • 開啟時必須在 post_fork Hook 中關閉資料庫連接
  • 開啟時全域變數會在多個 worker 間共享(需要注意線程安全)

Q3:如何驗證配置文件是否正確?

答:

# 1. 檢查語法錯誤
python -c "import gunicorn.conf"

# 2. Dry-run 模式(檢查配置但不啟動)
gunicorn --check-config -c gunicorn.conf.py myapp.asgi:application

# 3. 查看實際載入的配置
gunicorn -c gunicorn.conf.py myapp.asgi:application --print-config

# 4. 短時間測試啟動
timeout 10 gunicorn -c gunicorn.conf.py myapp.asgi:application

Q4:生產環境最佳配置是什麼?

答: 2025 年推薦的生產環境配置:

# 最佳實踐配置
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count()  # CPU 核心數
worker_class = "uvicorn.workers.UvicornWorker"  # 現代 ASGI
worker_connections = 1000
timeout = 120
graceful_timeout = 30
keepalive = 20
max_requests = 2000
max_requests_jitter = 200
preload_app = True
worker_tmp_dir = "/dev/shm"

關鍵要點

  • 使用 Uvicorn Worker(支援 async、WebSocket)
  • Workers 數量 = CPU 核心數
  • 啟用 preload_app 節省記憶體
  • 使用 /dev/shm 提高性能
  • 設定 max_requests 防止記憶體洩漏
  • 適當的 timeout 和 graceful_timeout

下一章:03-1. 案例:I/O 密集型應用

0%