文章目录
- [前言:为什么"裸跑" Flask/Django 不够?](#前言:为什么“裸跑” Flask/Django 不够?)
- 环境说明
- [1、Gunicorn 是什么?](#1、Gunicorn 是什么?)
- [2、安装 & 最小可运行示例](#2、安装 & 最小可运行示例)
- 3、核心概念速览
- 4、并发模型怎么选?
- 5、生产级配置清单(可直接抄)
- [6、优雅重启 & 零宕机发布](#6、优雅重启 & 零宕机发布)
- 7、容器化最佳实践
- [8、常见坑 8 则](#8、常见坑 8 则)
- [小结 & 延伸阅读](#小结 & 延伸阅读)
前言:为什么"裸跑" Flask/Django 不够?
本地开发时,一条 flask run 或 python manage.py runserver 就能让应用愉快地跑起来。但这两句命令背后都挂着 单线程、单进程、自动重载、调试钩子 的"开发服务器"标签,一旦放到生产流量下,就会出现:
- 并发处理能力弱(单进程/线程)
- 无安全熔断(慢请求会拖垮整个服务)
- 静态文件效率低
- 不支持热升级、优雅重启
于是,我们需要一个 专注生产场景的 WSGI 服务器------Gunicorn(Green Unicorn)。
环境说明
操作系统:Ubuntu 24.04.1 LTS
Python 版本:3.10.19
Flask 版本:3.1.2
Gunicorn 版本:23.0.0
Visual Studio Code 版本:1.106.3
Visual Studio Code 插件:Python、Pylance、Docker
插件可参考:https://blog.csdn.net/achi010/article/details/154604215
1、Gunicorn 是什么?
- 用 Python 写的轻量级 WSGI HTTP Server
- 诞生于 2010 年,目前由 Benoit Chesneau 等人维护
- 口号:"WSGI HTTP Server for UNIX",定位就是 Unix/Linux 上跑 Python Web 的生产入口
- 支持多种并发模型(sync、gevent、eventlet、tornado、meinheld、uvloop...)
- 零配置即可跑,也提供 100+ 可调参数,可玩性高
部署相关文档(Nginx 相关配置注意事项):https://docs.gunicorn.org/en/latest/deploy.html
GitHub:https://github.com/benoitc/gunicorn
Gunicorn --- Flask Documentation (3.1.x)
2、安装 & 最小可运行示例
(1)安装
bash
python -m venv venv
source venv/bin/activate
pip install gunicorn flask
(2)示例应用 app.py
python
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.get("/")
def index():
return jsonify(
pid=os.getpid(),
msg="Hello from Gunicorn!"
)
(3)启动测试
bash
gunicorn app:app -b 0.0.0.0:8000
访问 http://localhost:8000 ,看到返回的进程 PID,说明 Gunicorn 已经在背后监听 8000 端口。
3、核心概念速览
| 术语 | 含义 |
|---|---|
| Worker | 实际处理请求的进程/线程 |
| Master | 管理 Worker 生命周期的守护进程 |
| Pre-fork | Master 先 fork() 出 N 个 Worker,再共同 accept() 同一个 Socket |
| Sync | 默认模型,每个 Worker 一次只处理一个请求(适合 CPU 密集) |
| Async | 基于 gevent/eventlet,一个 Worker 可并发上千个绿色线程(适合 I/O 密集) |
| graceful timeout | 给 Worker 的最大"优雅"处理时间,超时则强制 SIGKILL |
4、并发模型怎么选?
(1)CPU 密集(图像处理、加密、大数据计算)
推荐: sync 或 gthread,Worker 数 ≈ CPU 核数
bash
gunicorn app:app -w 4 -k sync
# 或者
gunicorn app:app -w 4 -k gthread
(2)I/O 密集(代理、API Gateway、爬虫聚合)
推荐: gevent
bash
pip install gevent
gunicorn app:app -k gevent -w 2 --worker-connections 1000
(3)混合场景(有点复杂,暂不讨论)
gthread 兜底,或者把 CPU 任务拆到 Celery,Web 层保持轻量
5、生产级配置清单(可直接抄)
配置参数 - 官方说明文档:https://docs.gunicorn.org/en/stable/settings.html#server-mechanics
conf
# gunicorn.conf.py
# 绑定地址和端口
bind = "0.0.0.0:8000"
# 工作进程数量,通常设置为CPU核心数的2倍加1
workers = 4
# 工作进程类型,使用gevent异步模式或同步模式"sync"
worker_class = "gevent"
# 每个工作进程的最大并发连接数
worker_connections = 1000
# 每个工作进程处理多少个请求后重启,防止内存泄漏
max_requests = 10000
# max_requests的抖动值,避免所有工作进程同时重启
max_requests_jitter = 500
# 优雅关闭超时时间(秒)
graceful_timeout = 30
# HTTP keep-alive持续时间(秒)
keepalive = 2
# 是否预加载应用,可以节省内存并提高性能
preload_app = True
# 访问日志格式定义
access_logformat = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# 访问日志输出位置,"-"表示标准输出
accesslog = "-"
# 错误日志输出位置,"-"表示标准输出
errorlog = "-"
# 日志级别:debug, info, warning, error, critical
loglevel = "info"
启动:
bash
gunicorn app:app -c gunicorn.conf.py
6、优雅重启 & 零宕机发布
(1)收到 HUP 信号后,Master 会重新载入代码,新 Worker 逐步替换旧 Worker,旧请求不受影响:
bash
kill -HUP <master_pid>
(2)如果在容器中,可把 PID 1 换成 gunicorn,docker stop 会自动发 SIGTERM,Gunicorn 会等待 graceful_timeout,再退出。
(3)蓝绿 / 滚动发布:结合 K8s RollingUpdate + readinessProbe,实现 零中断。
7、容器化最佳实践
(1)Dockerfile(多阶段、非 root、健康检查)
dockerfile
# 使用 Python 3.10.19 slim 镜像作为多阶段构建的第一阶段,命名为 builder
# 这个阶段专门用于安装 Python 依赖包
FROM python:3.10.19-slim AS builder
# 设置 pip 的国内镜像源为默认
ENV PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
# 设置工作目录为 /app
WORKDIR /app
# 将项目中的 requirements.txt 文件复制到容器的 /app 目录中
COPY requirements.txt .
# 在 builder 阶段安装项目所需的 Python 依赖包到用户目录
RUN pip install --user -r requirements.txt
# 开始第二阶段构建,使用相同版本的 Python 基础镜像
FROM python:3.10.19-slim
# 设置环境变量 PATH,使用户安装的包可以在命令行中直接使用
ENV PATH=/home/app/.local/bin:$PATH
# 创建一个 UID 为 1000 的 app 用户并创建其主目录
# 这样避免以后以 root 用户身份运行应用,提高安全性
RUN useradd -m -u 1000 app
# 设置工作目录为 /app
WORKDIR /app
# 从第一阶段(builder)复制已安装的 Python 包到 app 用户的本地目录
COPY --from=builder /root/.local /home/app/.local
# 将项目中的所有文件复制到容器的 /app 目录中
COPY . .
# 切换到 app 用户执行后续指令,增强容器安全性
USER app
# 声明容器运行时监听的端口为 8000
EXPOSE 8000
# 配置健康检查,通过 Python 脚本发送 HTTP 请求检测应用是否正常运行
HEALTHCHECK CMD python -c "import requests; requests.get('http://localhost:8000/healthz', timeout=3)"
# 容器启动时执行的命令:使用 gunicorn WSGI 服务器启动 Flask 应用
# app:app 表示导入 app 模块中的 app 对象
# -c gunicorn.conf.py 表示使用 gunicorn.conf.py 配置文件
CMD ["gunicorn", "app:app", "-c", "gunicorn.conf.py"]
(2)构建镜像
bash
docker build -t app:0.1 .
(3)运行测试
bash
docker run --name app -p 8000:8000 -d app:0.1
浏览器访问:http://localhost:8000 看到返回的进程 PID,说明容器正常启动
8、常见坑 8 则
-
忘记设置
forwarded-allow-ips用 Nginx 做反向代理时,需把真实 IP 传过去:
forwarded-allow-ips = "*"或具体 CIDR -
静态文件 404
Gunicorn 只负责 动态 WSGI,静态资源应交给 Nginx/Caddy/S3
-
Worker 内存暴涨
设置
max_requests+max_requests_jitter,让 Worker 定期"重生" -
HTTPS 重定向死循环
在代理层加
X-Forwarded-Proto: https,并在 Flask 加ProxyFix -
Docker 里
PID 1收不到信号用
exec gunicorn ...启动,或加tini作为 init -
Gevent 与 C 扩展冲突
部分库(如
psycopg2)需改用 纯 Python 或协程友好 版本(psycopg2-binary→psycopg[binary]) -
日志时间戳不对
容器内默认 UTC,可在
gunicorn.conf.py里:access_log_format = '%(t)s %(h)s "%(r)s" %(s)s %(b)s' -
高并发下端口耗尽
打开
net.ipv4.tcp_tw_reuse = 1,或让客户端走连接池
小结 & 延伸阅读
- Gunicorn 不是银弹,而是 WSGI 的最后一公里。合理选择并发模型 + 参数调优,才能发挥多核威力。
- 源码仅 7k 行,注释友好,推荐阅读
gunicorn/workers/下各种模型的实现差异。 - 进一步学习 ASGI 相关知识