ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django生产部署实战:Nginx反向代理、Gunicorn与静态文件配置

Django生产部署实战:Nginx反向代理、Gunicorn与静态文件配置 1. 为什么Django部署总绕不开Nginx1.1 从开发服务器到生产环境的鸿沟刚接触Django的朋友经常会有一个疑问python manage.py runserver跑得好好的为什么一上线就要折腾Nginx、WSGI这些东西我当初也是这么想的直到第一次把项目丢到服务器上被人跑了几百个并发请求开发服务器直接卡死页面转圈转到天荒地老我才明白这两者根本不是一个量级的东西。Django自带的runserver本质上是一个单进程、多线程的调试用服务器它的设计目标就是让你在本地写代码时能快速看到效果而不是扛住真实流量。它没有做连接池、没有做静态资源的高效分发、没有做请求缓冲、也没有考虑过慢连接攻击这类问题。你在本地只有自己一个人访问当然感觉不到瓶颈可一旦放到公网环境情况完全不同。Nginx在这里扮演的角色是站在Django前面的那道“前台保安调度员”。它负责接收所有来自浏览器的连接把静态文件CSS、JS、图片、字体直接自己处理掉把动态请求转发给后面的Django应用服务器。这个分工带来的好处是Django只需要专心处理业务逻辑不用分心去管磁盘IO密集的静态资源分发Nginx用C语言写成处理高并发连接的能力甩开Python应用服务器好几条街同时Nginx还能顺手做负载均衡、限流、压缩、缓存这些事。我个人的经验是只要你的Django项目不是纯粹内部使用、每天只有几十个请求那就应该老老实实配一套Nginx WSGI。这不是“高级玩法”而是生产环境的标配。你不需要成为Nginx专家但至少要搞懂它的基本配置逻辑否则一旦线上出问题连日志在哪看都不知道。1.2 Nginx与WSGI服务器的组合逻辑很多教程一上来就讲怎么装、怎么配却很少解释清楚一个核心问题Nginx和Django之间到底是怎么“通话”的中间那个WSGI是什么角色简单来说Django本身是一个Python应用它遵循WSGIWeb Server Gateway Interface协议。这个协议规定了Web服务器和应用之间如何传递请求和响应。但Nginx不认识WSGI它只懂HTTP和反向代理。所以我们需要一个中间层也就是常说的WSGI服务器常见的有Gunicorn、uWSGI、Waitress等。它们之间的关系是这样的浏览器发来HTTP请求Nginx接收后按照配置转发给WSGI服务器比如Gunicorn监听的本地端口WSGI服务器把请求翻译成符合WSGI协议的形式交给Django处理Django返回响应再沿着原路返回给浏览器。这里有一个常见的误区有人以为Nginx能直接跑Django所以在配置里写一堆奇怪的指令。实际上Nginx永远只做反向代理真正执行Python代码的是WSGI服务器。你用Gunicorn也好、uWSGI也好、Waitress也好本质上都是替代了runserver的角色只是它们经过了生产环境的优化支持多进程、多线程、优雅重启、进程监控等功能。理解了这个组合逻辑后面看配置文件就不会一头雾水。每当看到proxy_pass你就知道这是在把请求转给后面的WSGI服务器每当看到location /static/你就知道这是让Nginx自己去读静态文件不走Django。这套思路建立起来之后无论换什么WSGI服务器、换什么云环境你都能快速上手。2. 部署前的环境准备与版本选型2.1 Python、Django与Nginx的版本搭配版本选型这件事说大不大说小也不小。我见过太多人因为版本不匹配折腾一整天最后发现是Nginx版本太老不支持某个指令或者Django版本和Python版本对不上。先看Python。Django 4.2是LTS版本官方支持Python 3.8到3.12Django 5.x则要求Python 3.10以上。我的建议是如果你在做一个长期维护的项目优先选Django 4.2 LTS搭配Python 3.10或3.11稳定性和生态兼容性都经过充分验证。如果你追求新特性Django 5.x配Python 3.12也没问题但要确认你用的第三方库都已经适配。Nginx方面稳定版Stable和主线版Mainline都可以用。稳定版更新频率低、经过更多测试适合生产环境主线版会包含一些新特性适合喜欢尝鲜或者有特定需求的情况。现在主流Linux发行版的软件源里自带的Nginx版本基本都在1.18以上满足绝大多数场景。如果你需要特定版本比如某些老系统像银河麒麟这类国产化环境自带源里版本太旧那就需要离线安装或者从源码编译这个后面会细说。还有一个容易忽略的点是操作系统。Ubuntu/Debian系和CentOS/RHEL系在包管理、服务管理、防火墙配置上都有差异。Ubuntu用apt服务用systemctl防火墙默认是ufwCentOS用yum或dnf防火墙是firewalld。写配置之前先确认自己的系统环境能省掉很多查文档的时间。提示不要盲目追求最新版本。生产环境的第一原则是稳定可控新版本带来的新特性往往不值得你承担未知风险。除非有明确的安全补丁或功能需求否则选一个成熟的LTS版本更省心。2.2 依赖安装与虚拟环境搭建环境隔离这件事我再怎么强调都不为过。系统自带的Python环境是给操作系统自己用的你往里装包轻则版本冲突重则把系统工具搞崩。所以第一步永远是创建虚拟环境。# 安装虚拟环境工具如果还没装 sudo apt install python3-venv python3-pip -y # 进入项目目录创建虚拟环境 cd /path/to/your/project python3 -m venv venv # 激活虚拟环境 source venv/bin/activate # 安装依赖 pip install --upgrade pip pip install django gunicorn如果你用的是Windows做开发、Linux做部署或者反过来一定要生成一份requirements.txt把项目依赖固定下来。我习惯在开发机上用pip freeze requirements.txt导出依赖然后部署机上pip install -r requirements.txt一键安装。这样能避免“我这里能跑你那里报错”的经典问题。WSGI服务器的选择上Gunicorn是目前最主流的选择配置简单、文档丰富、社区活跃。uWSGI性能也很强但配置项多、上手曲线陡新手容易被一堆参数搞晕。Waitress是纯Python实现的在Windows上跑得比较顺适合开发环境或小型内部项目。如果你在Windows10上部署waitress nginx是一个可行的组合但要注意Windows下Nginx的进程管理方式跟Linux不太一样守护进程、信号处理这些都有差异长期跑建议还是用Linux服务器。Nginx的安装就简单多了# Ubuntu/Debian sudo apt update sudo apt install nginx -y # CentOS/RHEL sudo yum install nginx -y # 或 sudo dnf install nginx -y装完之后nginx -v确认版本systemctl start nginx启动服务浏览器访问服务器IP看到欢迎页就说明装好了。如果看不到先检查防火墙有没有放行80端口再看Nginx服务状态和错误日志。3. Nginx核心配置逐行拆解3.1 反向代理配置到底在写什么Nginx的配置文件通常位于/etc/nginx/nginx.conf但更推荐的做法是在/etc/nginx/sites-available/下为每个项目建一个独立配置文件然后在sites-enabled/里做个软链接。这样配置清晰也方便管理多个项目。一个最基础的反向代理配置长这样server { listen 80; server_name yourdomain.com; 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; } }乍一看很简单但每一行都有讲究。listen 80是监听端口server_name是匹配的域名。真正核心的是location /里的那几行。proxy_pass把请求转发到本地的8000端口这个端口就是你WSGI服务器监听的端口。后面的几个proxy_set_header是在给转发过去的请求“加头信息”让Django知道真实的客户端信息。这里重点说X-Forwarded-For和X-Real-IP。因为经过Nginx转发后Django看到的请求来源IP变成了127.0.0.1如果不加这两个头你在Django里拿到的request.META[REMOTE_ADDR]永远是本机地址做IP限流、日志分析、地域统计全都会出错。X-Forwarded-Proto则是告诉Django原始请求用的是HTTP还是HTTPS涉及重定向和CSRF校验时很重要。还有一个坑是proxy_pass末尾的斜杠。如果你想保留路径就写http://127.0.0.1:8000如果要剥离路径前缀就写http://127.0.0.1:8000/。这个斜杠的有无会导致请求路径完全不同我当初就是在这里踩了坑配了半天发现所有请求都404最后才发现是斜杠问题。注意改了Nginx配置后一定要先nginx -t测试语法通过后再systemctl reload nginx平滑重载。直接restart会中断正在处理的请求生产环境能不用就不用。3.2 静态文件与媒体文件的处理策略Django在生产环境下默认是不负责静态文件的DEBUGFalse之后runserver那套静态文件自动服务就失效了。所以静态文件的处理必须交给Nginx。Django有两个跟文件相关的配置项STATIC_URL、STATIC_ROOT和MEDIA_URL、MEDIA_ROOT。静态文件指的是你项目里写死的CSS、JS、图片通过collectstatic命令收集到STATIC_ROOT目录媒体文件是用户上传的内容存储在MEDIA_ROOT目录。Nginx配置里对应的部分是location /static/ { alias /path/to/your/project/staticfiles/; expires 30d; add_header Cache-Control public, immutable; } location /media/ { alias /path/to/your/project/media/; expires 7d; }alias和root的区别要搞清楚。root会把location的路径拼接到后面alias则是直接替换。比如location /static/配alias /data/static/访问/static/css/app.css会去读/data/static/css/app.css如果配root /data/static/则会去读/data/static/static/css/app.css。绝大多数情况我们用alias更符合直觉。expires和Cache-Control是给浏览器缓存用的静态文件加了哈希指纹的可以设长一点比如30天甚至一年媒体文件视情况设置用户频繁更新的内容不要设太长。有一点要特别提醒STATIC_ROOT和MEDIA_ROOT目录的权限要配好。Nginx通常以www-data或nginx用户运行如果目录权限是700且属于你的部署用户Nginx读不到文件页面样式会全部丢失控制台一堆403错误。稳妥的做法是目录权限755文件权限644或者把Nginx用户加入对应的用户组。3.3 HTTPS配置与自签名证书的实操现在浏览器对HTTP站点越来越不友好正式环境基本都要上HTTPS。如果你有域名用Lets Encrypt免费证书是最省事的方案如果是内部测试环境或者没有公网域名自签名证书也能用。自签名证书的生成步骤如下# 生成私钥和证书有效期365天 sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /etc/nginx/ssl/selfsigned.key \ -out /etc/nginx/ssl/selfsigned.crt # 执行过程中会交互式询问国家、组织、Common Name等信息 # Common Name 填你的域名或服务器IP生成之后Nginx配置里加上443端口的server块server { listen 443 ssl; server_name yourdomain.com; ssl_certificate /etc/nginx/ssl/selfsigned.crt; ssl_certificate_key /etc/nginx/ssl/selfsigned.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; 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; } } # HTTP强制跳转HTTPS server { listen 80; server_name yourdomain.com; return 301 https://$host$request_uri; }自签名证书浏览器会报“不安全”警告这是正常的因为它没有经过受信任的CA签发。内部测试可以手动信任正式环境还是建议用正规证书。如果只是测试HTTPS行为自签名完全够用。另外当Django跑在HTTPS后面时settings.py里要加一行SECURE_PROXY_SSL_HEADER (HTTP_X_FORWARDED_PROTO, https)否则Django会认为请求是HTTP的导致重定向循环或者CSRF验证失败。这个是很多人配完HTTPS后遇到“重定向次数过多”的罪魁祸首。4. Django项目侧的对接配置4.1 settings.py生产环境必改项从开发环境切到生产环境settings.py里有几处必须调整否则要么跑不起来要么存在安全隐患。第一是DEBUG False。开发环境开着DEBUG方便看报错但生产环境绝不能开。一旦开着DEBUG任何报错页面都会暴露你的代码路径、环境变量、数据库结构等于把家门钥匙挂在门口。关掉DEBUG之后还要配ALLOWED_HOSTS把域名或服务器IP填进去不填的话所有请求都会被拒绝。第二是静态文件相关配置。STATIC_ROOT要指向一个实际目录部署时运行python manage.py collectstatic把所有静态文件收集过去。STATIC_URL保持/static/和Nginx配置对应。如果你用了django.contrib.staticfiles相关配置都要确认一遍。第三是数据库配置。开发环境常用SQLite生产环境建议换成MySQL或PostgreSQL。切换数据库不只是改ENGINE还要装对应的驱动、配连接池、调整CONN_MAX_AGE这些都会影响并发性能。第四是安全相关配置。除了上面提到的SECURE_PROXY_SSL_HEADER还有CSRF_TRUSTED_ORIGINSDjango 4.0、SESSION_COOKIE_SECURE、CSRF_COOKIE_SECURE等在HTTPS环境下要打开。密钥SECRET_KEY不要硬编码在代码里用环境变量或者独立的配置文件读取。import os DEBUG False ALLOWED_HOSTS [yourdomain.com, 192.168.1.100] SECRET_KEY os.environ.get(DJANGO_SECRET_KEY) STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, staticfiles) MEDIA_URL /media/ MEDIA_ROOT os.path.join(BASE_DIR, media) SECURE_PROXY_SSL_HEADER (HTTP_X_FORWARDED_PROTO, https) CSRF_TRUSTED_ORIGINS [https://yourdomain.com]这些配置写完之后建议用python manage.py check --deploy做一次部署检查Django会给出安全建议按提示逐项处理。4.2 WSGI服务器的启动与管理Gunicorn的启动命令不复杂但要跑得稳参数得调一调gunicorn yourproject.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 4 \ --threads 2 \ --timeout 60 \ --access-logfile /var/log/gunicorn/access.log \ --error-logfile /var/log/gunicorn/error.log \ --daemonworkers的数量有个经验公式(2 * CPU核心数) 1。比如你服务器是2核那就设5个worker。但这不是死规定还要看你的应用是CPU密集型还是IO密集型。IO密集型的可以适当增加worker数CPU密集型的加多了反而因为上下文切换损失性能。--daemon让Gunicorn在后台运行但生产环境更推荐用systemd来管理这样开机自启、崩溃自动重启、日志统一管理都能搞定。systemd的service文件大概长这样[Unit] DescriptionGunicorn daemon for Django project Afternetwork.target [Service] Userwww-data Groupwww-data WorkingDirectory/path/to/your/project ExecStart/path/to/your/project/venv/bin/gunicorn \ --bind 127.0.0.1:8000 \ --workers 4 \ yourproject.wsgi:application Restartalways [Install] WantedBymulti-user.target写好之后systemctl daemon-reload、systemctl start gunicorn、systemctl enable gunicorn一套下来就稳了。崩溃自动拉起这个特性特别重要我遇到过半夜进程挂掉、第二天才发现的情况有了systemd的Restartalways这种事基本不会发生。关于django-streaminghttpresponse的content_type和content_disposition参数如果你在做文件下载或流式响应记得content_type要设置正确比如application/octet-stream用于二进制下载content_disposition用来控制浏览器是内联展示还是弹出下载框。这些响应经过Nginx代理时如果用了proxy_buffering on默认开启流式响应可能会被缓冲导致延迟大文件下载或SSE场景下建议对特定location关掉缓冲proxy_buffering off;。5. 完整部署流程与多项目方案5.1 从零到上线的一次性走通把前面所有环节串起来一次完整部署的流程是这样的。假设你有一台全新的Ubuntu服务器项目代码已经通过Git或者scp传到了/var/www/myproject。第一步装系统依赖sudo apt update sudo apt install python3-pip python3-venv nginx git -y第二步创建虚拟环境并安装项目依赖cd /var/www/myproject python3 -m venv venv source venv/bin/activate pip install -r requirements.txt pip install gunicorn第三步配置Django的settings.py确保DEBUGFalse、ALLOWED_HOSTS正确、数据库连接可用。第四步初始化数据库并收集静态文件python manage.py migrate python manage.py collectstatic --noinput第五步测试Gunicorn能否正常启动gunicorn myproject.wsgi:application --bind 127.0.0.1:8000看到监听日志且curl http://127.0.0.1:8000有响应说明应用侧没问题。第六步配置systemd服务让Gunicorn后台运行并开机自启。第七步配置Nginx在/etc/nginx/sites-available/myproject写好server块然后软链接到sites-enabled/sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx第八步检查防火墙放行80和443端口sudo ufw allow Nginx Full到这里浏览器访问你的域名或IP应该就能看到项目页面了。如果看不到按这个顺序排查Nginx错误日志 → Gunicorn日志 → Django日志 → 防火墙规则 → SELinuxCentOS系特有。这个排查顺序是我踩了无数坑之后总结出来的从外到内一层层剥基本能定位到问题。5.2 一台服务器部署多个项目的Nginx写法很多人手里只有一台服务器但想跑多个Django项目、甚至还有其他Web应用。Nginx天生支持这种场景有两种常用方案。方案一基于域名区分。每个项目有自己的域名Nginx通过server_name匹配不同请求。server { listen 80; server_name project-a.com; location / { proxy_pass http://127.0.0.1:8001; } } server { listen 80; server_name project-b.com; location / { proxy_pass http://127.0.0.1:8002; } }这套方案最干净项目之间完全隔离。缺点是每个域名都要单独解析没有域名的话用不了。方案二基于路径前缀区分。同一个域名下用不同的路径前缀分流。server { listen 80; server_name yourdomain.com; location /project-a/ { proxy_pass http://127.0.0.1:8001/; } location /project-b/ { proxy_pass http://127.0.0.1:8002/; } }注意这里proxy_pass末尾的斜杠加了斜杠会把/project-a/前缀剥掉再转发给后端。如果不加后端收到的路径会带着前缀Django的URL路由就要相应地加上前缀否则匹配不到。这两种做法都能用关键是要前后端一致别一边剥一边没配。这种路径分流的方案有个副作用Django里的绝对路径链接、STATIC_URL、表单提交地址可能都会出问题因为框架默认自己部署在根路径下。要么给每个项目配置FORCE_SCRIPT_NAME要么在Nginx层把路径处理好让Django以为自己在根路径。我个人的经验是如果不是有特别强的需求尽量用域名区分方案路径方案坑比较多调起来费时费力。不管用哪种方案静态文件的location块也要相应调整确保每个项目的静态文件路径指向正确。多个项目共用一台服务器时端口规划也要提前做好比如8001、8002、8003依次分配systemd服务名对应gunicorn-a、gunicorn-b避免混淆。6. 常见问题排查与实战避坑指南6.1 部署问题速查表部署过程中遇到问题是在所难免的我把这些年踩过的典型问题整理成一张表方便对照排查。现象常见原因排查方向502 Bad GatewayGunicorn没启动或崩了检查systemd状态、Gunicorn错误日志404 Not FoundURL路由不匹配或proxy_pass斜杠问题看Nginx access日志的请求路径对比Django URL配置静态文件全部404STATIC_ROOT路径错误或权限不足确认collectstatic已执行检查目录权限和Nginx alias路径页面样式错乱STATIC_URL和Nginx location不匹配浏览器开发者工具看资源请求路径核对配置重定向次数过多HTTPS配置与Django协议判断冲突检查SECURE_PROXY_SSL_HEADER设置CSRF验证失败域名不在信任列表或Cookie Secure设置错误检查CSRF_TRUSTED_ORIGINS和CSRF_COOKIE_SECURE上传大文件失败Nginx client_max_body_size限制调大client_max_body_size或Django的DATA_UPLOAD_MAX_MEMORY_SIZE中文乱码编码配置不一致检查Nginx charset、Django DEFAULT_CHARSET、响应头请求慢但CPU不高数据库连接或外部API阻塞检查慢查询日志、连接池配置内存持续增长worker泄漏或缓存未清理设置max_requests让worker定期重启这张表里的每一条我几乎都实际遇到过尤其是502和静态文件404这两个新手部署时出现的频率极高。502十有八九是Gunicorn没起来先systemctl status gunicorn看一眼再翻/var/log/gunicorn/error.log基本就能定位。静态文件404则多半是collectstatic没跑或者跑到了错误的目录进服务器确认一下文件到底在哪。6.2 那些文档里不会写的实操心得讲几条我在实际部署中总结出来的、教程里很少提但特别有用的经验。第一条是关于日志的。Nginx默认的access日志格式信息有限看不到上游响应时间。建议自定义log_format把$upstream_response_time、$request_time、$upstream_addr都加上这样排查性能问题时能一眼看出是Nginx慢还是后端慢。log_format detailed $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent rt$request_time urt$upstream_response_time ua$upstream_addr; access_log /var/log/nginx/access.log detailed;第二条是Gunicorn的--max-requests参数。Python应用跑久了难免有内存泄漏与其等它把服务器吃满不如设置--max-requests 1000 --max-requests-jitter 100让每个worker处理一定数量请求后自动重启。这个技巧看起来不起眼但能避免很多“跑几天就变慢”的诡异问题重启间隙设个抖动值还能防止所有worker同时重启造成服务中断。第三条是关于collectstatic的。很多人改了静态文件之后忘了重新收集导致线上还是旧版本。建议把collectstatic加到部署脚本里每次部署自动执行或者用CI/CD流水线固化这个步骤。另外如果用了ManifestStaticFilesStorage文件会带哈希指纹配合Nginx的长缓存策略效果最好但要注意哈希变化后旧文件会被清理如果有外部引用旧路径的地方需要留意。第四条是关于hexo部署到github这类静态站点和Django项目共存的情况。如果同一个域名下既要跑Django又要挂静态博客可以用Nginx的location优先级来做精确匹配某个路径走静态目录其余走反向代理。location /blog/这种精确匹配优先级最高location ^~ /static/前缀匹配次之正则和普通前缀再往后排。搞清楚这个优先级顺序配置共存的站点就不会互相抢流量。第五条是关于代码热更新的。生产环境改代码后Django不会自动重载需要手动重启Gunicorn。我习惯用systemctl reload gunicorn或者发HUP信号这样能优雅地重启worker正在处理的请求不会被打断。直接restart会有短暂的服务中断用户能感知到。# 优雅重启Gunicorn sudo systemctl reload gunicorn # 或者 kill -HUP $(cat /path/to/gunicorn.pid)最后说一个关于离线安装的场景。有些内网服务器不能连外网装Nginx要么用系统源里的包要么下载rpm/deb包手动安装。如果是源码编译记得先把pcre、zlib、openssl这些依赖装好./configure的时候按需启用模块比如--with-http_ssl_module是HTTPS必需的。编译安装的Nginx不会自动生成systemd服务需要自己写一个unit文件路径也要注意别和系统自带的冲突。部署这件事说起来就是Nginx加Django加WSGI三件套但每一个环节的细节都能让你卡上半天。我的建议是第一次部署不要照抄别人的配置而是每写一行都问自己“这行是干嘛的”理解之后配置出问题你才知道从哪下手。把一套完整的部署流程走通一遍记录下来形成一个自己的checklist以后再部署新项目就是按流程执行而已十分钟就能搞定。这套东西一旦掌握无论是传统服务器还是容器化环境底层逻辑都是相通的。
返回列表