Appearance
章节3:部署与运维
学习目标
完成本章学习后,你将能够:
- 编写 Dockerfile 和 docker-compose.yml,实现全栈项目的容器化打包
- 配置 Nginx 反向代理与静态资源托管
- 使用 Gunicorn 部署 Django 生产级服务
- 配置域名解析与 HTTPS 证书(Let's Encrypt SSL)
- 搭建日志监控体系,使用 Locust 进行性能压测
3.1 Docker 容器化打包
3.1.1 Docker 核心概念
定义:Docker 是一种容器化技术,将应用及其依赖打包在一个轻量级、可移植的容器中,保证开发、测试、生产环境的一致性。
| 概念 | 说明 | 类比 |
|---|---|---|
| Image(镜像) | 只读模板,包含运行环境与代码 | 安装盘 |
| Container(容器) | 镜像的运行实例,可读写 | 虚拟机 |
| Dockerfile | 构建镜像的指令清单 | 配方 |
| Volume(数据卷) | 持久化容器数据 | 外接硬盘 |
| docker-compose | 编排多容器服务 | 乐队指挥 |
3.1.2 后端 Dockerfile
dockerfile
# backend/Dockerfile
# ===== 构建阶段 =====
FROM python:3.11-slim AS builder
WORKDIR /app
# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt \
&& pip install gunicorn gevent
# ===== 运行阶段 =====
FROM python:3.11-slim
WORKDIR /app
# 从构建阶段复制依赖
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin/gunicorn /usr/local/bin/gunicorn
# 复制项目代码
COPY . .
# 创建非 root 用户运行(安全最佳实践)
RUN useradd -m -r appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
# 启动命令(使用 Gunicorn)
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", \
"--workers", "4", "--worker-class", "gevent", "--timeout", "120"]3.1.3 前端 Dockerfile
dockerfile
# frontend/Dockerfile
# ===== 构建阶段 =====
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# ===== Nginx 运行阶段 =====
FROM nginx:alpine
# 复制构建产物
COPY --from=build /app/dist /usr/share/nginx/html
# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]3.1.4 docker-compose.yml 编排
yaml
# docker-compose.yml
version: '3.8'
services:
# ===== MySQL 数据库 =====
mysql:
image: mysql:8.0
container_name: ecommerce-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./backend/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- ecommerce-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# ===== Redis 缓存 =====
redis:
image: redis:7-alpine
container_name: ecommerce-redis
restart: always
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- ecommerce-net
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
# ===== Django 后端 =====
backend:
build:
context: ./backend
dockerfile: Dockerfile
container_name: ecommerce-backend
restart: always
environment:
DJANGO_SETTINGS_MODULE: config.settings.prod
MYSQL_HOST: mysql
REDIS_HOST: redis
REDIS_PASSWORD: ${REDIS_PASSWORD}
env_file:
- .env
ports:
- "8000:8000"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
volumes:
- static_volume:/app/static
- media_volume:/app/media
networks:
- ecommerce-net
# ===== Celery 异步任务 =====
celery:
build:
context: ./backend
container_name: ecommerce-celery
restart: always
command: celery -A config worker -l info --concurrency=4
environment:
DJANGO_SETTINGS_MODULE: config.settings.prod
env_file:
- .env
depends_on:
- backend
- redis
networks:
- ecommerce-net
# ===== Nginx 反向代理 =====
nginx:
image: nginx:alpine
container_name: ecommerce-nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
- static_volume:/static
- media_volume:/media
- ./ssl:/etc/nginx/ssl
depends_on:
- backend
networks:
- ecommerce-net
volumes:
mysql_data:
redis_data:
static_volume:
media_volume:
networks:
ecommerce-net:
driver: bridge3.1.5 构建与运行命令
bash
# 构建镜像
docker-compose build
# 启动所有服务
docker-compose up -d
# 查看运行状态
docker-compose ps
# 查看日志
docker-compose logs -f backend
# 执行 Django 管理命令
docker-compose exec backend python manage.py migrate
docker-compose exec backend python manage.py collectstatic --noinput
# 停止所有服务
docker-compose down
# 完全重建(含卷清理)
docker-compose down -v && docker-compose up -d --build3.2 Nginx 反向代理配置
3.2.1 Nginx 核心概念
定义:Nginx 是一款高性能的 Web 服务器和反向代理服务器,承担请求分发、负载均衡、静态资源托管、SSL 终结等职责。
架构角色:
客户端请求
│
▼
Nginx(反向代理)
│
├──→ /api/* → Django Backend (8000)
├──→ /static/* → 静态文件目录
├──→ /media/* → 上传媒体文件
└──→ /* → Vue 前端 SPA3.2.2 Nginx 配置示例
nginx
# nginx/nginx.conf
upstream django_backend {
server backend:8000; # docker-compose 服务名
# server 127.0.0.1:8001; # 可添加多个实现负载均衡
# server 127.0.0.1:8002;
}
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri; # HTTP → HTTPS 重定向
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 证书配置
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# 请求大小限制(上传文件)
client_max_body_size 20M;
# ===== 静态文件 =====
location /static/ {
alias /static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# ===== 媒体文件 =====
location /media/ {
alias /media/;
expires 7d;
}
# ===== API 代理 =====
location /api/ {
proxy_pass http://django_backend;
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;
# WebSocket 支持(如需)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# ===== Admin 后台 =====
location /admin/ {
proxy_pass http://django_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# ===== Vue SPA(前端路由)=====
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html; # SPA 路由 fallback
}
}3.2.3 Nginx 常用运维命令
bash
# 测试配置文件语法
nginx -t
# 重载配置(不中断服务)
nginx -s reload
# 查看访问日志
tail -f /var/log/nginx/access.log
# 查看错误日志
tail -f /var/log/nginx/error.log3.3 Gunicorn 生产级部署
3.3.1 Gunicorn 核心参数
定义:Gunicorn(Green Unicorn)是 Python 的 WSGI HTTP 服务器,用于在生产环境中运行 Django 应用,比 Django 自带的开发服务器更稳定、高性能。
bash
# 基础启动命令
gunicorn config.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 4 \
--worker-class gevent \
--timeout 120 \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
--max-requests 10000 \
--max-requests-jitter 2000参数详解:
| 参数 | 推荐值 | 说明 |
|---|---|---|
--workers | (2 × CPU核数) + 1 | 工作进程数,4 核推荐 9 |
--worker-class | gevent | 异步 Worker,处理 I/O 密集型请求 |
--timeout | 120 | 请求超时时间(秒) |
--max-requests | 10000 | 进程处理多少请求后重启(防内存泄漏) |
--keep-alive | 5 | 保持连接秒数 |
3.3.2 Gunicorn 配置文件
python
# backend/gunicorn.conf.py
import multiprocessing
bind = '0.0.0.0:8000'
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'gevent'
worker_connections = 1000
timeout = 120
graceful_timeout = 30
keepalive = 5
# 日志配置
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'info'
# 进程名
proc_name = 'ecommerce-backend'
# 守护进程化
daemon = False # Docker 中保持前台运行3.3.3 生产环境 Django 配置
python
# backend/config/settings/prod.py
from .base import *
import environ
env = environ.Env()
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# ===== 安全配置 =====
DEBUG = False
SECRET_KEY = env('DJANGO_SECRET_KEY')
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['your-domain.com'])
# ===== CORS 配置 =====
CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS')
CORS_ALLOW_CREDENTIALS = True
# ===== 数据库 =====
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': env('MYSQL_DATABASE'),
'USER': env('MYSQL_USER'),
'PASSWORD': env('MYSQL_PASSWORD'),
'HOST': env('MYSQL_HOST'),
'PORT': '3306',
'OPTIONS': {
'charset': 'utf8mb4',
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
'CONN_MAX_AGE': 60, # 连接复用
}
}
# ===== Redis 缓存 =====
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': f"redis://:{env('REDIS_PASSWORD')}@{env('REDIS_HOST')}:6379/0",
'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'},
}
}
# ===== 安全头部 =====
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True3.4 域名与 HTTPS 配置
3.4.1 域名解析
| 记录类型 | 主机记录 | 记录值 | 说明 |
|---|---|---|---|
| A | @ | 服务器公网IP | 根域名解析 |
| A | www | 服务器公网IP | www 子域名 |
| CNAME | api | your-domain.com | API 子域名别名 |
3.4.2 使用 Certbot 获取 SSL 证书
bash
# 安装 Certbot
apt install certbot python3-certbot-nginx -y
# 获取 SSL 证书(自动配置 Nginx)
certbot --nginx -d your-domain.com -d www.your-domain.com
# 证书续期(Let's Encrypt 90天有效期,自动续期)
certbot renew --dry-run
# 查看证书状态
certbot certificates3.4.3 手动配置 SSL 证书
bash
# 将证书文件放到指定目录
mkdir -p /etc/nginx/ssl
# fullchain.pem — 完整证书链
# privkey.pem — 私钥文件
# 验证证书
openssl x509 -in /etc/nginx/ssl/fullchain.pem -text -noout3.5 日志监控与性能压测
3.5.1 日志集中管理
定义:日志是运维的眼睛,记录系统运行状态、错误堆栈、请求信息,帮助快速定位问题。
python
# backend/config/settings/base.py —— 日志配置
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'json': { # JSON 格式,方便 ELK 收集
'()': 'pythonjsonlogger.jsonlogger.JsonFormatter',
'format': '%(asctime)s %(levelname)s %(name)s %(message)s',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/app.log',
'maxBytes': 10485760, # 10MB
'backupCount': 10,
'formatter': 'json',
},
'error_file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/django/error.log',
'maxBytes': 10485760,
'backupCount': 10,
'level': 'ERROR',
},
},
'loggers': {
'django': {
'handlers': ['console', 'file'],
'level': 'INFO',
'propagate': False,
},
'django.request': {
'handlers': ['error_file'],
'level': 'ERROR',
'propagate': False,
},
},
}3.5.2 性能压测(Locust)
定义:性能压测通过模拟大量用户并发访问,检测系统的吞吐量(TPS)、响应时间、错误率,找到性能瓶颈。
python
# locustfile.py
from locust import HttpUser, task, between, tag
class ECommerceUser(HttpUser):
"""模拟电商平台的普通用户行为"""
wait_time = between(1, 5) # 用户操作间隔 1~5 秒
token = None
def on_start(self):
"""用户启动时登录"""
res = self.client.post("/api/v1/auth/login/", json={
"username": "test_user",
"password": "test_pass"
})
if res.status_code == 200:
self.token = res.json()["data"]["access_token"]
self.client.headers = {"Authorization": f"Bearer {self.token}"}
@tag("home")
@task(3)
def view_homepage(self):
"""浏览首页(权重 3)"""
self.client.get("/api/v1/banners/")
self.client.get("/api/v1/products/hot/")
@tag("product")
@task(5)
def browse_products(self):
"""浏览商品(权重 5)"""
self.client.get("/api/v1/products/", params={
"page": 1, "page_size": 20, "ordering": "-sales_count"
})
@tag("cart")
@task(2)
def add_to_cart(self):
"""加入购物车(权重 2)"""
self.client.post("/api/v1/cart/", json={
"product_id": 1, "quantity": 1
})
@tag("order")
@task(1)
def create_order(self):
"""下单(权重 1)"""
self.client.post("/api/v1/orders/", json={
"cart_ids": [1, 2, 3], "address_id": 1
})
# 运行命令
# locust -f locustfile.py --host=https://your-domain.com --web-host=0.0.0.0
# 浏览器访问 http://localhost:8089 设置并发数和生成速率压测指标解读:
| 指标 | 含义 | 健康值 |
|---|---|---|
| RPS / TPS | 每秒请求数 / 每秒事务数 | ≥ 500 |
| P50 响应时间 | 50% 的请求在此时间内完成 | ≤ 200ms |
| P95 响应时间 | 95% 的请求在此时间内完成 | ≤ 800ms |
| P99 响应时间 | 99% 的请求在此时间内完成 | ≤ 2000ms |
| 错误率 | 非 2xx/3xx 响应占比 | ≤ 1% |
3.5.3 监控工具推荐
| 工具 | 用途 | 类别 |
|---|---|---|
| Prometheus + Grafana | 系统指标采集与可视化面板 | 基础设施监控 |
| Sentry | 错误追踪与性能监控 | 应用层监控 |
| ELK Stack | 日志收集、搜索、分析 | 日志管理 |
| Supervisor | 进程守护与管理 | 进程管理 |
| Docker Healthcheck | 容器健康检查 | 容器层 |
yaml
# docker-compose.yml 中集成 Healthcheck 示例
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s小结
知识回顾
- Docker 容器化:通过 Dockerfile 构建镜像,docker-compose 编排多服务,实现环境一致性
- Nginx 反向代理:动静分离、API 代理、SSL 终结、SPA 路由支持
- Gunicorn 生产部署:多进程 + Gevent 异步 Worker,参数调优,守护进程管理
- HTTPS 配置:Let's Encrypt 免费证书 + Certbot 自动化续期
- 监控与压测:日志分级管理 + Locust 性能压测 + 指标解读
练习与思考
- Docker 练习:为本节课项目编写一个完整的 Dockerfile(多阶段构建),并解释每一行的作用
- Nginx 配置:给 Nginx 添加 IP 白名单限制,只允许公司出口 IP 访问
/admin/ - Gunicorn 调优:在一台 4 核 8G 的服务器上,你应该设置多少 workers?为什么?
- 压测实践:使用 Locust 对本地项目进行压测,找出瓶颈(数据库/缓存/网络/代码),写出分析报告
- 思考题:前后端分离部署时,跨域问题可以在 Nginx 层解决,也可以在 Django CORS 层解决,谈谈两种方案的优缺点