
1. 部署方案的整体设计思路1.1 为什么需要uWSGI先搞清楚请求是怎么走的后面详细的操作很多人写过但我还是想先聊一下架构层面的问题。因为这个东西如果不理解透后面配置的时候很容易一头雾水出了问题也不知道从哪排查。先说结论一个Python Web应用比如Django、Flask在开发阶段跑自带的开发服务器只能自己调试用扛不住真实流量也经不起公网访问。生产环境里常规做法是把应用挂在一个专门的应用服务器上前面再挡一层Web服务器。这里的uWSGI就是那个“专门的应用服务器”nginx就是那层“挡在前面的Web服务器”。请求链路是这么走的浏览器 → nginx(80/443端口) → uWSGI(socket或端口) → Python应用nginx负责接收外部请求、处理静态文件、做反向代理uWSGI负责把Python应用“跑起来”接收nginx转过来的动态请求交给应用逻辑处理完再返回。两者通过一种叫uWSGI协议的通信方式交互比裸用HTTP转发效率更高这也是我们选择这套组合的核心原因。对于实际正在学习部署的人来说核心要理解一点nginx处理动态请求并不直接它不知道Python应用怎么调用uWSGI又是专为Python应用设计的但它作为对外服务的服务器又不够专业处理静态文件、并发连接都不如nginx。两者配合正好互补。1.2 为什么选nginx和uWSGI这套组合市面上的方案不止一种。比如Gunicorn nginx、uWSGI nginx、单独用uWSGI的HTTP模式、甚至直接上Docker容器化部署。之所以在学习阶段选uWSGI nginx主要看中几个点。第一uWSGI的配置能力非常强。进程数、线程数、协程、超时时间、日志切割、平滑重载几乎能想到的生产需求它都支持而且单个配置文件就能搞定全部设置不需要额外写一大堆胶水脚本。第二nginx对静态文件的处理能力是出了名的强。如果一个请求是图片、CSS、JS这类静态资源nginx直接自己就返回了完全不打扰后端的Python进程。动态请求才转发给uWSGI这样能省下大量后端计算资源。第三这套组合的资料极其丰富。不管是你用Django、Flask还是FastAPIFastAPI需要配合ASGI模式但原理类似会遇到的问题基本上都有人踩过了排查起来Stack Overflow上全是答案。对学习者来说这是非常大的优势。当然这套方案的缺点也有uWSGI本身的参数体系比较复杂初次接触容易搞混。但反过来看这正是一个能深入理解Web服务器原理的好机会把细碎参数搞明白后面学其他部署方案会轻松很多。1.3 部署环境与版本选型建议为了不让后面实操的时候踩版本坑我先交代一下我当时的环境和选型思路你不需要完全一样但版本对齐能省很多麻烦。操作系统Ubuntu 22.04 LTS64位Python版本3.10系统自带的Python 3.10.12注意不要用Python 2项目已经全面停止支持了Web框架Django 4.2示例用DjangoFlask同理uWSGI版本2.0.23目前最稳定的版本别追求最新新版本未必有更好的体验nginx版本1.18.0Ubuntu 22.04源里的默认版本稳定够用远程服务器是阿里云的学生机2核4G的配置。这个配置跑这套组合完全够用也适合学习期折腾就算弄坏了重装系统也不心疼。1.4 部署中涉及的几个核心组件拆解在动手部署前先把整个系统里各个角色和它们的功能拆开看一遍后面配置的时候就不会不知道自己在配什么了。nginx在架构里的角色监听外部端口默认是80端口HTTP和443端口HTTPS对静态资源的处理图片、CSS、JS等文件直接从磁盘读取返回不经过Python对动态请求的处理将请求转发给后端的uWSGI服务额外能力负载均衡、反向代理、访问日志、请求频率限制、Gzip压缩等uWSGI在架构里的角色真正的Python进程管理器启动并维护Python应用进程支持多进程、多线程模式运行Python应用通过socket或端口和nginx通信对Python应用进行健康检查、超时控制、优雅重启Django/Python应用在架构里的角色负责任务的实际处理比如查数据库、业务逻辑、返回响应需要配合uWSGI的调用方式通过WSGI接口暴露出来Django项目的wsgi.py文件就是为这个准备的三个角色搞清楚之后部署就变成了一件很机械的事情把每个组件配置好再让它们之间互相认识。2. uWSGI的安装与核心配置详解2.1 环境准备Python依赖与虚拟环境我先说一个很多人容易忽略的地方不要用系统全局的Python环境直接装项目依赖。尤其是服务器上可能同时有几个Python项目每个项目依赖的Django版本不一样全局环境里一装各种版本冲突瞬间就能把人搞崩溃。所以虚拟环境这一步一定要养成习惯。我的操作是这样# 进入项目目录 cd /var/www/myproject # 创建虚拟环境名字随意用venv大家一眼就能看出来 python3 -m venv venv # 激活虚拟环境注意后续所有操作都在这个虚拟环境下进行 source venv/bin/activate # 确认当前Python路径已经指向虚拟环境 which python # 安装项目依赖 pip install django4.2 pip install uwsgi2.0.23这里有几个细节值得说。第一用python3 -m venv而不是virtualenv这是官方推荐的现代做法virtualenv基本可以淘汰了。第二uWSGI最好也装进虚拟环境里这样每个项目都能有自己独立的uWSGI版本不会互相干扰。第三安装uWSGI时会自动编译所以服务器上需要有gcc和相关构建工具。如果没有先执行sudo apt update sudo apt install build-essential python3-dev如果缺了这一步uWSGI安装过程中大概率会报错提示缺少Python头文件或者编译失败。我最初在图省事跳过这一步结果反复踩坑后来老老实实装上就好了。2.2 uWSGI的安装方式对比与选择uWSGI官方提供了多种安装方式我梳理了一下各自的优劣大家可以根据自己情况选。安装方式优点缺点适用场景pip安装版本可控、跟随虚拟环境、切换项目方便需要编译环境推荐最常见最灵活apt安装安装简单、系统级管理版本较旧、无法按项目隔离快速验证场景源码编译可定制编译选项步骤多、维护成本高有特殊性能需求的场景我真正部署的时候用的就是pip安装。这里多说一句网上有些教程直接教apt install uwsgi然后全局环境里启动这种用法在单项目服务器上问题不大但如果你要在一个服务器上部署多个Python项目这种方式的劣势就会非常明显——因为系统级的uWSGI只能加载一个全局的Python环境项目之间的依赖会冲突而且切换配置非常麻烦。所以我的建议很明确uWSGI和项目本身一起装进虚拟环境每个项目一套完整独立的环境互不干扰。2.3 uWSGI核心参数解析不懂这些就是盲人摸象uWSGI参数非常多但真正实际部署时会用到的其实就那十来个。我一边解释参数含义一边把为什么这么配的原因说清楚。用命令行启动的方式便于理解参数# 在项目目录下执行 uwsgi --socket 127.0.0.1:8001 \ --chdir /var/www/myproject \ --wsgi-file myproject/wsgi.py \ --master \ --processes 4 \ --threads 2 \ --stats 127.0.0.1:9191 \ --vacuum \ --die-on-term逐个解释一下每个参数的含义--socket 127.0.0.1:8001指定uWSGI监听的socket地址。这里监听的是本机的8001端口专门给nginx转发请求用的。注意一定是127.0.0.1而不是0.0.0.0因为uWSGI只对内提供服务不需要对外暴露这样更安全。--chdir /var/www/myproject切换工作目录到项目目录。这个必须有否则uWSGI找不到Django项目文件。--wsgi-file myproject/wsgi.py指定WSGI入口文件。Django项目的wsgi.py文件定义了如何加载应用uWSGI就是通过这个文件来调用你的项目。--master启用在主进程模式下运行。主进程负责管理子进程可以平滑重载配置文件推荐生产环境使用。--processes 4启动4个worker进程来处理请求。这个数字不是越大越好一般和服务器核心数相关2核4G的机器配2-4个进程就够用。--threads 2每个worker进程里再开2个线程。进程线程的组合模式能够更好地处理I/O密集型请求。--stats 127.0.0.1:9191开启状态监控接口可以通过这个端口查看uWSGI运行状况。--vacuum在所有进程退出时自动清理环境。这个参数能避免产生一堆残留的socket文件和pid文件。--die-on-term收到SIGTERM信号时强制退出所有进程。没有这个参数的话关掉uWSGI有时候会有残留进程重启的时候端口被占用就很烦。用命令行参数启动有个好处能直观看到每个参数的效果方便学习和调试。但生产环境我更推荐用配置文件的方式后面会详细讲。2.4 生产环境推荐的ini配置文件写法命令行参数适合调试但真正生产环境部署强烈建议写成ini配置文件。好处是配置一目了然、容易版本管理、重启后配置不会丢、改参数不用翻历史命令。在项目目录下建一个uwsgi.ini文件[uwsgi] # 项目目录 chdir /var/www/myproject # 虚拟环境路径 home /var/www/myproject/venv # wsgi入口文件 module myproject.wsgi:application # 通信方式使用socket仅供nginx转发 socket 127.0.0.1:8001 # 或者使用Unix socket文件和端口方式二选一 # socket /var/www/myproject/uwsgi.sock # 进程和线程配置 master true processes 4 threads 2 enable-threads true # 进程管理 pidfile /var/www/myproject/uwsgi.pid daemonize /var/www/myproject/uwsgi.log # 安全和清理 vacuum true die-on-term true uid www-data gid www-data # 超时设置 harakiri 60 socket-timeout 30这里重点说几个配置文件里才有的注意点关于module myproject.wsgi:application这个写法直接指向Django项目的wsgi模块里的application对象PyCharm创建的Django项目结构基本都是一样的直接把myproject换成你实际的项目名就行。这个配置比--wsgi-file更灵活因为它是通过Python模块导入的方式加载的对依赖的处理更干净。关于uid和gid www-data这两个参数会把uWSGI进程切换到www-data用户下运行。之所以这么做是因为nginx默认也是www-data用户运行的如果nginx需要读取uWSGI创建的socket文件两者用户不一致会出现权限问题。后续我详细展开说这个问题。关于daemonize这个参数让uWSGI在后台运行日志输出到指定文件。调试阶段建议先不加这个参数让日志直接打在终端上看得清楚。启动方式很简单# 启动 uwsgi --ini uwsgi.ini # 平滑重载配置改了配置不用重启 uwsgi --reload uwsgi.pid # 停止 uwsgi --stop uwsgi.pid配置文件一旦创建好后续运维就非常省心改配置、重启、看日志几行命令搞定。3. nginx安装与配置串联实操3.1 nginx安装与基础校验nginx的安装非常成熟Ubuntu下简单的两条命令sudo apt update sudo apt install nginx -y装完以后第一件事是检查服务状态和版本# 查看nginx版本 nginx -v # 查看服务状态 sudo systemctl status nginx # 如果没启动启动它 sudo systemctl start nginx sudo systemctl enable nginx设置开机自启动这个步骤千万别忘否则服务器重启以后nginx不会自己起来网站直接挂掉。我遇到过好几次因为忘了enable结果服务器一重启服务就掉线的情况。验证nginx是不是正常工作直接访问服务器IP地址如果能看到一个Welcome to nginx的默认页面说明安装成功。3.2 nginx配置文件结构说明nginx的配置目录结构既定理解清楚才能改对地方/etc/nginx/ ├── nginx.conf # 主配置文件 ├── sites-available/ # 站点可用配置存放所有站点配置 └── sites-enabled/ # 站点启用配置软链接到可用配置这个「available」和「enabled」分开的设计非常符合运维习惯先写好配置放在available里然后用软链接的方式在enabled里启用禁用一个站点只需要删掉软链接配置不会丢特别适合一台机器上部署多个网站的场景。在sites-available里创建我们的项目配置文件sudo vim /etc/nginx/sites-available/myproject写入以下内容server { listen 80; server_name your_domain_or_ip; # 上传文件大小限制不设置默认1MB上传大文件会报413 client_max_body_size 20M; # 静态文件处理由nginx直接返回不走uWSGI location /static/ { alias /var/www/myproject/static/; expires 7d; } # 媒体文件处理 location /media/ { alias /var/www/myproject/media/; expires 30d; } # 动态请求转发给uWSGI location / { include uwsgi_params; uwsgi_pass 127.0.0.1:8001; uwsgi_read_timeout 60s; uwsgi_send_timeout 60s; } # 访问日志 access_log /var/log/nginx/myproject_access.log; error_log /var/log/nginx/myproject_error.log; }创建好后启用这个站点配置# 创建软链接到enabled目录 sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/ # 测试nginx配置是否合法 sudo nginx -t # 重载nginx配置使改动生效 sudo systemctl reload nginx3.3 nginx配置里的关键细节逐条讲上面的配置核心就是几个location块这里详细拆解一下各个部分的逻辑。location /static/块的alias是关键。很多人会在这里写错成root。root会把完整的URL路径拼接到目录后面。比如root /var/www/myproject/static/时请求/static/css/style.css会去找/var/www/myproject/static/static/css/style.css这个路径显然是不对的。alias则是把/static/前缀替换成指定的目录。请求/static/css/style.css会直接找/var/www/myproject/static/css/style.css这才是正确的。include uwsgi_params是必须的。这个文件里定义了一些变量nginx会把这些变量通过uWSGI协议传给后端的uWSGI服务比如HTTP_HOST、REQUEST_URI、REMOTE_ADDR这些关键信息。很多初学者忘了加这一行结果Django里拿不到请求头信息各种诡异的问题都来了。这个文件nginx自带不需要自己写。**uwsgi_pass 127.0.0.1:8001**这个地址必须和uWSGI配置的socket地址完全一致一个端口不一致直接502。**超时参数uwsgi_read_timeout**需要根据业务特点来设。我配的60秒因为有些报表导出功能处理时间比较长。如果业务接口都很快可以适当调短比如30秒防止超慢请求占着连接不放。但注意如果这里设置太短而后端处理时间又确实长会频繁导致504。Django静态文件的收集。有人可能会问我Django项目里明明有static目录为什么nginx就是找不到这里有个前提——Django项目开发模式下是Django自己处理静态文件的但生产模式下需要先执行收集命令把项目里所有app的静态文件统一收集到一个目录里nginx才能直接从磁盘定位到cd /var/www/myproject source venv/bin/activate python manage.py collectstatic --noinput这个命令会把所有静态文件复制到settings.py里STATIC_ROOT指定的目录一般是项目下的static/目录执行完之后nginx的alias配置才有意义。不执行这一步静态文件404你就是排查到天亮也找不到原因。3.4 启动服务器并验证整个链路配置文件都准备好以后按顺序启动先uWSGI后nginx其实顺序无所谓但要保证uWSGI起来之后再重载nginx# 启动uWSGI cd /var/www/myproject source venv/bin/activate uwsgi --ini uwsgi.ini # 确认uWSGI进程在运行 ps aux | grep uwsgi # 确认端口在监听 ss -tlnp | grep 8001 # 重载nginx sudo systemctl reload nginx然后本地浏览器访问http://服务器IP如果能正常打开Django页面静态文件样式也正确加载说明整个链路已经通了。如果页面打不开或者报错按顺序去排查直接访问http://服务器IP如果显示nginx默认欢迎页说明nginx运行正常但站点配置没生效如果显示502 Bad Gateway说明nginx已正确转发但uWSGI没正常响应如果显示404但nginx日志没报错可能是Django里的URL配置问题如果页面出来了但样式全乱一定是静态文件配置的问题4. 常见问题与排查技巧实录4.1 502 Bad Gateway九成问题出在这些地方502是部署过程中遇到最多的错误nginx返回502说明nginx已经尝试连接uWSGI了但连接失败。我总结了一下常见的就这几种原因原因一uWSGI根本没启动或者启动后就崩了。启动后一定要确认uWSGI确实在运行ps aux | grep uwsgi如果没有进程去查看uWSGI日志tail -100 /var/www/myproject/uwsgi.log日志里一般会明确写出错误原因比如Python模块导入失败、端口被占用、目录不存在。看日志是最直接的排查方式别瞎猜。原因二socket地址对不上。uWSGI监听的是127.0.0.1:8001nginx转发的是127.0.0.1:8001但有时候手滑配置里某个地方写成了0.0.0.0:8001或者端口写成了别的。仔细检查两边配置确保完全一致。原因三权限问题。如果用的是Unix socket文件方式比如/var/www/myproject/uwsgi.socknginx的www-data用户必须能访问这个文件。最稳妥的方式是让uWSGI也以www-data用户运行并且socket文件放到nginx可以访问的目录。如果遇到permission denied相关的错误用这个命令临时测试一下sudo -u www-data ls -l /var/www/myproject/uwsgi.sock如果www-data用户连访问都不行那就是uWSGI的uid和gid没配置对。原因四nginx worker进程没有权限访问项目目录。这个比较隐蔽。nginx以www-data用户运行如果项目目录的owner是root权限是700那nginx就没有任何权限读取。把项目目录权限调一下sudo chown -R www-data:www-data /var/www/myproject这个命令直接把项目目录的全部权限给了www-data一劳永逸。4.2 Django静态文件404都是root和alias搞混的锅静态文件404是特别常见的问题而且非常打击人因为页面内容能返回就是样式全都丢了。排查思路是这样的先确认Django的静态文件有没有收集到位ls /var/www/myproject/static/如果目录是空的说明collectstatic没执行或者没收集成功。看下settings.py配置# settings.py STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, static) STATICFILES_DIRS [ os.path.join(BASE_DIR, staticfiles), ]这里有三个相关的变量容易混淆STATIC_URLURL前缀就是浏览器访问静态资源时的路径nginx的location匹配的就是这个东西STATIC_ROOTcollectstatic收集静态文件的输出目录必须要有STATICFILES_DIRSDjango开发模式下额外的静态文件目录这个目录不能和STATIC_ROOT相同否则会报错如果配置文件没问题执行完collectstatic以后目录有内容了就去nginx的error日志里看具体报错tail -50 /var/log/nginx/myproject_error.log日志里会显示实际查找的文件路径对照nginx配置里的alias路径仔细核对。99%的情况就是路径拼接不对改一下alias路径就好了。4.3 uWSGI启动失败日志里的真正原因如果uWSGI启动不成功先别急着百度报错信息先看日志cat /var/www/myproject/uwsgi.log比较常见的启动失败原因有ModuleNotFoundError: No module named myproject这说明uWSGI在启动时找不到项目模块。先确认启动命令里有没有chdir参数或者配置文件里有没有写chdir。没有的话uWSGI会在当前目录找项目而你当前目录在别的地方自然就找不到了。另外确认一下虚拟环境的home配置是不是正确指向你创建的那个venv。bind(): Address already in use端口被占用了八成是上一次运行没有完全退出。用这个命令查谁占用了端口ss -tlnp | grep 8001找到占用进程的PID杀掉再启动kill -9 [PID]Permission denied这个也常见尤其是用Unix socket文件的时候。确认配置文件里的uid、gid设置正确socket文件路径所在的目录启动uWSGI的用户必须有写权限。4.4 开机自启动配置不配好等于没部署服务器生产环境一个最重要的要求就是重启后服务要自动恢复。nginx用systemd管理很简单uWSGI就需要额外配置。在/etc/systemd/system/下创建uWSGI的服务文件[Unit] DescriptionuWSGI service for myproject Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/var/www/myproject EnvironmentPATH/var/www/myproject/venv/bin ExecStart/var/www/myproject/venv/bin/uwsgi --ini /var/www/myproject/uwsgi.ini Restartalways RestartSec10 [Install] WantedBymulti-user.target写好后启用sudo systemctl daemon-reload sudo systemctl enable myproject-uwsgi sudo systemctl start myproject-uwsgi这里说几个细节Environment里指定了PATH确保uWSGI能找到虚拟环境里的Python和相关依赖ExecStart用的是虚拟环境里uWSGI的绝对路径而不是系统全局的uwsgi命令这点非常关键不然容易启动成全局版本Restartalways让服务挂掉后自动拉起生产环境下这个配置是底线之前的uwsgi.ini里有daemonize配置和systemd一起用的时候建议去掉让进程以非daemon方式运行交给systemd统一管理日志输出到systemd的journal里也可以用StandardOutput配置修改输出位置。两者对日志的处理逻辑不同同时配置容易出现日志丢失或文件占用的问题。4.5 性能调优进程数、线程数到底怎么配把这个放到最后说是因为性能调优是在功能正常之后才需要考虑的事情但很多朋友上来就对着各种调优参数死磕反而把部署搞复杂了。uWSGI的进程数processes和线程数threads一般按这个思路来配进程数一般等于或略大于服务器的CPU核心数。2核的机器配2-4个进程即可线程数2即可不必贪多。Python有GIL锁线程对CPU密集型的任务帮助有限主要受益于I/O密集型的等待场景进程线程的模式比纯进程模式更能控制资源占用如果API接口耗时比较长允许并发比较多还可以考虑加--async和--ugreen等异步模式但会让配置复杂度明显上升。对大多数中小型应用4进程2线程的组合已经能应付日常流量了。nginx这边能做的调优keepalive_timeout设小一点比如15秒减少闲置连接占用的资源开启Gzip压缩对文本类资源HTML、CSS、JS的传输量能减少60%以上适当增大worker_processes比如设为auto让nginx根据CPU核心数自动分配最后要说的是调优是建立在监控数据之上的别上来就凭着感觉猛调参数。先跑一段时间观察uWSGI的stats页面curl 127.0.0.1:9191或者nginx的访问日志看响应耗时和错误率再有针对性地调整这才是健康的迭代方式。我个人在实际部署中的体会是uWSGI加nginx这套组合配置本身并不难难的是理解每个配置背后的原因。只要把「谁负责什么」「数据流怎么走」「每个参数更改会造成什么影响」这三个问题想清楚任何异常你都能从一个线索顺藤摸瓜找到根因而不是永远靠复制别人的配置碰运气。这套部署方案我后来又复现过很多次包括Flask项目、FastAPI项目原理都是通用的把Django这套吃透了其他框架完全是举一反三的事。