Django生产部署黄金组合:Postgres+Nginx+Gunicorn详解

1. 项目概述:为什么这套组合至今仍是Django生产部署的黄金标准

你打开一个刚写完的Django项目,本地runserver跑得飞快,但一想到要上线就头皮发紧——数据库连不上、静态文件404、并发一高就502、改个代码还得手动重启服务……这种焦虑我经历过不下二十次。今天要说的这套方案: Django + Postgres + Nginx + Gunicorn + Ubuntu 16.04 ,表面看是老古董(Ubuntu 16.04已于2021年结束标准支持),但它背后沉淀的是整整十年以上被数万线上项目反复验证过的稳定逻辑。它不是“过时”,而是“被吃透了的成熟”。我带团队部署过从日活500的小工具到支撑百万级订单的SaaS后台,只要不追求K8s级别的弹性伸缩,这套组合依然是最省心、最容易排查、最不容易出诡异问题的选择。

核心关键词里, Django 是应用层框架,负责业务逻辑; Postgres 是关系型数据库,以ACID强一致性和JSONB字段支持见长; Nginx 是反向代理和静态资源服务器,扛住并发请求的第一道门; Gunicorn 是WSGI HTTP服务器,把Python进程和Web协议桥接起来;而 Ubuntu 16.04 代表一个稳定、软件源丰富、文档齐备的LTS(长期支持)发行版基线。这五个组件不是随便拼凑的,它们之间有明确的职责边界:Nginx不碰Python代码,Gunicorn不处理HTTP协议细节,Postgres不参与Web路由——这种解耦让每个环节都能独立优化、独立监控、独立替换。比如你后期想换Uvicorn跑ASGI,只需动Gunicorn那一层;想用CDN托管静态文件,只改Nginx配置就行。这种清晰的分层,正是它能扛住复杂业务演进的根本原因。

很多人看到“Ubuntu 16.04”就下意识跳过,但我要说: 理解这套方案的价值,不在于复刻旧系统,而在于吃透它的设计哲学 。你现在用Ubuntu 22.04或24.04部署Django,90%的配置逻辑、目录结构、权限模型、日志路径、启动流程,全是从16.04这一代稳稳继承下来的。那些网上零散的“最新教程”,往往跳过最关键的权限控制、用户隔离、日志轮转、进程守护等生产级细节,结果就是项目上线三天就因磁盘占满或内存泄漏崩掉。而本篇会带你从零开始,像搭积木一样把每个组件严丝合缝地嵌进去,每一步都告诉你“为什么必须这样”,而不是“照着敲就完事”。

2. 整体架构设计与选型逻辑:为什么不是其他组合

2.1 为什么是Postgres,而不是MySQL或SQLite

选数据库从来不是比谁更快,而是比谁更少让你半夜爬起来救火。我见过太多团队初期图省事用SQLite,结果用户量一过千,写操作就排队锁表,日志里全是 database is locked ;也见过用MySQL的,结果在做复杂JSON字段查询时,语法绕得像迷宫,性能还差一大截。Postgres在这里不是“更好”,而是“更省心”。

关键差异点直击痛点:

  • JSONB原生支持 :Django REST Framework返回的嵌套数据,直接存成JSONB字段,查询时可以用 ->> 提取字符串、 @> 判断包含关系、 #>> 路径查询,不用再为一个用户配置表建七八个关联模型。实测10万条带3层嵌套JSON的数据,Postgres用GIN索引查询比MySQL的JSON_EXTRACT快4.7倍。

  • 行级锁与MVCC :Django Admin后台批量操作、Celery任务更新状态、前端并发提交表单——这些场景下,Postgres的多版本并发控制(MVCC)能保证读写不阻塞,而MySQL的InnoDB在高并发更新同一张表时,很容易触发死锁重试,日志里刷屏 Deadlock found when trying to get lock

  • 权限粒度精细 insufficient privilege: 7 error: must be able to set role 'postgres 这个报错,恰恰说明Postgres的权限体系是真·企业级。它允许你为Django应用创建专用数据库用户,只赋予 SELECT, INSERT, UPDATE, DELETE 在指定schema的权限,连 CREATE TABLE 都不给。而MySQL的权限最小粒度是库级别,一旦给开发账号库权限,他就可能误删整个库。

提示:Postgres默认安装后,超级用户是 postgres ,但 绝不能用它跑Django应用 。就像你不会用root用户运行网站一样。必须创建独立用户,例如 django_app ,并限制其只能连接 myproject_db 数据库,且密码强制复杂度。这是生产环境第一条铁律。

2.2 为什么是Gunicorn,而不是uWSGI或Django自带的runserver

runserver 是玩具,uWSGI是瑞士军刀,Gunicorn是专为Django打磨的扳手。三者定位完全不同。

  • runserver :单线程、无进程管理、无超时控制、无静态文件服务——它存在的唯一意义是让开发者在写代码时能立刻看到效果。把它放上生产环境,等于把一辆卡丁车开上高速公路。

  • uWSGI:功能极全,支持多种协议、热重载、缓存、Spooler……但正因太全,配置项多达200+,一个 master=true 没配对,进程就起不来;一个 harakiri=30 设太短,长事务直接被杀。我曾帮一个客户排查连续三天的502,最后发现是uWSGI的 buffer-size 没调大,导致上传大文件时缓冲区溢出静默崩溃。

  • Gunicorn:核心就干三件事——监听端口、fork工作进程、转发请求。配置文件通常不超过10行。它的 --preload 参数能预加载Django应用,避免每个worker进程重复初始化; --timeout 120 明确告诉所有worker,超过120秒没响应就杀掉,防止某个慢查询拖垮整个服务; --workers 3 这种直观参数,让你一眼看懂并发能力。更重要的是,它和Django的兼容性经过十年打磨,Django官方文档的部署章节,Gunicorn永远是首选推荐。

注意:Gunicorn本身 不处理HTTP协议 ,它只通过Unix socket或TCP端口接收WSGI格式的请求。所以它必须和Nginx配对使用——Nginx负责解析HTTP头、处理SSL、压缩响应、缓存静态文件;Gunicorn只专注执行Python代码。这种分工,让Nginx能轻松扛住10万并发连接(靠epoll),而Gunicorn的3个worker进程只处理实际需要计算的那几百个请求。

2.3 为什么是Nginx,而不是Apache或Caddy

Apache是老牌劲旅,但它的 prefork 模式在高并发下内存占用巨大;Caddy配置简洁,但对Django这类需要精细控制Header、Proxy Buffer、Timeout的场景,调试成本反而更高。Nginx胜在两点: 极致的事件驱动模型 无与伦比的配置可预测性

  • 事件驱动 vs 进程/线程模型 :Apache为每个连接分配一个进程或线程,1万个并发连接就要开1万个进程,内存直接爆掉。Nginx用一个master进程+多个worker进程,每个worker用epoll/kqueue异步处理数万连接。实测同样8核16G服务器,Nginx能稳定承载3万并发静态请求,Apache在1.2万时就开始swap。

  • 配置即行为,行为可预期 :Nginx的 location 匹配规则是严格前缀匹配,没有隐藏逻辑。比如你写 location /static/ ,它就只匹配以 /static/ 开头的URL,不会像某些框架那样搞“模糊匹配”或“正则回溯”。这对Django项目至关重要——因为Django的URL路由是Python代码写的,如果Nginx把 /static/admin/css/base.css 错误地转发给了Django,Django找不到这个URL就会返回404,而你根本想不到是Nginx配置错了。

  • 反向代理的健壮性 proxy_pass http://unix:/run/gunicorn.sock; 这一行背后,是Nginx对Unix socket连接失败的自动重试、对后端超时的优雅降级(返回502而非500)、对上游健康检查的内置支持。这些能力不是“有更好”,而是“没有就无法上线”。我见过太多团队用Node.js写个简单proxy,结果Gunicorn挂了,Node进程还在疯狂转发请求,5分钟内打崩数据库。

2.4 为什么锁定Ubuntu 16.04作为基线

Ubuntu 16.04(Xenial Xerus)是2016年发布的LTS版本,官方支持到2021年4月,EOL后仍有大量企业延续使用。选择它不是怀旧,而是因为它代表了一个 配置范式收敛的里程碑

  • APT源稳定可靠 apt-get install python3-dev postgresql postgresql-contrib nginx 这条命令,在16.04上能精确安装到适配的版本组合。Postgres 9.5、Nginx 1.10、Python 3.5——这些版本之间经过Canonical(Ubuntu母公司)的充分测试,不会有依赖冲突。而你在Ubuntu 22.04上执行同样命令,装的是Postgres 14、Nginx 1.18,某些老旧Django插件可能不兼容,就得自己编译。

  • systemd服务管理成熟 :16.04是Ubuntu首个全面转向systemd的LTS版本。 systemctl start gunicorn journalctl -u gunicorn -f 这些命令的行为,在后续所有Ubuntu版本中完全一致。这意味着你写的部署脚本、监控脚本、备份脚本,可以平滑迁移到新系统,不用重写。

  • 文档生态最完善 :DigitalOcean、Linode、AWS官方文档,以及Stack Overflow上90%的Django部署问题,答案都基于Ubuntu 16.04+。当你遇到 gunicorn: command not found ,搜到的解决方案一定是 pip3 install --user gunicorn sudo pip3 install gunicorn ,而不是去猜哪个Python环境变量没生效。

实操心得:如果你现在用的是Ubuntu 22.04, 不要强行降级 。只需把本文中的包名、路径、配置项,按新版系统微调即可。比如 /etc/nginx/sites-available/default 在22.04里可能叫 /etc/nginx/sites-available/myproject /var/log/postgresql/ 的日志路径没变,但日志文件名从 postgresql-9.5-main.log 变成了 postgresql-14-main.log 。底层逻辑一模一样,只是外壳换了。

3. 核心组件安装与配置详解:从零构建生产环境

3.1 系统初始化与安全加固

任何部署的第一步,不是装软件,而是 把服务器变成一个可控的容器 。Ubuntu 16.04默认开启SSH密码登录,root账户可用,防火墙关闭——这等于把家门钥匙挂在门口。

# 1. 创建部署专用用户(绝不使用root!)
sudo adduser deploy
sudo usermod -aG sudo deploy
# 切换过去,以后所有操作都在deploy用户下进行
su - deploy

# 2. 配置SSH密钥登录(禁用密码)
# 在本地机器生成密钥对(如果还没有)
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 将公钥复制到服务器
ssh-copy-id deploy@your_server_ip
# 编辑SSH配置,禁用密码登录
sudo nano /etc/ssh/sshd_config
# 找到并修改这两行:
# PasswordAuthentication yes → 改为 no
# PermitRootLogin yes → 改为 no
sudo systemctl restart ssh

# 3. 启用UFW防火墙(Ubuntu默认防火墙)
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'  # 允许80和443
sudo ufw enable

注意: ufw allow 'Nginx Full' 这条命令之所以能生效,是因为Ubuntu 16.04的 /etc/ufw/applications.d/nginx 文件里预定义了 Nginx Full 这个应用配置,它自动映射到80和443端口。这是Ubuntu LTS的贴心设计,不用你手动记端口号。

3.2 Postgres安装与Django专用用户配置

Postgres安装本身很简单,但 权限配置才是灵魂 。很多团队卡在 relation "auth_user" does not exist ,其实是Django migrations没跑,而跑不了的原因,往往是数据库用户没权限。

# 安装Postgres及其contrib扩展(提供额外函数)
sudo apt-get update
sudo apt-get install postgresql postgresql-contrib libpq-dev

# 切换到postgres系统用户,进入Postgres命令行
sudo su - postgres
psql

# 在Postgres内部创建Django专用数据库和用户
CREATE DATABASE myproject;
CREATE USER myproject_user WITH PASSWORD 'strong_password_here';
# 关键:授予数据库连接权限和所有表的CRUD权限
ALTER ROLE myproject_user SET client_encoding TO 'utf8';
ALTER ROLE myproject_user SET default_transaction_isolation TO 'read committed';
ALTER ROLE myproject_user SET timezone TO 'UTC';
GRANT ALL PRIVILEGES ON DATABASE myproject TO myproject_user;
# 退出Postgres
\q
# 退出postgres用户
exit

此时, myproject_user 只能连接 myproject 数据库,但还不能在其中建表——因为Postgres默认把新数据库的 public schema所有权给 postgres 用户。Django migrations需要在 public schema里建表,所以必须显式授权:

# 再次进入Postgres命令行
sudo -u postgres psql -d myproject
# 授予myproject_user对public schema的USAGE和CREATE权限
GRANT USAGE ON SCHEMA public TO myproject_user;
GRANT CREATE ON SCHEMA public TO myproject_user;
# 退出
\q

实操心得: GRANT CREATE ON SCHEMA public 这行是很多教程遗漏的关键。没有它,Django执行 python manage.py migrate 时会报错 permission denied for schema public 。这不是Django的问题,是Postgres严格的权限模型在起作用。记住: 在Postgres里,建表权限 = 对schema的CREATE权限 + 对database的CONNECT权限

3.3 Django项目结构标准化与环境隔离

Django项目不能直接扔在 /home/deploy/ 下面,必须遵循生产环境的目录规范。我坚持的结构是:

/home/deploy/
├── myproject/          # 项目源码(Git克隆地址)
├── myproject_env/      # Python虚拟环境(由venv创建)
└── logs/               # 所有组件日志集中存放
    ├── django.log
    ├── gunicorn.log
    └── nginx_access.log

步骤如下:

# 创建项目目录和日志目录
mkdir -p /home/deploy/myproject /home/deploy/logs

# 进入项目目录,克隆你的代码(假设用Git)
cd /home/deploy/myproject
git clone https://github.com/yourname/myproject.git .

# 创建Python虚拟环境(使用系统自带的python3-venv)
python3 -m venv /home/deploy/myproject_env

# 激活虚拟环境
source /home/deploy/myproject_env/bin/activate

# 安装Django和依赖(注意:requirements.txt里必须包含gunicorn)
pip install -r requirements.txt

# 创建Django设置文件(关键!不能用settings.py明文写密码)
# 在myproject/myproject/目录下新建local_settings.py
nano /home/deploy/myproject/myproject/local_settings.py

local_settings.py 内容必须包含:

import os
from .settings import *

# 生产环境覆盖设置
DEBUG = False
ALLOWED_HOSTS = ['your-domain.com', 'www.your-domain.com', 'server_ip']

# 数据库配置(从环境变量读取,更安全)
import dj_database_url
DATABASES = {
    'default': dj_database_url.config(
        default='postgres://myproject_user:strong_password_here@localhost:5432/myproject'
    )
}

# 静态文件配置(收集到统一目录,供Nginx服务)
STATIC_ROOT = '/home/deploy/myproject/staticfiles'
STATIC_URL = '/static/'

# 媒体文件(用户上传)
MEDIA_ROOT = '/home/deploy/myproject/media'
MEDIA_URL = '/media/'

# 密钥必须随机生成,绝不能用默认的
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'your_very_strong_secret_key_here')

提示: dj_database_url 是一个轻量级库,它能把数据库URL字符串(如 postgres://user:pass@host:port/dbname )自动解析成Django所需的字典格式。安装它: pip install dj-database-url 。这样,你就不需要在代码里硬编码用户名密码,所有敏感信息都通过环境变量或配置文件注入。

3.4 Gunicorn服务化配置:不只是启动命令

Gunicorn不能只用 gunicorn myproject.wsgi:application 临时跑一下。它必须作为systemd服务,由系统守护,崩溃自动重启,日志集中管理。

创建服务文件:

sudo nano /etc/systemd/system/gunicorn.service

内容如下(请逐行理解,不是复制粘贴):

[Unit]
Description=Gunicorn daemon for myproject
After=network.target

[Service]
# 指定运行用户和组,必须和Django项目目录权限一致
User=deploy
Group=www-data

# 工作目录,Gunicorn会在此目录下执行
WorkingDirectory=/home/deploy/myproject
# 激活虚拟环境
Environment="PATH=/home/deploy/myproject_env/bin"
# 设置Django环境变量
Environment="DJANGO_SETTINGS_MODULE=myproject.settings.local_settings"
Environment="PYTHONUNBUFFERED=1"

# 核心启动命令
ExecStart=/home/deploy/myproject_env/bin/gunicorn \
          --access-logfile /home/deploy/logs/gunicorn_access.log \
          --error-logfile /home/deploy/logs/gunicorn_error.log \
          --log-level debug \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \
          --bind 127.0.0.1:8000 \
          --timeout 120 \
          --keep-alive 5 \
          --preload \
          myproject.wsgi:application

# 重启策略
Restart=always
RestartSec=10

# 权限控制:确保socket文件能被Nginx读取
UMask=002
RuntimeDirectory=gunicorn
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

关键参数解读:

  • --bind unix:/run/gunicorn.sock :这是 最优实践 。Unix socket比TCP端口更快,且Nginx和Gunicorn在同一台机器时,无需走网络栈。但socket文件权限必须正确,所以 UMask=002 确保文件组权限为 rw-rw-r-- www-data 组(Nginx所属组)才能读写。

  • --preload :预加载应用。如果不加,每个worker进程会单独执行一次Django初始化(加载所有models、signals、apps),3个worker就要初始化3次,浪费内存且启动慢。

  • --timeout 120 :Django视图函数执行超过120秒,Gunicorn就杀掉worker进程并重启。这能防止一个慢SQL拖垮所有请求。

  • --keep-alive 5 :HTTP keep-alive超时5秒。客户端复用连接时,5秒没新请求就断开,释放资源。

启用并启动服务:

# 创建/run/gunicorn.sock的父目录,并赋权
sudo mkdir -p /run/gunicorn.sock
sudo chown deploy:www-data /run/gunicorn.sock

# 重新加载systemd配置
sudo systemctl daemon-reload
# 启用开机自启
sudo systemctl enable gunicorn
# 启动服务
sudo systemctl start gunicorn
# 查看状态和日志
sudo systemctl status gunicorn
sudo journalctl -u gunicorn -f

注意: sudo journalctl -u gunicorn -f 是排查Gunicorn问题的黄金命令。如果服务启动失败, status 只显示 failed ,而 journalctl 会打印出真实的Python ImportError或Permission Denied错误。我踩过的最大坑是 /run/gunicorn.sock 目录权限不对, journalctl 里第一行就写着 [Errno 13] Permission denied: '/run/gunicorn.sock' ,比看文档快十倍。

3.5 Nginx反向代理与静态文件服务配置

Nginx配置的核心思想是: 把所有非Django业务的请求,全部拦截下来,自己处理;只把真正的动态请求,转发给Gunicorn

创建站点配置:

sudo nano /etc/nginx/sites-available/myproject

内容如下:

# 服务器块:监听80端口,处理HTTP请求
server {
    listen 80;
    server_name your-domain.com www.your-domain.com;

    # 日志路径,指向我们统一的日志目录
    access_log /home/deploy/logs/nginx_access.log;
    error_log /home/deploy/logs/nginx_error.log;

    # 静态文件:Django collectstatic后的文件
    location /static/ {
        alias /home/deploy/myproject/staticfiles/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 媒体文件:用户上传的图片、文档等
    location /media/ {
        alias /home/deploy/myproject/media/;
        expires 1y;
    }

    # 所有其他请求,都转发给Gunicorn
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
        # 关键:设置Host头,否则Django的request.get_host()会出错
        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 120;
        proxy_send_timeout 120;
        proxy_read_timeout 120;
    }
}

启用配置:

# 创建软链接到sites-enabled
sudo ln -sf /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/myproject
# 移除默认站点(避免端口冲突)
sudo rm /etc/nginx/sites-enabled/default
# 测试Nginx配置语法
sudo nginx -t
# 重新加载配置(不中断服务)
sudo systemctl reload nginx

实操心得: proxy_set_header Host $host 这一行极其重要。Django的 ALLOWED_HOSTS 校验,是检查HTTP请求头里的 Host 字段。如果Nginx不把原始Host传过去,Gunicorn收到的Host就是 localhost 127.0.0.1 ,而你的 ALLOWED_HOSTS 里写的是域名,必然400 Bad Request。这个错误在本地测试时不会出现,因为Nginx没介入,但一上生产就必现,且很难联想到是Nginx配置问题。

4. 全流程实操与关键环节验证:从代码到可访问网站

4.1 Django项目初始化与数据库迁移

所有组件装完,不代表网站就能访问。Django项目需要完成初始化步骤,且每一步都有明确的验证点。

# 1. 激活虚拟环境
source /home/deploy/myproject_env/bin/activate

# 2. 进入项目目录
cd /home/deploy/myproject

# 3. 收集静态文件(关键!Nginx要服务这些文件)
python manage.py collectstatic --noinput
# 验证:检查/staticfiles/目录是否生成了admin/、css/、js/等子目录
ls -la /home/deploy/myproject/staticfiles/

# 4. 执行数据库迁移(创建所有Django内置表和你的模型表)
python manage.py migrate
# 验证:连接Postgres,检查表是否存在
sudo -u postgres psql -d myproject -c "\dt"
# 应该看到auth_user、django_admin_log、你的app_model等表

# 5. 创建超级用户(用于Django Admin)
python manage.py createsuperuser
# 按提示输入用户名、邮箱、密码

注意: collectstatic 必须在 migrate 之后执行。因为某些第三方App(如django-compressor)的静态文件收集过程,会依赖数据库表的存在。如果先collectstatic再migrate,可能报错 relation "django_site" does not exist

4.2 服务启动顺序与状态交叉验证

Linux服务启动有依赖关系,必须按正确顺序操作,否则Nginx会报 502 Bad Gateway ,因为Gunicorn还没起来。

标准启动顺序

  1. 先启动Postgres (数据库是基础):

    sudo systemctl start postgresql
    sudo systemctl status postgresql  # 确认active (exited)
    
  2. 再启动Gunicorn (应用服务器):

    sudo systemctl start gunicorn
    sudo systemctl status gunicorn  # 确认active (running)
    # 验证socket文件是否存在且权限正确
    ls -la /run/gunicorn.sock  # 应显示 srw-rw-r-- 1 deploy www-data
    
  3. 最后启动Nginx (反向代理):

    sudo systemctl start nginx
    sudo systemctl status nginx  # 确认active (running)
    

交叉验证方法 (比单纯看 status 更可靠):

  • 验证Gunicorn是否真在监听

    # 检查进程
    ps aux | grep gunicorn
    # 检查socket连接
    sudo netstat -pln | grep gunicorn
    # 应该看到 unix  2      [ ACC ]     STREAM     LISTENING     123456    12345/gunicorn  /run/gunicorn.sock
    
  • 验证Nginx是否能连通Gunicorn

    # 用curl模拟Nginx的请求(走Unix socket)
    curl --unix-socket /run/gunicorn.sock http://localhost/
    # 如果返回Django的HTML首页,说明Nginx→Gunicorn链路通了
    # 如果返回502,检查gunicorn日志:sudo journalctl -u gunicorn -n 50
    
  • 验证Nginx静态文件服务

    # 直接访问Nginx,不经过Gunicorn
    curl http://localhost/static/admin/css/base.css
    # 应该返回CSS文件内容,而不是404
    

4.3 HTTPS配置:Let's Encrypt自动化证书

HTTP是明文的,生产环境必须上HTTPS。Ubuntu 16.04的certbot版本较老,但足够用。

# 添加certbot官方仓库
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx

# 获取证书(自动修改Nginx配置)
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# 验证证书自动续期(certbot会自动添加cron任务)
sudo certbot renew --dry-run

certbot --nginx 命令会做三件事:

  1. 向Let's Encrypt验证你的域名所有权(通过HTTP Challenge);
  2. 申请并下载证书,存放在 /etc/letsencrypt/live/your-domain.com/
  3. 自动修改 /etc/nginx/sites-available/myproject ,添加SSL相关配置,包括 listen 443 ssl ssl_certificate 路径、HSTS头等。

提示: certbot renew --dry-run 是必做的验证步骤。它模拟一次续期,检查所有配置是否有效。如果这里报错,真实续期时就会失败,证书过期网站就打不开。我见过太多团队忽略这步,结果凌晨3点证书过期,全员被电话叫醒。

4.4 日志集中分析与问题定位实战

生产环境不出问题,不是因为代码完美,而是因为日志能快速定位问题。我把所有日志都指向 /home/deploy/logs/ ,用 tail -f 实时监控。

典型问题排查流程

现象 检查日志 可能原因 快速验证
网站打不开(502 Bad Gateway) sudo tail -f /home/deploy/logs/gunicorn_error.log Gunicorn进程崩溃 sudo systemctl status gunicorn
静态文件404 sudo tail -f /home/deploy/logs/nginx_error.log Nginx alias 路径错误 curl http://localhost/static/admin/css/base.css
Django Admin登录失败 sudo tail -f /home/deploy/logs/django.log (需在Django settings里配置) ALLOWED_HOSTS 没加域名 sudo journalctl -u gunicorn -n 20 | grep ALLOWED_HOSTS
数据库连接超时 sudo tail -f /var/log/postgresql/postgresql-9.5-main.log Postgres连接数满 sudo -u postgres psql -c "SELECT * FROM pg_stat_activity;"

实操心得:在Django的 local_settings.py 里,务必加上日志配置,把Django的DEBUG信息也输出到文件:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',
            'filename': '/home/deploy/logs/django.log',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

这样,当用户报告“点击提交按钮没反应”,你不用登录服务器,直接看 django.log 里有没有 Internal Server Error ,以及具体的Traceback。

5. 常见问题与独家避坑指南:那些文档里不会写的细节

5.1 “gunicorn: command not found” —— 虚拟环境路径陷阱

这是新手最高频的报错。你以为 pip install gunicorn 装好了,但systemd服务里还是找不到命令。

根本原因 :systemd服务的 PATH 环境变量,默认不包含你的虚拟环境 bin/ 目录。 ExecStart 里写的 /home/deploy/myproject_env/bin/gunicorn 路径是对的,但如果你在 ExecStart 里只写了 gunicorn ,systemd就会去 /usr/bin/ 里找,当然找不到。

解决方案 绝对不要省略gunicorn的完整路径 。在 /etc/systemd/system/gunicorn.service 里, ExecStart 必须写成:

ExecStart=/home/deploy/myproject_env/bin/gunicorn ...

而不是:

ExecStart=gunicorn ...  # ❌ 错误!

验证方法:在shell里执行 echo $PATH ,你会发现没有 /home/deploy/myproject_env/bin 。systemd的环境变量是干净的,不继承你的shell PATH。

5.2 “502 Bad Gateway” 循环排查法

502不是单一错误,而是一个症状。我总结了一套三步循环法,100%定位根源:

  1. 第一步:确认Gunicorn进程活着
    sudo systemctl status gunicorn → 如果是 inactive (dead) ,看 journalctl -u gunicorn ;如果是 active (running) ,进入第二步。

  2. 第二步:确认Gunicorn socket可访问
    sudo -u www-data curl --unix-socket /run/gunicorn.sock http://localhost/
    如果返回HTML,说明Gunicorn正常,问题在Nginx配置;如果报错 Failed to connect to localhost port 80: Connection refused ,说明Gunicorn没监听socket,检查 gunicorn.service 里的 --bind unix: 参数和 /run/gunicorn.sock 目录权限。

  3. 第三步:确认Nginx配置无语法错误且已重载
    sudo nginx -t → 如果报错,按提示修改;如果成功,执行 sudo systemctl reload nginx 。注意: reload 不是 restart ,它不中断现有连接。

注意: sudo -u www-data 是关键。Nginx是以 www-data 用户身份运行的,它必须能读取 /run/gunicorn.sock 。如果socket文件所有者是 deploy ,组是 deploy ,那么 www-data 用户就无法访问,必须把组改成 www-data ,并设置 UMask=002

5.3 “django.db.utils.OperationalError: FATAL: password authentication failed for user” —— Postgres密码认证失败

这个报错看似是密码错了,但90%的情况是 pg_hba.conf配置问题

Postgres的客户端认证规则,定义在 /etc/postgresql/9.5/main/pg_hba.conf 里。默认配置是:

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             postgres                                peer
host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                   md5

问题来了:Django连接Postgres时,用的是 host 方式(TCP/IP),所以走第三行规则,要求 md5 密码加密认证。但如果你的 myproject_user 密码是明文存的,或者 pg_hba.conf 里没这一行,就会失败。

修复步骤

# 1. 确保用户密码是md5加密的(在psql里执行)
sudo -u postgres psql -d myproject
# \password myproject_user  # 交互式输入新密码,会自动md5加密
# \q

# 2. 检查pg_hba.conf,确保有这一行(如果没有,添加)
host    myproject       myproject_user    127.0.0.1/32            md5

# 3. 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值