【高级指南】Django部署最佳实践:从开发到生产的全流程解析
前言:为什么正确的部署策略如此重要?
在Django项目的生命周期中,将应用从开发环境成功迁移到生产环境是一个关键节点。即使设计精良的应用程序,如果部署不当,也会面临性能瓶颈、安全漏洞和可靠性问题。根据最近的研究,超过60%的Web应用故障与部署配置不当有关,而非代码本身的缺陷。正确的部署策略不仅能确保应用稳定运行,还能优化资源利用,提高响应速度,并为未来的扩展奠定基础。
本文将全面探讨Django应用的部署最佳实践,从环境配置到持续集成,从单服务器部署到容器化微服务架构,帮助你构建安全、高效、可扩展的生产环境。无论你是刚刚完成第一个Django项目的开发者,还是需要优化现有部署架构的团队,这份详尽指南都将为你提供清晰的路线图和实用的解决方案。
1. 部署前的准备工作
在将Django应用部署到生产环境之前,需要进行一系列准备工作,确保应用已为生产环境做好准备。
1.1 环境分离与配置管理
良好的环境分离是成功部署的基础:
# myproject/settings/__init__.py
from .base import *
# 根据环境变量加载相应的设置文件
import os
env = os.environ.get('DJANGO_ENV', 'development')
if env == 'production':
from .production import *
elif env == 'staging':
from .staging import *
else:
from .development import *
基础设置文件:
# myproject/settings/base.py
import os
from pathlib import Path
# 构建路径
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# 核心设置
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用
'rest_framework',
'django_celery_beat',
# 项目应用
'myapp',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
WSGI_APPLICATION = 'myproject.wsgi.application'
ASGI_APPLICATION = 'myproject.asgi.application'
# 模板设置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# 认证设置
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]
# 国际化设置
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 静态文件设置
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
# 媒体文件设置
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
生产环境设置:
# myproject/settings/production.py
import os
from .base import *
from django.core.exceptions import ImproperlyConfigured
def get_env_variable(var_name):
"""从环境变量获取敏感设置"""
try:
return os.environ[var_name]
except KeyError:
error_msg = f"找不到环境变量 {var_name}"
raise ImproperlyConfigured(error_msg)
# 安全设置
DEBUG = False
SECRET_KEY = get_env_variable('DJANGO_SECRET_KEY')
ALLOWED_HOSTS = get_env_variable('DJANGO_ALLOWED_HOSTS').split(',')
# 数据库设置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': get_env_variable('DB_NAME'),
'USER': get_env_variable('DB_USER'),
'PASSWORD': get_env_variable('DB_PASSWORD'),
'HOST': get_env_variable('DB_HOST'),
'PORT': get_env_variable('DB_PORT'),
'CONN_MAX_AGE': 600,
'OPTIONS': {
'sslmode': 'require',
}
}
}
# 缓存设置
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': get_env_variable('REDIS_URL'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'PASSWORD': get_env_variable('REDIS_PASSWORD'),
}
}
}
# 会话设置
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 1209600 # 2周
SESSION_COOKIE_SAMESITE = 'Lax'
# 安全设置
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000 # 1年
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# 静态文件设置
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
1.2 部署前检查清单
创建部署前检查脚本:
# myproject/management/commands/pre_deploy_check.py
import os
import sys
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.conf import settings
import requests
class Command(BaseCommand):
help = '部署前安全和性能检查'
def handle(self, *args, **options):
self.stdout.write(self.style.MIGRATE_HEADING('开始部署前检查...'))
# 检查列表
checks = [
self.check_debug_setting,
self.check_secret_key,
self.check_allowed_hosts,
self.check_security_settings,
self.check_database_settings,
self.check_static_files,
self.check_migrations,
self.run_system_checks,
self.check_dependencies,
]
# 运行所有检查
success = True
for check in checks:
if not check():
success = False
if success:
self.stdout.write(self.style.SUCCESS('✅ 所有检查通过,应用可以部署!'))
else:
self.stdout.write(self.style.ERROR('❌ 一些检查失败,请在部署前解决这些问题!'))
sys.exit(1)
def check_debug_setting(self):
"""检查DEBUG设置"""
if settings.DEBUG:
self.stdout.write(self.style.ERROR('❌ DEBUG模式在生产环境中处于开启状态'))
return False
else:
self.stdout.write(self.style.SUCCESS('✅ DEBUG模式已关闭'))
return True
def check_secret_key(self):
"""检查SECRET_KEY是否安全"""
if not settings.SECRET_KEY:
self.stdout.write(self.style.ERROR('❌ SECRET_KEY未设置'))
return False
if len(settings.SECRET_KEY) < 50:
self.stdout.write(self.style.WARNING('⚠️ SECRET_KEY可能不够强壮(长度<50)'))
if settings.SECRET_KEY == 'your-secret-key-here' or 'secret' in settings.SECRET_KEY.lower():
self.stdout.write(self.style.ERROR('❌ SECRET_KEY使用了默认值或过于简单'))
return False
self.stdout.write(self.style.SUCCESS('✅ SECRET_KEY配置正确'))
return True
def check_allowed_hosts(self):
"""检查ALLOWED_HOSTS设置"""
if not settings.ALLOWED_HOSTS:
self.stdout.write(self.style.ERROR('❌ ALLOWED_HOSTS为空'))
return False
if '*' in settings.ALLOWED_HOSTS:
self.stdout.write(self.style.WARNING('⚠️ ALLOWED_HOSTS包含通配符"*",这在生产环境中不安全'))
self.stdout.write(self.style.SUCCESS(f'✅ ALLOWED_HOSTS已配置为: {", ".join(settings.ALLOWED_HOSTS)}'))
return True
def check_security_settings(self):
"""检查安全相关设置"""
all_passed = True
# 检查HTTPS设置
if not settings.SECURE_SSL_REDIRECT:
self.stdout.write(self.style.WARNING('⚠️ SECURE_SSL_REDIRECT未启用'))
all_passed = False
if not settings.SESSION_COOKIE_SECURE:
self.stdout.write(self.style.WARNING('⚠️ SESSION_COOKIE_SECURE未启用'))
all_passed = False
if not settings.CSRF_COOKIE_SECURE:
self.stdout.write(self.style.WARNING('⚠️ CSRF_COOKIE_SECURE未启用'))
all_passed = False
# 检查其他安全设置
if not settings.SECURE_HSTS_SECONDS:
self.stdout.write(self.style.WARNING('⚠️ SECURE_HSTS_SECONDS未设置'))
if not settings.SECURE_CONTENT_TYPE_NOSNIFF:
self.stdout.write(self.style.WARNING('⚠️ SECURE_CONTENT_TYPE_NOSNIFF未启用'))
if all_passed:
self.stdout.write(self.style.SUCCESS('✅ 安全设置已正确配置'))
return all_passed
def check_database_settings(self):
"""检查数据库设置"""
db = settings.DATABASES.get('default', {})
if db.get('ENGINE') == 'django.db.backends.sqlite3' and not settings.DEBUG:
self.stdout.write(self.style.ERROR('❌ 生产环境使用SQLite数据库'))
return False
if not db.get('CONN_MAX_AGE'):
self.stdout.write(self.style.WARNING('⚠️ 未配置数据库连接池(CONN_MAX_AGE)'))
self.stdout.write(self.style.SUCCESS('✅ 数据库设置有效'))
return True
def check_static_files(self):
"""检查静态文件设置"""
if not hasattr(settings, 'STATIC_ROOT') or not settings.STATIC_ROOT:
self.stdout.write(self.style.ERROR('❌ STATIC_ROOT未设置'))
return False
if not os.path.isabs(settings.STATIC_ROOT):
self.stdout.write(self.style.WARNING('⚠️ STATIC_ROOT不是绝对路径'))
if not hasattr(settings, 'STATICFILES_STORAGE') or settings.STATICFILES_STORAGE == 'django.contrib.staticfiles.storage.StaticFilesStorage':
self.stdout.write(self.style.WARNING('⚠️ 未使用生产级静态文件存储后端(如ManifestStaticFilesStorage)'))
self.stdout.write(self.style.SUCCESS('✅ 静态文件设置有效'))
return True
def check_migrations(self):
"""检查是否有待处理的迁移"""
try:
# 使用showmigrations命令检查
self.stdout.write('检查待处理的迁移...')
call_command('showmigrations', no_color=True, stdout=self.stdout)
self.stdout.write(self.style.SUCCESS('✅ 迁移检查完成,请确认所有迁移都已应用'))
return True
except Exception as e:
self.stdout.write(self.style.ERROR(f'❌ 检查迁移时出错: {str(e)}'))
return False
def run_system_checks(self):
"""运行Django系统检查"""
try:
self.stdout.write('运行系统检查...')
call_command('check', deploy=True, stdout=self.stdout)
self.stdout.write(self.style.SUCCESS('✅ 系统检查通过'))
return True
except Exception as e:
self.stdout.write(self.style.ERROR(f'❌ 系统检查失败: {str(e)}'))
return False
def check_dependencies(self):
"""检查项目依赖"""
try:
# 检查已安装的依赖是否符合要求
import pip
from pip._internal.operations.freeze import freeze
# 获取已安装的包
installed = {pkg.split('==')[0].lower(): pkg for pkg in freeze()}
# 检查关键依赖是否安装
required_packages = [
'django', 'gunicorn', 'psycopg2-binary', 'redis', 'celery'
]
missing = []
for pkg in required_packages:
if pkg.lower() not in installed:
missing.append(pkg)
if missing:
self.stdout.write(self.style.ERROR(f'❌ 缺少关键依赖: {", ".join(missing)}'))
return False
self.stdout.write(self.style.SUCCESS('✅ 所有关键依赖已安装'))
return True
except Exception as e:
self.stdout.write(self.style.ERROR(f'❌ 检查依赖时出错: {str(e)}'))
return False
运行部署前检查:
python manage.py pre_deploy_check
1.3 性能与安全基准测试
在部署前执行性能和安全基准测试:
# myproject/management/commands/benchmark.py
import time
import statistics
from django.core.management.base import BaseCommand
from django.test import Client
from django.urls import reverse
from django.conf import settings
class Command(BaseCommand):
help = '对关键页面进行性能基准测试'
def add_arguments(self, parser):
parser.add_argument('--runs', type=int, default=10, help='每个端点的测试次数')
def handle(self, *args, **options):
self.stdout.write(self.style.MIGRATE_HEADING('开始性能基准测试...'))
# 创建测试客户端
client = Client()
# 要测试的端点列表
endpoints = [
{'name': '首页', 'url': reverse('home')},
{'name': '用户登录', 'url': reverse('login')},
{'name': '文章列表', 'url': reverse('article_list')},
# 添加更多端点...
]
runs = options['runs']
results = {}
for endpoint in endpoints:
name = endpoint['name']
url = endpoint['url']
self.stdout.write(f'测试端点: {name} ({url})')
# 执行多次请求并记录响应时间
times = []
for i in range(runs):
start_time = time.time()
response = client.get(url)
end_time = time.time()
request_time = (end_time - start_time) * 1000 # 转换为毫秒
times.append(request_time)
# 检查响应状态
if response.status_code != 200:
self.stdout.write(self.style.WARNING(f' 运行 {i+1}/{runs}: 状态码 {response.status_code}'))
else:
self.stdout.write(f' 运行 {i+1}/{runs}: {request_time:.2f}ms')
# 计算统计数据
if times:
avg_time = statistics.mean(times)
median_time = statistics.median(times)
min_time = min(times)
max_time = max(times)
std_dev = statistics.stdev(times) if len(times) > 1 else 0
results[name] = {
'avg': avg_time,
'median': median_time,
'min': min_time,
'max': max_time,
'std_dev': std_dev
}
# 输出结果
self.stdout.write(self.style.SUCCESS(f'结果 - {name}:'))
self.stdout.write(f' 平均响应时间: {avg_time:.2f}ms')
self.stdout.write(f' 中位数响应时间: {median_time:.2f}ms')
self.stdout.write(f' 最短响应时间: {min_time:.2f}ms')
self.stdout.write(f' 最长响应时间: {max_time:.2f}ms')
self.stdout.write(f' 标准差: {std_dev:.2f}ms')
# 评估性能
if avg_time > 500: # 假设500ms是可接受的阈值
self.stdout.write(self.style.ERROR('❌ 性能警告: 平均响应时间超过500ms'))
elif avg_time > 200:
self.stdout.write(self.style.WARNING('⚠️ 性能注意: 平均响应时间超过200ms'))
else:
self.stdout.write(self.style.SUCCESS('✅ 性能良好: 平均响应时间低于200ms'))
else:
self.stdout.write(self.style.ERROR(f'❌ 无法收集 {name} 的性能数据'))
# 输出总结
self.stdout.write(self.style.MIGRATE_HEADING('基准测试总结:'))
for name, stats in results.items():
self.stdout.write(f'{name}: {stats["avg"]:.2f}ms (±{stats["std_dev"]:.2f}ms)')
2. 生产服务器配置
确保服务器正确配置是部署的关键步骤。以下是设置生产环境的最佳实践。
2.1 Web服务器配置
Nginx配置示例:
# /etc/nginx/sites-available/myproject.conf
upstream django_app {
server unix:/run/gunicorn.sock fail_timeout=0;
# 对于多个后端服务器
# server 10.0.0.2:8000 weight=1;
# server 10.0.0.3:8000 weight=1;
}
server {
listen 80;
server_name example.com www.example.com;
# 将所有HTTP请求重定向到HTTPS
return 301 https://$host$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_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
# SSL参数
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384';
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy strict-origin-when-cross-origin;
# 日志配置
access_log /var/log/nginx/myproject_access.log;
error_log /var/log/nginx/myproject_error.log error;
# 客户端配置
client_max_body_size 20M; # 允许上传的最大文件大小
# 静态文件
location /static/ {
alias /var/www/myproject/staticfiles/;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# 媒体文件
location /media/ {
alias /var/www/myproject/media/;
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
# Django应用
location / {
proxy_set_header Host $http_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_redirect off;
# 传递给Gunicorn
proxy_pass http://django_app;
# WebSocket支持(如果需要)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 75s;
proxy_read_timeout 300s;
}
# 防止访问特定文件
location ~ /\. {
deny all;
}
}
2.2 应用服务器配置
Gunicorn配置示例:
# gunicorn_config.py
import multiprocessing
import os
# 绑定地址
bind = 'unix:/run/gunicorn.sock'
# 工作进程数
workers = multiprocessing.cpu_count() * 2 + 1
# 工作模式
worker_class = 'gevent' # 使用gevent处理异步请求
# 每个工作进程的线程数
threads = 2
# 请求超时
timeout = 120
# 保持连接
keepalive = 2
# 最大请求数
max_requests = 1000
max_requests_jitter = 200 # 防止所有工作进程同时重启
# 进程名称
proc_name = 'myproject_gunicorn'
# 用户与组
user = 'www-data'
group = 'www-data'
# 日志配置
loglevel = 'info'
errorlog = '/var/log/gunicorn/error.log'
accesslog = '/var/log/gunicorn/access.log'
access_log_format = '%({X-Forwarded-For}i)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(L)s %({X-Request-Id}i)s'
# 守护进程模式
daemon = False # 与systemd一起使用时设为False
# 环境变量
raw_env = [
'DJANGO_SETTINGS_MODULE=myproject.settings',
'DJANGO_ENV=production',
]
# 启动前和关闭后的钩子
def on_starting(server):
"""服务启动前执行"""
print("Gunicorn正在启动...")
def on_exit(server):
"""服务关闭后执行"""
print("Gunicorn已关闭")
# 工作进程启动前的钩子
def pre_fork(server, worker):
"""工作进程启动前执行"""
pass
# 工作进程启动后的钩子
def post_fork(server, worker):
"""工作进程启动后执行"""
pass
Systemd服务配置:
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon for Django application
After=network.target postgresql.service redis.service
Requires=postgresql.service redis.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/gunicorn \
--config /var/www/myproject/gunicorn_config.py \
myproject.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5s
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
Celery服务配置:
# /etc/systemd/system/celery.service
[Unit]
Description=Celery worker daemon
After=network.target postgresql.service redis.service
Requires=postgresql.service redis.service
[Service]
User=celery
Group=celery
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/celery -A myproject worker \
--loglevel=INFO \
--concurrency=4
Restart=on-failure
RestartSec=5s
KillMode=mixed
TimeoutStopSec=10
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/celerybeat.service
[Unit]
Description=Celery beat daemon
After=network.target postgresql.service redis.service
Requires=postgresql.service redis.service
[Service]
User=celery
Group=celery
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/venv/bin/celery -A myproject beat \
--loglevel=INFO \
--scheduler=django_celery_beat.schedulers:DatabaseScheduler
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
2.3 数据库优化
PostgreSQL配置优化:
# postgresql.conf 优化示例
# 连接设置
max_connections = 100
# 内存设置
shared_buffers = 2GB # 服务器内存的25%
work_mem = 64MB # 复杂查询的工作内存
maintenance_work_mem = 256MB # 维护操作的内存
# 写入设置
wal_buffers = 16MB
checkpoint_completion_target = 0.9
effective_cache_size = 6GB # 服务器内存的75%
# 查询优化
random_page_cost = 1.1 # 对于SSD
effective_io_concurrency = 200 # 对于SSD
# 日志设置
logging_collector = on
log_min_duration_statement = 1000 # 记录超过1秒的查询
log_checkpoints = on
log_connections = on
log_disconnections = on
log_lock_waits = on
log_temp_files = 0
数据库备份脚本:
# myproject/management/commands/backup_db.py
import os
import time
import subprocess
from datetime import datetime
from django.core.management.base import BaseCommand
from django.conf import settings
class Command(BaseCommand):
help = '备份PostgreSQL数据库'
def add_arguments(self, parser):
parser.add_argument('--output-dir', default='/var/backups/myproject', help='备份文件输出目录')
parser.add_argument('--keep-days', type=int, default=7, help='保留备份的天数')
def handle(self, *args, **options):
output_dir = options['output_dir']
keep_days = options['keep_days']
# 确保输出目录存在
os.makedirs(output_dir, exist_ok=True)
# 从设置中获取数据库信息
db_settings = settings.DATABASES['default']
db_name = db_settings['NAME']
db_user = db_settings['USER']
db_host = db_settings.get('HOST', 'localhost')
db_port = db_settings.get('PORT', '5432')
# 创建备份文件名
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_file = os.path.join(output_dir, f"{db_name}_{timestamp}.sql.gz")
# 构建pg_dump命令
cmd = [
'pg_dump',
'-h', db_host,
'-p', db_port,
'-U', db_user,
'-d', db_name,
'-F', 'c', # 自定义格式,适合pg_restore
'-Z', '9', # 最高压缩级别
'-f', backup_file,
]
# 设置环境变量
env = os.environ.copy()
env['PGPASSWORD'] = db_settings.get('PASSWORD', '')
# 执行备份
try:
self.stdout.write(f"开始备份数据库 {db_name} 到 {backup_file}...")
start_time = time.time()
result = subprocess.run(
cmd,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
text=True
)
duration = time.time() - start_time
self.stdout.write(self.style.SUCCESS(f"备份完成! 耗时: {duration:.2f} 秒"))
# 删除过期备份
self.cleanup_old_backups(output_dir, keep_days)
return backup_file
except subprocess.CalledProcessError as e:
self.stdout.write(self.style.ERROR(f"备份失败: {e.stderr}"))
raise
def cleanup_old_backups(self, backup_dir, keep_days):
"""删除超过保留天数的旧备份"""
self.stdout.write(f"清理超过 {keep_days} 天的旧备份...")
current_time = time.time()
max_age = keep_days * 86400 # 转换为秒
count = 0
for filename in os.listdir(backup_dir):
file_path = os.path.join(backup_dir, filename)
# 只处理备份文件
if not filename.endswith('.sql.gz') and not filename.endswith('.dump'):
continue
# 检查文件年龄
file_age = current_time - os.path.getmtime(file_path)
if file_age > max_age:
os.remove(file_path)
count += 1
self.stdout.write(f"已清理 {count} 个旧备份文件")
3. Docker容器化部署
使用Docker和Docker Compose进行部署提供了一致的环境和简化的管理。
3.1 构建Docker镜像
基本Dockerfile:
# Dockerfile
FROM python:3.9-slim-bullseye
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on
# 创建非root用户
RUN groupadd -r django && useradd -r -g django django
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
gettext \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install -r requirements.txt
# 复制项目文件
COPY --chown=django:django . .
# 收集静态文件
RUN python manage.py collectstatic --noinput
# 切换到非root用户
USER django
# 暴露端口
EXPOSE 8000
# 声明数据卷
VOLUME ["/app/media", "/app/logs"]
# 默认命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--config", "gunicorn_config.py", "myproject.wsgi:application"]
多阶段构建优化:
# 构建阶段
FROM python:3.9-slim-bullseye AS builder
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on
WORKDIR /build
# 安装构建依赖
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
libpq-dev \
git \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /build/wheels -r requirements.txt
# 最终阶段
FROM python:3.9-slim-bullseye
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
DJANGO_ENV=production
# 创建非root用户
RUN groupadd -r django && useradd -r -g django django
# 创建目录结构
WORKDIR /app
RUN mkdir -p /app/media /app/logs /app/staticfiles \
&& chown -R django:django /app
# 安装运行时依赖
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libpq5 \
gettext \
postgresql-client \
&& rm -rf /var/lib/apt/lists/*
# 从构建阶段复制依赖
COPY --from=builder /build/wheels /wheels
RUN pip install --no-cache /wheels/*
# 复制项目文件
COPY --chown=django:django . .
# 运行部署检查
RUN python manage.py check --deploy
# 收集静态文件
RUN python manage.py collectstatic --noinput
# 切换到非root用户
USER django
# 暴露端口
EXPOSE 8000
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8000/health/ || exit 1
# 定义启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--config", "gunicorn_config.py", "myproject.wsgi:application"]
3.2 Docker Compose配置
# docker-compose.yml
version: '3.8'
services:
web:
build: .
image: myproject:${TAG:-latest}
restart: unless-stopped
depends_on:
- db
- redis
env_file:
- .env.production
volumes:
- media_data:/app/media
- log_data:/app/logs
networks:
- backend
deploy:
replicas: 3
resources:
limits:
cpus: '1'
memory: 2G
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
db:
image: postgres:14-alpine
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
networks:
- backend
deploy:
resources:
limits:
cpus: '2'
memory: 4G
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:6-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
networks:
- backend
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
celery:
build: .
image: myproject:${TAG:-latest}
restart: unless-stopped
depends_on:
- db
- redis
env_file:
- .env.production
command: celery -A myproject worker --loglevel=info
volumes:
- media_data:/app/media
- log_data:/app/logs
networks:
- backend
deploy:
replicas: 2
resources:
limits:
cpus: '1'
memory: 2G
celerybeat:
build: .
image: myproject:${TAG:-latest}
restart: unless-stopped
depends_on:
- db
- redis
env_file:
- .env.production
command: celery -A myproject beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
volumes:
- log_data:/app/logs
networks:
- backend
deploy:
resources:
limits:
cpus: '0.5'
memory: 1G
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./nginx/ssl:/etc/nginx/ssl
- media_data:/var/www/media
- static_data:/var/www/static
ports:
- "80:80"
- "443:443"
depends_on:
- web
networks:
- backend
- frontend
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
healthcheck:
test: ["CMD", "nginx", "-t"]
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
redis_data:
media_data:
static_data:
log_data:
networks:
backend:
driver: bridge
frontend:
driver: bridge
3.3 容器编排与管理
Docker Stack部署示例:
# docker-stack.yml
version: '3.8'
services:
web:
image: ${REGISTRY}/myproject:${TAG:-latest}
deploy:
mode: replicated
replicas: 3
update_config:
parallelism: 1
delay: 10s
order: start-first
failure_action: rollback
restart_policy:
condition: on-failure
max_attempts: 3
window: 120s
resources:
limits:
cpus: '1'
memory: 2G
env_file:
- .env.production
volumes:
- media_data:/app/media
- log_data:/app/logs
networks:
- backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
db:
image: postgres:14-alpine
deploy:
mode: replicated
replicas: 1
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
resources:
limits:
cpus: '2'
memory: 4G
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:6-alpine
deploy:
## 6.2 容器化部署实战
Docker容器化是现代Django应用部署的主流方式,以下是完整的实现步骤:
```dockerfile
# Dockerfile
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 设置环境变量
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 \
postgresql-client 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
# 暴露端口
EXPOSE 8000
# 运行gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "myproject.wsgi:application"]
配合使用docker-compose实现完整的部署环境:
# docker-compose.yml
version: '3.8'
services:
web:
build: .
restart: always
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
depends_on:
- db
- redis
env_file:
- ./.env.prod
networks:
- app_network
db:
image: postgres:14
volumes:
- postgres_data:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
networks:
- app_network
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- app_network
nginx:
image: nginx:1.23-alpine
ports:
- "80:80"
- "443:443"
volumes:
- static_volume:/app/staticfiles
- media_volume:/app/media
- ./nginx:/etc/nginx/conf.d
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- web
networks:
- app_network
volumes:
postgres_data:
redis_data:
static_volume:
media_volume:
networks:
app_network:
7. 自动化部署与持续集成
7.1 使用GitHub Actions实现CI/CD
持续集成和持续部署(CI/CD)是现代开发流程的重要组成部分:
# .github/workflows/django-ci-cd.yml
name: Django CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.11
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
env:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/test_db
DJANGO_SETTINGS_MODULE: myproject.settings.test
run: |
python manage.py test
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /path/to/project
git pull
docker-compose -f docker-compose.prod.yml down
docker-compose -f docker-compose.prod.yml up -d --build
8. 监控与日志管理
8.1 监控工具配置
# settings/production.py
INSTALLED_APPS += [
'django_prometheus',
]
MIDDLEWARE = [
'django_prometheus.middleware.PrometheusBeforeMiddleware',
# 其他中间件...
'django_prometheus.middleware.PrometheusAfterMiddleware',
]
# Prometheus 指标端点配置
PROMETHEUS_METRICS_EXPORT_PORT = 8001
PROMETHEUS_METRICS_EXPORT_ADDRESS = ''
8.2 Sentry集成实现异常追踪
# settings/production.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn="https://your-sentry-dsn@sentry.io/project-id",
integrations=[DjangoIntegration()],
traces_sample_rate=0.5, # 采样率
send_default_pii=True, # 发送用户信息
environment="production",
)
8.3 日志配置最佳实践
# settings/production.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'json': {
'format': '{"time": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s", "module": "%(module)s"}',
'class': 'pythonjsonlogger.jsonlogger.JsonFormatter',
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/app.log',
'maxBytes': 1024*1024*10, # 10 MB
'backupCount': 10,
'formatter': 'json',
},
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': True,
},
'myproject': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': False,
},
},
}
9. 多环境配置管理
9.1 使用环境分离设置文件
myproject/
└── settings/
├── __init__.py
├── base.py # 基础配置,被其他环境继承
├── development.py
├── test.py
└── production.py
9.2 环境变量管理
使用python-dotenv管理环境变量:
# settings/base.py
import os
from pathlib import Path
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 基础设置
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = False
# 生产环境设置 (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'),
}
}
10. 总结与推荐实践
根据我多年部署Django应用的经验,以下是最重要的部署建议:
- 安全第一:始终使用HTTPS,配置适当的安全头信息,定期更新依赖
- 分层架构:使用Nginx作为前端服务器,Gunicorn作为WSGI服务器
- 容器化部署:使用Docker简化环境管理和扩展
- CI/CD自动化:实现自动测试和部署流程
- 健壮的监控:配置完善的日志和监控系统
- 自动备份:定期备份数据库和媒体文件
- 负载均衡:对于高流量站点,实现多实例负载均衡
- 缓存策略:针对不同内容类型实施分层缓存策略
通过遵循这些最佳实践,您的Django应用不仅能够更加稳定、高效地运行,还能够在业务增长时轻松扩展。部署不是一次性的任务,而是一个持续改进的过程,定期审核和优化您的部署设置,将确保您的Django应用始终处于最佳状态。