Django 部署与生产环境
2026/3/20大约 6 分钟
Django 部署与生产环境
一、生产环境配置
1.1 settings 分离
# settings/base.py(基础配置)
import os
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ...
]
# settings/development.py(开发环境)
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# settings/production.py(生产环境)
from .base import *
DEBUG = False
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT', '5432'),
}
}
# 安全设置
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# 静态文件
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# 日志
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'ERROR',
'class': 'logging.FileHandler',
'filename': '/var/log/django/error.log',
},
},
'root': {
'handlers': ['file'],
'level': 'ERROR',
},
}
1.2 环境变量管理
# 使用 python-dotenv
# pip install python-dotenv
# .env 文件
DJANGO_SECRET_KEY=your-secret-key
DEBUG=False
ALLOWED_HOSTS=example.com,www.example.com
DB_NAME=mydb
DB_USER=myuser
DB_PASSWORD=mypassword
DB_HOST=localhost
DB_PORT=5432
# settings.py
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# 或使用 django-environ
# pip install django-environ
import environ
env = environ.Env(
DEBUG=(bool, False)
)
environ.Env.read_env(BASE_DIR / '.env')
DEBUG = env('DEBUG')
SECRET_KEY = env('SECRET_KEY')
DATABASES = {
'default': env.db(),
}
1.3 生产检查清单
# 运行部署检查
python manage.py check --deploy
# 常见检查项
# SECURE_HSTS_SECONDS
# SECURE_SSL_REDIRECT
# SESSION_COOKIE_SECURE
# CSRF_COOKIE_SECURE
# DEBUG = False
# ALLOWED_HOSTS
二、WSGI 服务器
2.1 Gunicorn
# 安装
pip install gunicorn
# 基本运行
gunicorn myproject.wsgi:application
# 生产配置
gunicorn myproject.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 4 \
--threads 2 \
--worker-class gthread \
--timeout 120 \
--keep-alive 5 \
--max-requests 1000 \
--max-requests-jitter 50 \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
--capture-output \
--enable-stdio-inheritance
# gunicorn.conf.py
import multiprocessing
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gthread"
threads = 2
timeout = 120
keepalive = 5
max_requests = 1000
max_requests_jitter = 50
# 日志
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
capture_output = True
enable_stdio_inheritance = True
# 进程管理
daemon = False
pidfile = "/var/run/gunicorn/gunicorn.pid"
user = "www-data"
group = "www-data"
# 钩子
def on_starting(server):
print("Gunicorn starting...")
def on_exit(server):
print("Gunicorn exiting...")
2.2 uWSGI
# 安装
pip install uwsgi
# 运行
uwsgi --http :8000 --module myproject.wsgi
# uwsgi.ini
[uwsgi]
# 项目目录
chdir = /var/www/myproject
module = myproject.wsgi:application
# 进程设置
master = true
processes = 4
threads = 2
enable-threads = true
# Socket
socket = /var/run/uwsgi/myproject.sock
chmod-socket = 664
vacuum = true
# 日志
logto = /var/log/uwsgi/myproject.log
# 性能优化
harakiri = 120
max-requests = 5000
buffer-size = 32768
# 用户权限
uid = www-data
gid = www-data
# 虚拟环境
virtualenv = /var/www/myproject/venv
三、Nginx 配置
3.1 基础配置
# /etc/nginx/sites-available/myproject
upstream django {
server 127.0.0.1:8000;
# 或 socket
# server unix:///var/run/gunicorn/gunicorn.sock;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL 证书
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# 日志
access_log /var/log/nginx/myproject_access.log;
error_log /var/log/nginx/myproject_error.log;
# 静态文件
location /static/ {
alias /var/www/myproject/staticfiles/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# 媒体文件
location /media/ {
alias /var/www/myproject/media/;
expires 7d;
}
# 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_connect_timeout 30;
proxy_read_timeout 120;
}
# WebSocket 支持
location /ws/ {
proxy_pass http://django;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# 限制上传大小
client_max_body_size 10M;
}
3.2 性能优化
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# 基础设置
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
# 缓存
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 限流
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
}
四、Docker 部署
4.1 Dockerfile
# Dockerfile
FROM python:3.11-slim
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV DJANGO_SETTINGS_MODULE=myproject.settings.production
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
build-essential \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 收集静态文件
RUN python manage.py collectstatic --noinput
# 创建非 root 用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "--config", "gunicorn.conf.py", "myproject.wsgi:application"]
4.2 Docker Compose
# docker-compose.yml
version: "3.8"
services:
web:
build: .
command: gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
expose:
- 8000
env_file:
- .env
depends_on:
- db
- redis
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
redis:
image: redis:6-alpine
volumes:
- redis_data:/data
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- static_volume:/app/staticfiles
- media_volume:/app/media
- ./ssl:/etc/nginx/ssl
depends_on:
- web
celery:
build: .
command: celery -A myproject worker -l info
env_file:
- .env
depends_on:
- db
- redis
celery-beat:
build: .
command: celery -A myproject beat -l info
env_file:
- .env
depends_on:
- db
- redis
volumes:
postgres_data:
redis_data:
static_volume:
media_volume:
4.3 多阶段构建
# 构建阶段
FROM python:3.11-slim as builder
WORKDIR /app
RUN apt-get update && apt-get install -y build-essential libpq-dev
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /app/wheels -r requirements.txt
# 运行阶段
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
RUN apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*
COPY /app/wheels /wheels
RUN pip install --no-cache /wheels/*
COPY . .
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
五、系统服务
5.1 Systemd 服务
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon for Django
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
Environment="PATH=/var/www/myproject/venv/bin"
Environment="DJANGO_SETTINGS_MODULE=myproject.settings.production"
ExecStart=/var/www/myproject/venv/bin/gunicorn \
--config /var/www/myproject/gunicorn.conf.py \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
# 启动服务
sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn
# 重启服务
sudo systemctl restart gunicorn
# 查看日志
sudo journalctl -u gunicorn -f
5.2 Supervisor
# /etc/supervisor/conf.d/myproject.conf
[program:django]
command=/var/www/myproject/venv/bin/gunicorn myproject.wsgi:application -c /var/www/myproject/gunicorn.conf.py
directory=/var/www/myproject
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/django.log
[program:celery]
command=/var/www/myproject/venv/bin/celery -A myproject worker -l info
directory=/var/www/myproject
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/celery.log
[program:celery-beat]
command=/var/www/myproject/venv/bin/celery -A myproject beat -l info
directory=/var/www/myproject
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/supervisor/celery-beat.log
[group:myproject]
programs=django,celery,celery-beat
六、数据库管理
6.1 数据库备份
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/var/backups/myproject"
DB_NAME="mydb"
DB_USER="myuser"
# PostgreSQL 备份
pg_dump -U $DB_USER -h localhost $DB_NAME | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# 保留最近 7 天备份
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +7 -delete
# 上传到 S3
aws s3 cp $BACKUP_DIR/db_$DATE.sql.gz s3://my-backups/db/
6.2 数据库迁移
# 生产环境迁移
python manage.py migrate --plan # 预览迁移
python manage.py migrate # 执行迁移
# 安全迁移脚本
#!/bin/bash
set -e
# 备份数据库
pg_dump -U $DB_USER $DB_NAME > backup.sql
# 执行迁移
python manage.py migrate
# 如果失败则回滚
if [ $? -ne 0 ]; then
psql -U $DB_USER $DB_NAME < backup.sql
exit 1
fi
七、监控与日志
7.1 日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'json': {
'class': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(levelname)s %(name)s %(message)s',
},
},
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/app.log',
'maxBytes': 10485760, # 10MB
'backupCount': 10,
'formatter': 'verbose',
},
'error_file': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/error.log',
'maxBytes': 10485760,
'backupCount': 10,
'formatter': 'verbose',
},
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler',
},
},
'loggers': {
'django': {
'handlers': ['file', 'error_file'],
'level': 'INFO',
'propagate': True,
},
'django.request': {
'handlers': ['error_file', 'mail_admins'],
'level': 'ERROR',
'propagate': False,
},
},
}
7.2 健康检查
# views.py
from django.http import JsonResponse
from django.db import connection
def health_check(request):
"""健康检查端点"""
health = {
'status': 'healthy',
'database': check_database(),
'cache': check_cache(),
}
status_code = 200 if all(health.values()) else 503
return JsonResponse(health, status=status_code)
def check_database():
try:
with connection.cursor() as cursor:
cursor.execute('SELECT 1')
return True
except Exception:
return False
def check_cache():
from django.core.cache import cache
try:
cache.set('health_check', 'ok', 1)
return cache.get('health_check') == 'ok'
except Exception:
return False
# urls.py
urlpatterns = [
path('health/', views.health_check, name='health_check'),
]
八、CI/CD
8.1 GitHub Actions 部署
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run tests
run: |
pip install -r requirements.txt
python manage.py test
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/myproject
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart gunicorn
8.2 零停机部署
#!/bin/bash
# deploy.sh
set -e
APP_DIR="/var/www/myproject"
BACKUP_DIR="/var/www/backup"
# 备份当前版本
cp -r $APP_DIR $BACKUP_DIR/$(date +%Y%m%d_%H%M%S)
cd $APP_DIR
# 拉取新代码
git pull origin main
# 安装依赖
source venv/bin/activate
pip install -r requirements.txt
# 数据库迁移
python manage.py migrate
# 收集静态文件
python manage.py collectstatic --noinput
# 优雅重启 Gunicorn
sudo systemctl reload gunicorn
# 验证部署
sleep 5
if ! curl -f http://localhost:8000/health/; then
echo "Deployment failed, rolling back..."
# 回滚逻辑
exit 1
fi
echo "Deployment successful!"
九、安全加固
9.1 服务器安全
# 防火墙配置
sudo ufw allow 22
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
# 禁用 root SSH
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# 自动安全更新
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
9.2 Django 安全设置
# settings/production.py
DEBUG = False
SECRET_KEY = os.environ.get('SECRET_KEY')
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# HTTPS
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# Cookie 安全
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
# HSTS
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# 其他
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'