11-实战项目2-Web应用完整部署
完整的Web应用部署方案
上一章我们部署了Python后端API,今天我们来做前后端分离的完整Web应用部署。
项目架构
┌─────────────────────────────────────────────────────────┐
│ Nginx │
│ (反向代理 + SSL) │
│ ┌────────┬────────┐ │
│ │ │ │ │
│ ↓ ↓ ↓ │
│ React Flask Admin │
│ (前端) (API) (后台) │
│ │ │ │ │
│ └────────┴────────┘ │
│ ↓ │
│ ┌──────────────────┐ │
│ │ MySQL + Redis │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────┘
1. 前端项目(React)
项目结构
frontend/
├── package.json
├── Dockerfile
├── nginx.conf
└── src/
├── App.js
└── index.js
Dockerfile
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package.json package-lock.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 运行阶段
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/build /usr/share/nginx/html
# 复制nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 启动nginx
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
# 路由配置(React Router)
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://api:8000;
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;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
package.json
{
"name": "frontend",
"version": "1.0.0",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.0",
"axios": "^1.4.0"
},
"devDependencies": {
"react-scripts": "5.0.1"
}
}
2. 后端API(Flask)
项目结构
backend/
├── app.py
├── requirements.txt
└── Dockerfile
Dockerfile
FROM python:3.11-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
default-libmysqlclient-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
RUN chown -R appuser:appuser /app
USER appuser
# 环境变量
ENV PYTHONUNBUFFERED=1
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "--timeout", "120", "app:app"]
requirements.txt
flask==2.3.0
flask-cors==4.0.0
mysql-connector-python==8.1.0
redis==4.6.0
gunicorn==21.2.0
3. 管理后台(Flask Admin)
项目结构
admin/
├── app.py
├── requirements.txt
└── Dockerfile
Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN groupadd -r admin && useradd -r -g admin admin
RUN chown -R admin:admin /app
USER admin
ENV PYTHONUNBUFFERED=1
EXPOSE 8001
CMD ["gunicorn", "-w", "2", "-b", "0.0.0.0:8001", "app:app"]
requirements.txt
flask==2.3.0
flask-admin==1.6.1
flask-sqlalchemy==3.0.5
mysql-connector-python==8.1.0
gunicorn==21.2.0
4. Nginx反向代理
nginx/nginx.conf
events {
worker_connections 1024;
}
http {
# 前端
upstream frontend {
server frontend:80;
}
# 后端API
upstream api {
server api:8000;
}
# 管理后台
upstream admin {
server admin:8001;
}
# 限流配置
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
listen 80;
server_name example.com www.example.com;
# SSL重定向
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# SSL证书配置
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
# SSL优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
# 前端
location / {
proxy_pass http://frontend;
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;
}
# API限流
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://api;
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 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# 管理后台
location /admin/ {
proxy_pass http://admin/;
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;
# IP白名单(可选)
# allow 192.168.1.0/24;
# deny all;
}
# 日志配置
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
}
}
5. Docker Compose编排
docker-compose.yml
version: '3.8'
services:
# Nginx反向代理
nginx:
image: nginx:alpine
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl:/etc/nginx/ssl
- nginx-logs:/var/log/nginx
depends_on:
- frontend
- api
- admin
networks:
- app-network
restart: unless-stopped
# 前端
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: frontend-app
networks:
- app-network
restart: unless-stopped
# 后端API
api:
build:
context: ./backend
dockerfile: Dockerfile
container_name: api-server
environment:
- DB_HOST=db
- DB_USER=root
- DB_PASSWORD=${DB_PASSWORD:-123456}
- DB_NAME=${DB_NAME:-mydb}
- REDIS_HOST=redis
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
# 管理后台
admin:
build:
context: ./admin
dockerfile: Dockerfile
container_name: admin-panel
environment:
- DB_HOST=db
- DB_USER=root
- DB_PASSWORD=${DB_PASSWORD:-123456}
- DB_NAME=${DB_NAME:-mydb}
depends_on:
db:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
# MySQL数据库
db:
image: mysql:8.0
container_name: mysql-db
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-123456}
MYSQL_DATABASE: ${DB_NAME:-mydb}
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "${DB_PORT:-3306}:3306"
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD:-123456}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
container_name: redis-cache
volumes:
- redis-data:/data
command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD:-123456}
ports:
- "${REDIS_PORT:-6379}:6379"
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-123456}", "ping"]
interval: 10s
timeout: 5s
retries: 3
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
mysql-data:
redis-data:
nginx-logs:
6. 数据库初始化
init.sql
-- 创建数据库表
USE mydb;
-- 用户表
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 文章表
CREATE TABLE IF NOT EXISTS articles (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT,
author_id INT,
status ENUM('draft', 'published') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 评论表
CREATE TABLE IF NOT EXISTS comments (
id INT AUTO_INCREMENT PRIMARY KEY,
article_id INT,
user_id INT,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 插入测试数据
INSERT INTO users (username, email, password) VALUES
('admin', 'admin@example.com', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyY6X7yGKJ2i'),
('user1', 'user1@example.com', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewY5GyY6X7yGKJ2i');
INSERT INTO articles (title, content, author_id, status) VALUES
('第一篇文章', '这是第一篇文章的内容', 1, 'published'),
('第二篇文章', '这是第二篇文章的内容', 1, 'published'),
('草稿文章', '这是一篇草稿', 1, 'draft');
7. 部署和测试
构建和启动
# 构建所有镜像
docker-compose build
# 启动所有服务
docker-compose up -d
# 查看状态
docker-compose ps
# 查看日志
docker-compose logs -f
测试各服务
# 测试前端
curl http://localhost
# 测试API健康检查
curl http://localhost/api/health
# 测试管理后台
curl http://localhost/admin/
# 查看数据库
docker exec -it mysql-db mysql -uroot -p123456 -e "USE mydb; SELECT * FROM users;"
性能测试
# 使用ab进行压力测试
ab -n 1000 -c 100 http://localhost/api/health
# 查看Nginx日志
docker exec nginx-proxy tail -f /var/log/nginx/access.log
8. 监控和日志
安装监控工具
# 添加到docker-compose.yml
prometheus:
image: prom/prometheus:latest
container_name: prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
ports:
- "9090:9090"
networks:
- app-network
grafana:
image: grafana/grafana:latest
container_name: grafana
volumes:
- grafana-data:/var/lib/grafana
ports:
- "3000:3000"
networks:
- app-network
volumes:
prometheus-data:
grafana-data:
日志聚合
# 查看所有服务日志
docker-compose logs
# 查看特定服务日志
docker-compose logs -f api
# 导出日志
docker-compose logs --since 24h > logs.txt
9. 备份和恢复
数据库备份
# 备份数据库
docker exec mysql-db mysqldump -uroot -p123456 mydb > backup_$(date +%Y%m%d).sql
# 恢复数据库
docker exec -i mysql-db mysql -uroot -p123456 mydb < backup_20231201.sql
定时备份
# 添加到crontab
0 2 * * * /path/to/backup.sh
# backup.sh内容
#!/bin/bash
BACKUP_DIR="/data/backups"
DATE=$(date +%Y%m%d)
docker exec mysql-db mysqldump -uroot -p123456 mydb > ${BACKUP_DIR}/backup_${DATE}.sql
find ${BACKUP_DIR} -name "backup_*.sql" -mtime +7 -delete
10. SSL证书配置
使用Let’s Encrypt
# 安装certbot
sudo apt-get install certbot
# 获取证书
sudo certbot certonly --standalone -d example.com -d www.example.com
# 复制证书到项目
sudo cp /etc/letsencrypt/live/example.com/fullchain.pem ./nginx/ssl/cert.pem
sudo cp /etc/letsencrypt/live/example.com/privkey.pem ./nginx/ssl/key.pem
本章小结
- 完整架构:前端 + 后端 + 后台 + 数据库 + 缓存
- 反向代理:Nginx + SSL + 负载均衡 + 限流
- 部署方案:Docker Compose一键部署
- 监控日志:Prometheus + Grafana + 日志聚合
- 备份恢复:数据库备份 + 定时任务
现在已经掌握了完整Web应用部署,下一章我们来实战微服务架构!
继续学下去,马上就能处理企业级项目了!