Python Web 应用部署实战:Gunicorn 安装配置、systemd 托管与 Nginx 反向代理调优


<!DOCTYPE html>

Python Web 应用部署实战:Gunicorn 安装配置、systemd 托管与 Nginx 反向代理调优

Python Web 应用部署实战:Gunicorn 安装配置、systemd 托管与 Nginx 反向代理调优

写好的 Python Web 应用(Flask、Django、FastAPI)默认自带的开发服务器只能用于本地调试,直接裸奔到公网既慢又不安全。生产环境的标准做法是:Gunicorn 作为 WSGI 应用服务器负责跑 Python 代码,systemd 负责开机自启和崩溃拉起,Nginx 在它前面做反向代理、静态文件服务与 HTTPS 终结。本文从安装到调优,再到 502/504 排错,给出一套完整的部署链路。

一、为什么生产环境要用 Gunicorn 而不是 python app.py

Flask 自带的 app.run()、Django 的 runserver 都是单进程单线程的开发服务器,官方明确不建议用于生产:一次只能处理一个请求,并发稍微上来就排队,而且没有进程隔离,一个请求崩溃整个服务就挂了。

Gunicorn(Green Unicorn)是 Python 生态最主流的 WSGI 服务器:它按"预派生(pre-fork)"模型管理多个 worker 进程,每个 worker 独立处理请求,单个 worker 崩溃会被主进程自动拉起。请求到达后由 worker 调用你的 WSGI 应用对象(Flask 的 app、Django 的 wsgi.application),对应用代码完全无侵入。

浏览器 --> Nginx(80/443) --> Gunicorn(127.0.0.1:8000) --> WSGI 应用 --> 数据库/Redis

Nginx 只负责转发请求和返回静态文件,真正的 Python 代码全部在 Gunicorn 的 worker 里执行。这样静态资源不占用 Python 进程,性能和安全都更可控。

二、安装 Gunicorn:虚拟环境优先

强烈建议在虚拟环境里安装 Gunicorn,避免污染系统 Python 环境,也方便按项目锁定版本。以 Debian/Ubuntu 为例:

# 1. 安装 Python3、虚拟环境工具和项目依赖
apt install -y python3 python3-venv python3-pip

# 2. 创建项目目录并初始化虚拟环境
mkdir -p /var/www/myapp
cd /var/www/myapp
python3 -m venv venv

# 3. 激活虚拟环境并安装依赖
source venv/bin/activate
pip install -U pip
pip install gunicorn flask   # 按项目实际依赖安装

# 4. 验证版本
gunicorn --version

如果服务器无法直接访问 PyPI,可以临时换国内镜像源加速:

pip install -i https://mirrors.aliyun.com/pypi/simple/ gunicorn

三、Gunicorn 运行方式:命令行参数与配置文件

最简启动方式:指定 WSGI 入口模块。假设项目里 app.py 定义了 app = Flask(__name__):

# 从 app 模块导入 app 对象,监听本机 8000 端口
gunicorn -w 4 -b 127.0.0.1:8000 app:app

参数多了以后命令行太长,推荐写配置文件 gunicorn.conf.py,Gunicorn 会自动读取同目录下的同名文件,或显式指定:

# /var/www/myapp/gunicorn.conf.py
import multiprocessing

# 监听地址:只绑内网,由 Nginx 转发进来
bind = "127.0.0.1:8000"

# worker 数量:CPU 核数 * 2 + 1 是常见经验值
workers = multiprocessing.cpu_count() * 2 + 1

# worker 类型:sync 是最简单稳定的同步模型
worker_class = "sync"

# 单 worker 处理请求的超时秒数,超时会杀掉该 worker 并重启
timeout = 60

# 每个 worker 最多处理 1000 个请求后自动重启(防内存泄漏累积)
max_requests = 1000
max_requests_jitter = 100

# 日志
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"

# 进程名与文件
name = "myapp"
pidfile = "/var/run/gunicorn/myapp.pid"

创建日志目录并启动:

mkdir -p /var/log/gunicorn /var/run/gunicorn
gunicorn -c gunicorn.conf.py app:app

四、用 systemd 托管:开机自启与崩溃自动拉起

直接命令行启动的 Gunicorn 会在 SSH 断开时被终止。用 systemd 把它注册成服务,开机自启、崩溃重启、日志统一管理全都交给系统:

# /etc/systemd/system/myapp.service
[Unit]
Description=Gunicorn instance for myapp
After=network.target

[Service]
# 用专用系统用户运行,别用 root
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp
Environment="PATH=/var/www/myapp/venv/bin"
Environment="PYTHONUNBUFFERED=1"
ExecStart=/var/www/myapp/venv/bin/gunicorn -c gunicorn.conf.py app:app
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

启动并设置开机自启:

systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp

发布新代码后平滑重启(不中断在途请求):

# 优雅重启:先拉起新 worker,再回收旧 worker
systemctl reload myapp

# 如果改了 systemd 配置或环境变量,用 restart
systemctl restart myapp

注意 reload 依赖 Gunicorn 支持平滑重启(默认支持),它发送 HUP 信号给主进程,主进程逐个替换 worker,而不是粗暴全部杀掉。

五、Nginx 反向代理:静态文件分流与安全头

Nginx 配置一个 server 块,把动态请求代理给 Gunicorn,同时自己处理静态文件(图片、CSS、JS),Python 进程就完全不碰这些资源:

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name myapp.example.com;

    # 静态文件由 Nginx 直接返回
    location /static/ {
        alias /var/www/myapp/static/;
        expires 7d;
        access_log off;
    }

    # 其他请求全部转发给 Gunicorn
    location / {
        proxy_pass http://127.0.0.1:8000;
        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;

        # 长连接/超时设置,与 Gunicorn 的 timeout 保持协调
        proxy_connect_timeout 60s;
        proxy_read_timeout 60s;
    }
}

启用站点并重载:

ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

部署 HTTPS 时,证书续期工具(如 certbot)会在 reload 后自动加 443 跳转,这里不再展开。别忘了在 Gunicorn 侧设置 X-Forwarded-Proto 后,Flask/Django 里要开启代理信任(如 Flask 的 ProxyFix),否则应用拿到的 request.scheme 始终是 http,会导致生成的 URL 和 OAuth 回调错误。

六、性能调优:worker 数量、超时与内存泄漏

1. worker 数量怎么定
同步 worker(sync)每个 worker 同时只能处理一个请求,所以:

# 经验公式:2 * CPU 核数 + 1
# 2 核机器 → 5 个 worker
workers = 5

如果应用里有大量 I/O 等待(数据库查询、外部 API 调用),CPU 计算占比低,可以适当加大 worker 数;如果应用是 CPU 密集(计算型任务),worker 数超过核心数反而会因上下文切换变慢。

2. 用 gevent worker 提升 I/O 并发
同步模型下每个 worker 同一时间只能处理一个请求,遇到慢查询会被"卡住"。改用 gevent 协程 worker,一个 worker 内可以并发处理大量 I/O 请求:

pip install gevent

# gunicorn.conf.py 中修改
worker_class = "gevent"
# 每个 worker 的并发协程数
worker_connections = 1000

注意:gevent 会 monkey-patch 网络库,个别不兼容的库(某些数据库驱动、C 扩展)需要实测验证,生产环境务必先压测再上线。

3. 超时与 max_requests:两个防崩溃保险
timeout 防止某个请求卡死整个 worker;max_requests 防止长期运行的内存泄漏。设置建议:timeout 取接口最慢正常耗时的 3~5 倍;max_requests 1000~2000 是安全区间。日志里出现 Worker timed out 时,先看是不是某接口真的慢,而不是盲目调大超时掩盖问题。

# 快速观察 worker 数量与状态
ps aux | grep gunicorn

# 看错误日志中的超时和重启记录
tail -f /var/log/gunicorn/error.log

七、常见排错:502、504 与各种玄学问题

1. Nginx 返回 502 Bad Gateway
502 表示 Nginx 连不上 Gunicorn。按顺序排查:

# 1) Gunicorn 是否在跑
systemctl status myapp

# 2) 端口是否在监听(bind 必须与 proxy_pass 一致)
ss -tlnp | grep 8000

# 3) 权限问题:worker 用户能否读取项目文件
sudo -u www-data ls /var/www/myapp/app.py

# 4) 手动启动看报错
cd /var/www/myapp && sudo -u www-data ./venv/bin/gunicorn -c gunicorn.conf.py app:app

最常见原因:项目目录或代码文件权限是 root,www-data 用户读不了;或者 bind 写成了 0.0.0.0:8000 但防火墙没放行(其实绑 127.0.0.1 就完全不需要放行防火墙)。

2. 504 Gateway Timeout
504 是 Nginx 的 proxy_read_timeout 到点而 Gunicorn 还没返回。两个超时要配合:Nginx 的 proxy_read_timeout 必须大于等于 Gunicorn 的 timeout。否则 Gunicorn 还在跑(可能 60 秒内能完成),Nginx 60 秒先放弃了。另外注意 timeout 到点后 Gunicorn 会杀掉 worker,产生 Worker timed out 日志,此时前端表现为"请求突然中断"而不是 504。

3. 502 时日志却显示 worker 正常
检查是否端口冲突:另一个进程(比如别的项目)占了 8000。用 ss -tlnp | grep 8000 看进程名。还有可能是 systemd 服务启动了两次(daemon-reload 后旧的还在),先 systemctl stop 再 start。

4. 访问 /static 404
alias 路径写错最常见的坑:location /static/ { alias /var/www/myapp/static/; } 的 alias 路径末尾斜杠不能省,否则 Nginx 会把 URL 路径直接拼到 alias 后面导致路径错乱。用 nginx -T | grep -A 5 static 检查最终配置。

5. 修改代码后不生效
Gunicorn 默认不监听代码变化(那是开发模式)。发布流程是:git pull / 上传代码 → systemctl reload myapp。如果项目里用了 --reload 参数跑在后台,生产环境务必去掉。

八、小结

一套合格的 Python Web 生产部署 = Gunicorn 跑代码 + systemd 管生命周期 + Nginx 做反代和静态文件。本文的配置全部可以直接套用,关键决策点就三个:worker 数量按 CPU 与 I/O 特性定、timeout 与 Nginx 侧协调一致、max_requests 防内存泄漏。遇到 502/504 先分清是哪一层的问题——Nginx 连不上是 502,应用响应太慢是 504,按排错小节逐步定位即可。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

dnsmasq 实战:本地 DNS 缓存加速、内网域名解析与 DHCP 一体化配置

Nginx 访问控制实战:HTTP Basic Auth 基础认证、IP 黑白名单与防爬虫防扫描

评 论
更换验证码