#Django部署最佳实践 - 生产环境部署与运维
📂 所属阶段:第三部分 — 高级主题
🎯 难度等级:高级
⏰ 预计学习时间:6-8小时
🎒 前置知识:性能优化, 安全最佳实践
#目录
#部署核心架构与原则
#极简生产架构
我们不需要一开始就上AWS ELB这种重型组件,先从单机Docker可扩展架构切入:
Internet
↓
Nginx (反向代理、SSL、静态文件)
↓
Gunicorn Workers (WSGI服务)
↓
PostgreSQL (主数据库)
↓
Redis (缓存、异步队列broker)
↓
Celery (可选:异步任务)#5条铁则
必记原则
- 安全第一:DEBUG必须False、最小权限、强制HTTPS
- 高可用雏形:多Gunicorn Worker、进程自动重启
- 可快速扩容:预留Docker Swarm/K8s接口
- 基础监控要到位:日志、进程存活、资源使用率
- 自动化半自动化结合:先能用Shell脚本,再上CI/CD :::
#轻量级环境准备
这里只列通用Linux服务器必须的环境,不需要的依赖(比如HTOP可以自己选)已经砍掉:
:::code-group
#!/bin/bash
sudo apt update && sudo apt upgrade -y
# 安装生产核心依赖
sudo apt install -y \
python3-pip python3-venv python3-dev \
build-essential libpq-dev libssl-dev \
nginx supervisor git curl
# 创建非root应用用户(安全必做)
sudo useradd --system --home /app --shell /bin/bash app
sudo mkdir -p /app /var/log/myapp /var/run/myapp
sudo chown -R app:app /app /var/log/myapp /var/run/myapp
# 防火墙配置
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw --force enable# .env 放在项目根目录,不要提交到Git!
DEBUG=False
SECRET_KEY=your-very-long-random-secret-key-here
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
# 数据库
DB_NAME=myapp
DB_USER=myappuser
DB_PASSWORD=your-strong-db-password
DB_HOST=db
DB_PORT=5432
DB_CONN_MAX_AGE=60
# Redis
REDIS_URL=redis://redis:6379/0#Docker Compose全栈部署
这是最快能跑的单机生产级方案,以后可以直接升级到Swarm/K8s:
:::code-group
FROM python:3.11-slim
# 提前设置环境变量减少构建层
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DJANGO_SETTINGS_MODULE=myproject.settings.production
# 安装系统依赖(合并成一层)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev libjpeg-dev zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# 复制依赖(缓存层,只在requirements.txt变化时重建)
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 切换非root用户
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
# 收集静态文件(这里是生产模式,提前执行)
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "--config", "gunicorn.conf.py", "myproject.wsgi:application"]Django>=4.2,<5.0
gunicorn==21.2.0
psycopg2-binary==2.9.7
redis==5.0.1
celery==5.3.4
django-redis==5.4.0
Pillow==10.0.1
python-decouple==3.8
whitenoise==6.6.0version: '3.8'
services:
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
- ./postgres/init:/docker-entrypoint-initdb.d/ # 可选:初始化脚本
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-}
volumes:
- redis_data:/data
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
web:
build: .
ports:
- "127.0.0.1:8000:8000" # 只暴露给本地Nginx
environment:
- DB_HOST=db
- REDIS_URL=${REDIS_URL}
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
volumes:
- static_volume:/app/static
- media_volume:/app/media
networks:
- app-network
restart: unless-stopped
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
- static_volume:/app/static:ro
- media_volume:/app/media:ro
- ./ssl:/etc/ssl:ro
depends_on:
- web
networks:
- app-network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_volume:
media_volume:
networks:
app-network:
driver: bridge:::
#基础但高效的Nginx配置
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式带时间戳
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$request_time"';
access_log /var/log/nginx/access.log main;
# 基础性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
client_max_body_size 16M;
# Gzip压缩静态资源
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/javascript application/json application/javascript image/svg+xml;
include /etc/nginx/conf.d/*.conf;
}# nginx/conf.d/myapp.conf
upstream django {
server web:8000;
}
# 强制HTTPS重定向
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL证书(建议用Certbot免费申请)
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
# 安全头(必加)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# 静态文件(只读)
location /static/ {
alias /app/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# 媒体文件(禁止执行脚本)
location /media/ {
alias /app/media/;
expires 30d;
add_header Cache-Control "public";
location ~* \.(php|py|sh)$ { deny all; return 404; }
}
# 代理到Django
location / {
proxy_pass http://django;
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;
proxy_read_timeout 60s;
proxy_buffering on;
}
# 健康检查端点
location /health/ {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}#Gunicorn配置与优化
# gunicorn.conf.py
import multiprocessing
# 核心配置
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1 # 通用公式,CPU密集型可减少
worker_class = "sync" # 普通应用用sync足够,I/O密集型用gevent
worker_connections = 1000
timeout = 30
max_requests = 1000 # 防止内存泄漏,随机重启
max_requests_jitter = 100
# 性能优化
preload_app = True # 预加载Django,减少Worker启动内存
tmp_upload_dir = "/dev/shm" # 用内存临时目录#PostgreSQL生产安全与备份
#快速备份脚本(可选Celery定时)
# myapp/management/commands/backup_db.py
import os
import datetime
import subprocess
from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = "备份PostgreSQL数据库"
def handle(self, *args, **options):
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
backup_dir = '/backups'
os.makedirs(backup_dir, exist_ok=True)
backup_file = f"{backup_dir}/myapp_{timestamp}.sql.gz"
try:
# 带压缩的备份命令
cmd = [
'pg_dump',
'-h', settings.DATABASES['default']['HOST'],
'-U', settings.DATABASES['default']['USER'],
'-d', settings.DATABASES['default']['NAME'],
]
env = os.environ.copy()
env['PGPASSWORD'] = settings.DATABASES['default']['PASSWORD']
with open(backup_file, 'wb') as f:
proc1 = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
proc2 = subprocess.Popen(['gzip'], stdin=proc1.stdout, stdout=f)
proc1.stdout.close()
proc2.wait()
# 清理7天前的备份
cutoff = datetime.datetime.now() - datetime.timedelta(days=7)
for filename in os.listdir(backup_dir):
file_path = os.path.join(backup_dir, filename)
if os.path.getmtime(file_path) < cutoff.timestamp():
os.remove(file_path)
self.stdout.write(self.style.SUCCESS(f"备份成功: {backup_file}"))
except Exception as e:
self.stderr.write(self.style.ERROR(f"备份失败: {str(e)}"))#本章小结
今天我们完成了Django单机生产级部署的闭环:
- 先搞懂了极简架构和5条铁则
- 配置了轻量级的服务器和Python环境
- 用Docker Compose搭好了全栈环境(PostgreSQL、Redis、Nginx、Gunicorn)
- 写了基础但安全高效的Nginx和Gunicorn配置
- 加了PostgreSQL的定时备份脚本
下一步
生产环境上线前,一定要去申请免费的Certbot SSL证书,测试所有功能,配置Supervisor(如果不用Docker的重启策略),再加个简单的监控比如Prometheus+Grafana!

