Skip to content

Gunicorn 原生 ASGI Worker

Gunicorn 25.1.0 起內建穩定的原生 ASGI Worker,讓 FastAPI、Starlette 等 async 框架不再需要依賴 Uvicorn 即可生產部署。

概述

長期以來,在 Kubernetes 或 VM 上部署 FastAPI 應用的標準做法是 Gunicorn + UvicornWorker:Gunicorn 負責 process management(multi-process、graceful reload、signal handling),Uvicorn 作為 worker class 處理 ASGI 協定。這個組合運作良好,但需要同時維護兩個套件,且版本相容性偶爾造成困擾。

Gunicorn 25.0.0(2025)以 Beta 引入原生 asyncio ASGI Worker;25.1.0(2026-02-13)正式升級為 stable。現在只需要 gunicorn 單一套件,指定 --worker-class asgi 即可完整執行 ASGI 應用程式。

原生 ASGI Worker 最大的優勢是「零學習成本」:process management、logging、graceful reload、signal handling 全部沿用 WSGI 時代的行為,現有的 gunicorn.conf.py 只需改一個參數即可遷移。

核心內容

功能矩陣

原生 ASGI Worker 支援的協定與特性:

能力說明
HTTP/1.1含 keepalive 連線
HTTP/2需搭配 SSL 與 h2 套件
WebSocket內建支援,零額外設定
Lifespan protocol支援 startup / shutdown hooks
Fast HTTP parser可選安裝 gunicorn[fast](基於 picohttpparser + SIMD)
uvloop可選用,預設 auto(有 uvloop 就自動啟用)

與其他 ASGI Server 的比較:

特性Gunicorn ASGIUvicornHypercorn
Process management內建需外部管理內建
HTTP/2✔️✖️✔️
WebSocket✔️✔️✔️
Lifespan✔️✔️✔️

從 Uvicorn Worker 遷移

遷移只需改一個參數:

bash
# Before
gunicorn main:app -k uvicorn.workers.UvicornWorker --workers 4 --bind 0.0.0.0:8000

# After
gunicorn main:app --worker-class asgi --workers 4 --bind 0.0.0.0:8000

gunicorn.conf.py 同樣只需一行更改:

python
# Before
worker_class = "uvicorn.workers.UvicornWorker"

# After
worker_class = "asgi"

uvloop 行為: 預設 asgi_loop = "auto",有 uvloop 就自動使用,行為與 Uvicorn 一致,不需額外設定。若需明確控制:

bash
gunicorn main:app --worker-class asgi --asgi-loop uvloop   # 強制使用 uvloop
gunicorn main:app --worker-class asgi --asgi-loop asyncio  # 強制使用 asyncio

Lifespan 行為: 預設 asgi_lifespan = "auto",自動偵測應用是否支援 lifespan。FastAPI / Starlette 的 lifespan handler 無需修改。

遷移後可從 requirements.txt 移除的依賴:

uvicorn
httptools   # 除非其他地方有用到
websockets  # 除非其他地方有用到

改用效能更高的原生 C 解析器:

bash
pip install gunicorn[fast]   # 安裝 gunicorn_h1c(picohttpparser + SIMD)

注意事項與已知限制

  • --threads 無效:ASGI Worker 是 async 架構,並行度由 --worker-connections 控制,而非 threads
  • Parser 錯誤回應:部分 malformed request 的錯誤回應可能不符合預期,建議在 staging 環境測試
  • uWSGI protocol 不支援 WebSocket:若透過 nginx uwsgi_pass 與 Gunicorn 溝通,WebSocket 端點需改用 HTTP proxy 模式

Production 設定建議

gunicorn.conf.py 推薦設定:

python
# Worker
worker_class = "asgi"
workers = 4                    # 通常 = nproc(CPU 核心數)
worker_connections = 1000      # 每個 worker 的最大並行連線數

# Timeouts
keepalive = 5
timeout = 120
graceful_timeout = 30

# Performance
asgi_loop = "auto"             # 有 uvloop 就用
asgi_lifespan = "auto"         # 自動偵測 lifespan 支援

效能調校原則:

場景建議
CPU-bound(計算密集)增加 --workers,搭配 gunicorn[fast]
I/O-bound(DB/API 呼叫密集)維持 workers = nproc,增加 --worker-connections
大量 WebSocket 長連線顯著增加 --worker-connections(如 5000+)
追求極致 throughput安裝 uvloop + gunicorn[fast]

框架相容性

官方測試結果(25.x 系列),整體通過率 438/444(98%):

框架HTTP ScopeHTTP MessagesWebSocketLifespanStreaming總計
FastAPI19/1918/1919/198/89/973/74
Django + Channels19/1918/1919/198/89/973/74
Starlette19/1918/1919/198/89/973/74
Quart19/1918/1919/198/89/973/74
Litestar19/1918/1919/198/89/973/74
BlackSheep19/1918/1919/198/89/973/74

Nginx 整合

HTTP Proxy 模式(推薦,支援 WebSocket):

nginx
upstream gunicorn {
    server 127.0.0.1:8000;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://gunicorn;
        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;
    }

    # WebSocket endpoint
    location /ws {
        proxy_pass http://gunicorn;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

uWSGI Protocol 模式(更高效能,但不支援 WebSocket):

bash
# Gunicorn 端
gunicorn main:app --worker-class asgi --protocol uwsgi --bind 127.0.0.1:8000
nginx
# Nginx 端
location / {
    uwsgi_pass gunicorn;
    include uwsgi_params;
}

Docker 部署

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["gunicorn", "main:app", \
     "--worker-class", "asgi", \
     "--workers", "4", \
     "--worker-connections", "1000", \
     "--bind", "0.0.0.0:8000"]

子路徑部署(Root Path)

若應用掛載在 reverse proxy 的子路徑下:

bash
gunicorn main:app --worker-class asgi --root-path /api

等同 WSGI 的 SCRIPT_NAME,框架會自動調整 OpenAPI docs 路徑等行為。

Troubleshooting

症狀原因解法
worker_class = "asgi" 找不到gunicorn 版本 < 25.1.0升級 gunicorn>=25.1.0
FastAPI lifespan 沒執行缺少 asgi_lifespan 設定加入 asgi_lifespan = "auto"
Lifespan startup failedlifespan handler 有未捕捉的 exception暫時設 asgi_lifespan = "off" 繞過,修正後改回 "auto"
Loguru 沒攔截 gunicorn 錯誤日誌INTERCEPT_LOGGERS 未包含 gunicorn加入 "gunicorn""gunicorn.error"
StatsD 指標沒送出STATSD_HOST 格式錯誤或接收端未啟動確認格式 host:port;確認接收端監聽中
高併發拒絕連線worker-connections 不足增加 worker_connections = 2000workers = 8
高負載回應緩慢預設使用 asyncio安裝 uvloop,設定 asgi_loop = "uvloop"

關鍵要點

  • Gunicorn 25.1.0+ 內建穩定 ASGI Worker,只需 --worker-class asgi 一個參數
  • 從 Uvicorn Worker 遷移極低成本:只改 worker_class 一行,其他設定不變
  • asgi_lifespan = "auto" 是確保 FastAPI lifespan(startup/shutdown)正常觸發的必要設定
  • accesslog 目錄需在 Dockerfile 中 mkdir -p,否則啟動時報錯
  • --threads 對 ASGI Worker 無效,並行度改用 --worker-connections
  • 安裝 gunicorn[fast] + uvloop 可獲得最高效能
  • WebSocket 端點必須使用 HTTP proxy 模式,不支援 uWSGI protocol
  • Kubernetes 的 command 陣列不需分 args,可統一一行:["uv", "run", "gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]

實際應用

對於已在 Kubernetes 上以 Gunicorn + UvicornWorker 運行的 FastAPI 服務,Gunicorn 25.1.0 提供了無痛遷移路徑:修改 gunicorn.conf.py、移除 uvicorn 依賴、安裝 gunicorn[fast],在 staging 環境驗證 lifespan 與 WebSocket 行為後即可上線。對於全新 Python 微服務,原生 ASGI Worker 是統一 WSGI/ASGI 技術棧的最佳選擇。

若需要採集 Gunicorn 運行指標(請求數、回應時間、Worker 數量),可搭配 Prometheus StatsD Exporter 部署 的 Sidecar 模式,在 gunicorn.conf.py 設定 statsd_host = "127.0.0.1:9125" 即可自動推送指標。

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

項目
Gunicorn 最低版本25.1.0
Bind 位址0.0.0.0:8000
Workers2(建議依 CPU 核心數 × 2 + 1 調整)
Timeout120s
Graceful Timeout30s
Access Log 路徑logs/access.log
STATSD_HOST 格式host:port(例如 10.30.196.60:9125

依賴套件(pyproject.toml)

toml
# 移除
"uvicorn>=0.x.x"

# 新增
"gunicorn>=25.1.0"
"uvloop>=0.17.0"   # 選用:高效能 event loop

gunicorn.conf.py 完整設定(FastAPI)

python
import os

# === Core ===
worker_class = "asgi"          # Gunicorn 25.1.0+ 原生 ASGI worker
workers = 2                    # 建議:CPU 核心數 * 2 + 1,視記憶體調整
worker_connections = 1000
bind = "0.0.0.0:8000"

# === Timeout ===
timeout = 120                  # 單一請求最長處理時間(秒)
graceful_timeout = 30          # 關閉前等待 in-flight 請求完成的時間
keepalive = 5

# === ASGI ===
asgi_loop = "auto"             # 優先使用 uvloop(若有安裝),否則用 asyncio
asgi_lifespan = "auto"         # 自動偵測並執行 FastAPI lifespan context manager

# === Logging ===
accesslog = "logs/access.log"  # 需確保目錄存在(Dockerfile 中 mkdir -p)

# === Metrics(選用,搭配 StatsD)===
statsd_host = os.environ.get("STATSD_HOST", "")   # 例如 "10.x.x.x:9125"
statsd_prefix = os.environ.get("APP_NAME", "")    # 例如 "my-app"

Dockerfile 遷移

dockerfile
# Before
COPY log_config.json ./log_config.json
RUN mkdir -p /app/logs
EXPOSE 8000
CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--log-config", "log_config.json"]

# After
COPY gunicorn.conf.py ./gunicorn.conf.py
RUN mkdir -p /app/logs
EXPOSE 8000
CMD ["uv", "run", "gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]

docker-compose.yml 遷移

yaml
# Before
command:
  ["uv", "run", "uvicorn", "app.main:app",
   "--host", "0.0.0.0", "--port", "8000",
   "--log-config", "log_config.json"]

# After
command: ["uv", "run", "gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]

Kubernetes Deployment 遷移

yaml
# Before
containers:
  - name: my-app-server
    command: ["uv"]
    args: ["run", "uvicorn", "app.main:app",
           "--host", "0.0.0.0", "--port", "8000",
           "--log-config", "log_config.json"]

# After
containers:
  - name: my-app-server
    command: ["uv", "run", "gunicorn", "app.main:app", "-c", "gunicorn.conf.py"]

驗證指令

bash
# 1. 本地 docker-compose 啟動
docker compose up --build

# 2. 確認 Gunicorn master + worker 啟動訊息
#    應看到:[INFO] Using worker: asgi

# 3. 打 API request 確認正常回應
curl http://localhost:4449/api/health

# 4. 確認 access log 寫入
docker compose exec backend cat logs/access.log

# 5. 執行測試
cd backend && uv run pytest

# 6. 確認 gunicorn 版本(需 ≥ 25.1.0)
uv run gunicorn --version

相關概念

來源