1146 lines
27 KiB
Markdown
1146 lines
27 KiB
Markdown
# 🚀 单机 VPS 部署方案 - 优化 TodoList
|
||
|
||
**部署环境**:单机 VPS(公网 IP + 大带宽 + 100GB 存储)
|
||
**优化方向**:资源优化、单机部署、简化运维
|
||
**项目周期**:4-5 周 | **总任务数**:72 项 | **优先级分布**:P0(20) | P1(30) | P2(15) | P3(7)
|
||
|
||
---
|
||
|
||
## 📋 环境特点分析
|
||
|
||
### ✅ 优势
|
||
- 单一公网 IP,简化网络配置
|
||
- 大带宽支持高并发
|
||
- 100GB 存储足够应用 + 数据库 + 备份
|
||
|
||
### ⚠️ 制约
|
||
- 单机故障 = 全部服务宕机
|
||
- 内存/CPU 有限(需要优化)
|
||
- 需要更严格的监控和备份
|
||
|
||
### 🎯 优化策略
|
||
1. **容器精简** - 使用 Alpine 基础镜像
|
||
2. **资源共享** - 数据库、应用、Web 服务器在同一机器
|
||
3. **缓存优先** - Redis 缓存热数据
|
||
4. **监控告警** - 及时发现问题
|
||
5. **自动备份** - 防止数据丢失
|
||
|
||
---
|
||
|
||
## 🎯 第一阶段:环境准备与优化(第 1 周)
|
||
|
||
### 🔴 P0 任务:VPS 环境初始化
|
||
|
||
#### 1.1 VPS 系统准备
|
||
- [ ] SSH 连接 VPS,验证系统版本(建议 Ubuntu 20.04 LTS)
|
||
- [ ] 更新系统:`sudo apt update && apt upgrade -y`
|
||
- [ ] 安装基础工具:`sudo apt install -y curl wget git htop`
|
||
- [ ] 配置防火墙(UFW)
|
||
```bash
|
||
sudo ufw enable
|
||
sudo ufw allow 22/tcp # SSH
|
||
sudo ufw allow 80/tcp # HTTP
|
||
sudo ufw allow 443/tcp # HTTPS
|
||
```
|
||
- [ ] 配置时区和 NTP
|
||
- [ ] 创建普通用户用于应用运行(非 root)
|
||
|
||
#### 1.2 Docker 和 Docker Compose 安装
|
||
- [ ] 安装 Docker:`curl -fsSL https://get.docker.com | sh`
|
||
- [ ] 配置 Docker 权限:添加用户到 docker 组
|
||
- [ ] 安装 Docker Compose(最新版本)
|
||
- [ ] 验证 Docker 和 Compose 可用性
|
||
|
||
#### 1.3 域名和 SSL 配置
|
||
- [ ] 配置域名 DNS 解析到公网 IP
|
||
- [ ] 申请 Let's Encrypt SSL 证书(使用 Certbot)
|
||
- [ ] 配置 SSL 证书自动续期
|
||
- [ ] 测试 HTTPS 连接
|
||
|
||
#### 1.4 存储规划
|
||
- [ ] 检查磁盘使用情况:`df -h`
|
||
- [ ] 规划存储分配(100GB)
|
||
```
|
||
系统:20GB
|
||
数据库:30GB
|
||
应用:20GB
|
||
备份:20GB
|
||
日志:10GB
|
||
```
|
||
- [ ] 创建数据目录结构
|
||
|
||
### 🟠 P1 任务:后端项目创建
|
||
|
||
#### 1.5 创建后端项目
|
||
- [ ] 在 VPS 上克隆项目:`git clone <repo> /home/app/backend`
|
||
- [ ] 创建 `/backend` 目录结构
|
||
```
|
||
backend/
|
||
├── src/
|
||
│ ├── server.ts
|
||
│ ├── config/
|
||
│ ├── routes/
|
||
│ ├── controllers/
|
||
│ ├── middleware/
|
||
│ ├── database/
|
||
│ └── types/
|
||
├── docker/
|
||
│ ├── Dockerfile
|
||
│ └── .dockerignore
|
||
├── scripts/
|
||
│ ├── db-init.sql
|
||
│ └── backup.sh
|
||
├── package.json
|
||
├── tsconfig.json
|
||
└── .env
|
||
```
|
||
- [ ] 本地初始化 Node.js 项目(npm init)
|
||
- [ ] 编写最小化 package.json(仅必需依赖)
|
||
|
||
#### 1.6 依赖选择(针对单机优化)
|
||
- [ ] 后端依赖:
|
||
```json
|
||
{
|
||
"express": "^4.18.2",
|
||
"pg": "^8.10.0",
|
||
"cors": "^2.8.5",
|
||
"dotenv": "^16.3.1",
|
||
"zod": "^3.22.4",
|
||
"pino": "^8.16.1"
|
||
}
|
||
```
|
||
- [ ] 开发依赖:
|
||
```json
|
||
{
|
||
"typescript": "^5.2.2",
|
||
"ts-node": "^10.9.1",
|
||
"nodemon": "^3.0.1",
|
||
"@types/express": "^4.17.20",
|
||
"@types/node": "^20.8.0",
|
||
"jest": "^29.7.0",
|
||
"@types/jest": "^29.5.6"
|
||
}
|
||
```
|
||
- [ ] **移除不必要包**:@astrojs/node, 重量级 ORM 等
|
||
|
||
### 🟡 P2 任务:文档和规划
|
||
|
||
#### 1.7 部署文档编写
|
||
- [ ] 编写 VPS 部署指南
|
||
- [ ] 记录环境配置清单
|
||
- [ ] 编写故障恢复流程
|
||
- [ ] 编写备份和恢复步骤
|
||
|
||
---
|
||
|
||
## 🎯 第二阶段:后端开发(第 2 周)
|
||
|
||
### 🔴 P0 任务:核心后端功能
|
||
|
||
#### 2.1 Server 基础配置(精简版)
|
||
- [ ] 创建 `src/server.ts` 主文件
|
||
- [ ] 配置 Express 应用
|
||
```typescript
|
||
app.use(cors({ origin: process.env.FRONTEND_URL }));
|
||
app.use(express.json({ limit: '10mb' }));
|
||
app.use(compression()); // 启用 Gzip
|
||
```
|
||
- [ ] 配置错误处理中间件
|
||
- [ ] 实现健康检查端点 `GET /health`
|
||
- [ ] 配置日志(使用 Pino,性能优于 Winston)
|
||
|
||
#### 2.2 数据库配置(针对单机)
|
||
- [ ] 创建 PostgreSQL 容器配置
|
||
```yaml
|
||
postgres:
|
||
image: postgres:15-alpine # 使用 Alpine 减少镜像大小
|
||
environment:
|
||
POSTGRES_DB: jiao77_db
|
||
POSTGRES_USER: app
|
||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
restart: always
|
||
```
|
||
- [ ] 创建连接池配置
|
||
```typescript
|
||
new Pool({
|
||
max: 20, // 单机限制连接数
|
||
idleTimeoutMillis: 30000,
|
||
connectionTimeoutMillis: 2000,
|
||
})
|
||
```
|
||
- [ ] 实现连接池监控函数
|
||
|
||
#### 2.3 数据库 Schema(优化版)
|
||
- [ ] 创建 SQL 初始化脚本
|
||
```sql
|
||
-- 评论表(带分区考虑)
|
||
CREATE TABLE comments (
|
||
id SERIAL PRIMARY KEY,
|
||
report_id VARCHAR(100) NOT NULL,
|
||
author VARCHAR(100) NOT NULL,
|
||
content TEXT NOT NULL CHECK (char_length(content) <= 5000),
|
||
status VARCHAR(20) DEFAULT 'pending',
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
updated_at TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- 索引优化
|
||
CREATE INDEX CONCURRENTLY idx_comments_report_id
|
||
ON comments(report_id, created_at DESC);
|
||
CREATE INDEX CONCURRENTLY idx_comments_status
|
||
ON comments(status, created_at DESC);
|
||
|
||
-- 统计表
|
||
CREATE TABLE report_stats (
|
||
id SERIAL PRIMARY KEY,
|
||
report_id VARCHAR(100) UNIQUE NOT NULL,
|
||
views INTEGER DEFAULT 0,
|
||
comments INTEGER DEFAULT 0,
|
||
likes INTEGER DEFAULT 0,
|
||
shares INTEGER DEFAULT 0,
|
||
last_updated TIMESTAMP DEFAULT NOW()
|
||
);
|
||
|
||
-- 点赞表
|
||
CREATE TABLE likes (
|
||
id SERIAL PRIMARY KEY,
|
||
report_id VARCHAR(100) NOT NULL,
|
||
client_id VARCHAR(100) NOT NULL,
|
||
created_at TIMESTAMP DEFAULT NOW(),
|
||
UNIQUE(report_id, client_id)
|
||
);
|
||
```
|
||
- [ ] 创建数据库初始化脚本 `scripts/db-init.sql`
|
||
- [ ] 准备数据库迁移工具(简单的 SQL 文件版本控制)
|
||
|
||
#### 2.4 评论 API 实现(轻量级)
|
||
- [ ] `GET /api/comments?reportId=xxx&page=1&limit=20` - 分页获取
|
||
- [ ] `POST /api/comments` - 创建评论(含速率限制)
|
||
- [ ] `DELETE /api/comments/:id` - 删除评论(仅作者或管理员)
|
||
- [ ] 实现数据验证(使用 Zod)
|
||
- [ ] 实现错误处理
|
||
|
||
#### 2.5 统计 API 实现(轻量级)
|
||
- [ ] `GET /api/stats/:reportId` - 获取统计数据(加缓存)
|
||
- [ ] `POST /api/stats/:reportId/view` - 记录浏览(批量处理)
|
||
- [ ] `POST /api/stats/:reportId/like` - 点赞
|
||
- [ ] 实现请求去重(基于 IP + clientId)
|
||
- [ ] 实现缓存策略
|
||
|
||
#### 2.6 中间件实现(安全 + 性能)
|
||
- [ ] 速率限制中间件
|
||
```typescript
|
||
const rateLimit = require('express-rate-limit');
|
||
const limiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 分钟
|
||
max: 100, // 单机限制请求数
|
||
keyGenerator: (req) => req.ip,
|
||
skip: (req) => req.path === '/health'
|
||
});
|
||
```
|
||
- [ ] 请求日志中间件(Pino)
|
||
- [ ] 数据验证中间件(Zod)
|
||
- [ ] 错误处理中间件
|
||
|
||
#### 2.7 本地测试
|
||
- [ ] 运行 `npm run dev` 本地启动
|
||
- [ ] 测试各个 API 端点
|
||
- [ ] 测试数据库连接
|
||
- [ ] 验证错误处理
|
||
|
||
### 🟠 P1 任务:后端测试
|
||
|
||
#### 2.8 单元测试(简化版)
|
||
- [ ] 编写 API 端点测试
|
||
- [ ] 编写数据验证测试
|
||
- [ ] 编写错误处理测试
|
||
- [ ] 目标覆盖率 > 70%(单机优先功能质量)
|
||
|
||
#### 2.9 集成测试
|
||
- [ ] 测试完整的评论流程
|
||
- [ ] 测试完整的统计流程
|
||
- [ ] 测试异常场景
|
||
|
||
---
|
||
|
||
## 🎯 第三阶段:前端开发(第 2-3 周)
|
||
|
||
### 🔴 P0 任务:前端集成
|
||
|
||
#### 3.1 Astro 混合渲染配置
|
||
- [ ] 更新 `astro.config.mjs`
|
||
```javascript
|
||
export default defineConfig({
|
||
integrations: [react(), tailwind()],
|
||
output: 'hybrid',
|
||
adapter: node({ mode: 'standalone' }),
|
||
vite: {
|
||
ssr: { external: ['pg'] }
|
||
}
|
||
});
|
||
```
|
||
- [ ] 本地测试混合渲染
|
||
|
||
#### 3.2 API 客户端库
|
||
- [ ] 创建 `src/lib/api-client.ts`
|
||
```typescript
|
||
const API_BASE = process.env.PUBLIC_API_URL || 'http://localhost:3000';
|
||
|
||
export async function fetchAPI(
|
||
path: string,
|
||
options?: RequestInit
|
||
) {
|
||
const response = await fetch(`${API_BASE}${path}`, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...options?.headers,
|
||
},
|
||
...options,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`API Error: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
}
|
||
```
|
||
- [ ] 实现缓存策略
|
||
|
||
#### 3.3 React 组件开发(轻量级)
|
||
- [ ] 开发 `CommentSection` 组件
|
||
```typescript
|
||
export default function CommentSection({
|
||
reportId
|
||
}: {
|
||
reportId: string
|
||
}) {
|
||
const [comments, setComments] = useState<Comment[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
useEffect(() => {
|
||
setLoading(true);
|
||
fetchAPI(`/api/comments?reportId=${reportId}`)
|
||
.then(setComments)
|
||
.finally(() => setLoading(false));
|
||
}, [reportId]);
|
||
|
||
// ... 组件逻辑
|
||
}
|
||
```
|
||
- [ ] 开发 `StatsPanel` 组件(简化版)
|
||
```typescript
|
||
export default function StatsPanel({ reportId }: { reportId: string }) {
|
||
const [stats, setStats] = useState<Stats | null>(null);
|
||
|
||
useEffect(() => {
|
||
fetchAPI(`/api/stats/${reportId}`).then(setStats);
|
||
}, [reportId]);
|
||
|
||
if (!stats) return <Skeleton />;
|
||
|
||
return (
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<StatCard label="浏览" value={stats.views} />
|
||
<StatCard label="评论" value={stats.comments} />
|
||
<StatCard label="点赞" value={stats.likes} />
|
||
<StatCard label="分享" value={stats.shares} />
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
- [ ] 开发 `LikeButton` 组件
|
||
|
||
#### 3.4 页面集成
|
||
- [ ] 在报告页面导入组件
|
||
- [ ] 设置客户端加载指令
|
||
- [ ] 样式适配
|
||
|
||
### 🟠 P1 任务:前端测试
|
||
|
||
#### 3.5 组件测试
|
||
- [ ] 编写简单的组件测试(Vitest)
|
||
- [ ] 测试数据加载
|
||
- [ ] 测试用户交互
|
||
|
||
---
|
||
|
||
## 🎯 第四阶段:单机容器化部署(第 3 周)
|
||
|
||
### 🔴 P0 任务:Docker 化应用
|
||
|
||
#### 4.1 后端 Dockerfile(Alpine 优化)
|
||
- [ ] 创建 `backend/Dockerfile`
|
||
```dockerfile
|
||
# 构建阶段
|
||
FROM node:18-alpine AS builder
|
||
WORKDIR /app
|
||
COPY package*.json ./
|
||
RUN npm ci --only=production && npm cache clean --force
|
||
|
||
# 运行阶段
|
||
FROM node:18-alpine
|
||
WORKDIR /app
|
||
RUN apk add --no-cache dumb-init
|
||
|
||
COPY --from=builder /app/node_modules ./node_modules
|
||
COPY . .
|
||
|
||
EXPOSE 3000
|
||
ENTRYPOINT ["dumb-init", "--"]
|
||
CMD ["node", "dist/server.js"]
|
||
```
|
||
- [ ] 镜像大小目标:< 200MB
|
||
|
||
#### 4.2 前端 Dockerfile(静态优化)
|
||
- [ ] 创建 `frontend/Dockerfile`
|
||
```dockerfile
|
||
FROM node:18-alpine AS builder
|
||
WORKDIR /app
|
||
COPY package*.json ./
|
||
RUN npm ci
|
||
COPY . .
|
||
RUN npm run build
|
||
|
||
FROM node:18-alpine
|
||
WORKDIR /app
|
||
COPY --from=builder /app/dist ./dist
|
||
EXPOSE 3000
|
||
CMD ["node", "./dist/server/entry.mjs"]
|
||
```
|
||
|
||
#### 4.3 Docker Compose 配置(单机专用)
|
||
- [ ] 创建 `docker-compose.yml`
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
postgres:
|
||
image: postgres:15-alpine
|
||
container_name: jiao77_postgres
|
||
environment:
|
||
POSTGRES_DB: ${DB_NAME}
|
||
POSTGRES_USER: ${DB_USER}
|
||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||
volumes:
|
||
- postgres_data:/var/lib/postgresql/data
|
||
- ./scripts/db-init.sql:/docker-entrypoint-initdb.d/init.sql
|
||
restart: unless-stopped
|
||
# 资源限制
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '1'
|
||
memory: 1G
|
||
reservations:
|
||
cpus: '0.5'
|
||
memory: 512M
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 5
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
container_name: jiao77_redis
|
||
restart: unless-stopped
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '0.5'
|
||
memory: 512M
|
||
volumes:
|
||
- redis_data:/data
|
||
|
||
api:
|
||
build:
|
||
context: ./backend
|
||
dockerfile: Dockerfile
|
||
container_name: jiao77_api
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
redis:
|
||
condition: service_started
|
||
environment:
|
||
NODE_ENV: production
|
||
DATABASE_URL: postgresql://${DB_USER}:${DB_PASSWORD}@postgres:5432/${DB_NAME}
|
||
REDIS_URL: redis://redis:6379
|
||
PORT: 3000
|
||
ports:
|
||
- "3000:3000"
|
||
restart: unless-stopped
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '1'
|
||
memory: 1G
|
||
reservations:
|
||
cpus: '0.5'
|
||
memory: 512M
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||
interval: 30s
|
||
timeout: 10s
|
||
retries: 3
|
||
|
||
web:
|
||
build:
|
||
context: ./frontend
|
||
dockerfile: Dockerfile
|
||
container_name: jiao77_web
|
||
depends_on:
|
||
- api
|
||
environment:
|
||
PUBLIC_API_URL: http://api:3000
|
||
ports:
|
||
- "3001:3000"
|
||
restart: unless-stopped
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '0.5'
|
||
memory: 512M
|
||
|
||
nginx:
|
||
image: nginx:alpine
|
||
container_name: jiao77_nginx
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||
- ./ssl:/etc/nginx/ssl:ro
|
||
- nginx_cache:/var/cache/nginx
|
||
depends_on:
|
||
- web
|
||
- api
|
||
restart: unless-stopped
|
||
deploy:
|
||
resources:
|
||
limits:
|
||
cpus: '0.5'
|
||
memory: 256M
|
||
|
||
volumes:
|
||
postgres_data:
|
||
redis_data:
|
||
nginx_cache:
|
||
```
|
||
- [ ] 本地测试 Docker Compose
|
||
|
||
#### 4.4 Nginx 配置(单机反向代理)
|
||
- [ ] 创建 `nginx.conf`
|
||
```nginx
|
||
user nginx;
|
||
worker_processes auto;
|
||
error_log /var/log/nginx/error.log warn;
|
||
pid /var/run/nginx.pid;
|
||
|
||
events {
|
||
worker_connections 2048;
|
||
use epoll;
|
||
}
|
||
|
||
http {
|
||
include /etc/nginx/mime.types;
|
||
default_type application/octet-stream;
|
||
|
||
# 性能优化
|
||
sendfile on;
|
||
tcp_nopush on;
|
||
tcp_nodelay on;
|
||
keepalive_timeout 65;
|
||
|
||
# 日志格式
|
||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||
'$status $body_bytes_sent "$http_referer" '
|
||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||
access_log /var/log/nginx/access.log main;
|
||
|
||
# 缓存配置
|
||
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=http_cache:10m
|
||
max_size=1g inactive=60m use_temp_path=off;
|
||
|
||
gzip on;
|
||
gzip_vary on;
|
||
gzip_proxied any;
|
||
gzip_comp_level 6;
|
||
gzip_types text/plain text/css text/xml text/javascript
|
||
application/json application/javascript application/xml+rss;
|
||
|
||
upstream api {
|
||
server api:3000;
|
||
}
|
||
|
||
upstream web {
|
||
server web:3000;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name _;
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl http2;
|
||
server_name jiao77.cn www.jiao77.cn;
|
||
|
||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||
ssl_protocols TLSv1.2 TLSv1.3;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
|
||
# 静态文件
|
||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff|woff2)$ {
|
||
proxy_pass http://web;
|
||
proxy_cache http_cache;
|
||
proxy_cache_valid 200 30d;
|
||
expires 30d;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# API 接口
|
||
location /api/ {
|
||
proxy_pass http://api;
|
||
proxy_cache http_cache;
|
||
proxy_cache_valid 200 10m;
|
||
proxy_cache_key "$scheme$request_method$host$request_uri";
|
||
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 /health {
|
||
access_log off;
|
||
proxy_pass http://api;
|
||
}
|
||
|
||
# 前端
|
||
location / {
|
||
proxy_pass http://web;
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 4.5 环境配置
|
||
- [ ] 创建 `.env` 模板
|
||
```bash
|
||
NODE_ENV=production
|
||
|
||
# 数据库
|
||
DB_NAME=jiao77_db
|
||
DB_USER=app
|
||
DB_PASSWORD=<strong-password>
|
||
|
||
# Redis
|
||
REDIS_URL=redis://redis:6379
|
||
|
||
# 应用
|
||
PORT=3000
|
||
FRONTEND_URL=https://jiao77.cn
|
||
API_BASE=https://jiao77.cn/api
|
||
|
||
# 日志
|
||
LOG_LEVEL=info
|
||
```
|
||
|
||
#### 4.6 启动脚本
|
||
- [ ] 创建 `scripts/start.sh`
|
||
```bash
|
||
#!/bin/bash
|
||
set -e
|
||
|
||
echo "🚀 启动应用..."
|
||
|
||
# 拉取最新代码
|
||
git pull origin main
|
||
|
||
# 构建镜像
|
||
docker-compose build --no-cache
|
||
|
||
# 启动服务
|
||
docker-compose up -d
|
||
|
||
# 等待数据库启动
|
||
sleep 10
|
||
|
||
# 运行数据库迁移
|
||
docker-compose exec -T api npm run migrate
|
||
|
||
# 健康检查
|
||
echo "✅ 等待服务健康..."
|
||
for i in {1..30}; do
|
||
if curl -f http://localhost:3000/health > /dev/null 2>&1; then
|
||
echo "✅ 应用已启动"
|
||
exit 0
|
||
fi
|
||
echo "⏳ 尝试 $i/30..."
|
||
sleep 1
|
||
done
|
||
|
||
echo "❌ 启动失败"
|
||
exit 1
|
||
```
|
||
- [ ] 创建 `scripts/stop.sh` - 优雅关闭
|
||
- [ ] 创建 `scripts/backup.sh` - 数据备份
|
||
|
||
### 🟠 P1 任务:性能优化
|
||
|
||
#### 4.7 数据库优化(单机)
|
||
- [ ] 优化 PostgreSQL 配置
|
||
```yaml
|
||
environment:
|
||
POSTGRES_INITDB_ARGS: |
|
||
-c shared_buffers=256MB
|
||
-c effective_cache_size=1GB
|
||
-c maintenance_work_mem=64MB
|
||
-c work_mem=16MB
|
||
```
|
||
- [ ] 创建必要的数据库索引
|
||
- [ ] 配置连接池大小
|
||
|
||
#### 4.8 API 性能优化
|
||
- [ ] 实现 API 分页(20 条/页)
|
||
- [ ] 实现查询结果缓存(Redis)
|
||
- [ ] 实现请求压缩(Gzip)
|
||
- [ ] 实现 HTTP 缓存头
|
||
|
||
#### 4.9 前端性能优化
|
||
- [ ] Astro 静态生成优化
|
||
- [ ] 图片优化和懒加载
|
||
- [ ] 代码分割
|
||
- [ ] 浏览器缓存配置
|
||
|
||
---
|
||
|
||
## 🎯 第五阶段:监控与备份(第 4 周)
|
||
|
||
### 🔴 P0 任务:核心监控
|
||
|
||
#### 5.1 应用健康监控
|
||
- [ ] 配置 `/health` 端点
|
||
- [ ] 配置 `/metrics` 端点(Prometheus 格式)
|
||
- [ ] 编写简单的监控脚本
|
||
```bash
|
||
#!/bin/bash
|
||
# 定期检查服务状态
|
||
curl -f http://localhost:3000/health || {
|
||
echo "❌ API 宕机" | mail -s "告警" admin@jiao77.cn
|
||
docker-compose restart api
|
||
}
|
||
```
|
||
|
||
#### 5.2 数据库监控
|
||
- [ ] 监控数据库连接数
|
||
- [ ] 监控磁盘使用率
|
||
- [ ] 监控查询缓慢日志
|
||
|
||
#### 5.3 系统监控
|
||
- [ ] 监控 CPU 使用率(< 70%)
|
||
- [ ] 监控内存使用率(< 80%)
|
||
- [ ] 监控磁盘使用率(< 80%)
|
||
- [ ] 配置告警规则
|
||
|
||
#### 5.4 日志管理
|
||
- [ ] 配置 Docker 日志驱动(json-file + 轮转)
|
||
```yaml
|
||
logging:
|
||
driver: "json-file"
|
||
options:
|
||
max-size: "100m"
|
||
max-file: "3"
|
||
```
|
||
- [ ] 实现日志聚合(可选:ELK Stack 精简版)
|
||
|
||
### 🔴 P0 任务:备份策略
|
||
|
||
#### 5.5 数据库备份
|
||
- [ ] 创建自动备份脚本
|
||
```bash
|
||
#!/bin/bash
|
||
BACKUP_DIR="/backup/postgresql"
|
||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
|
||
docker-compose exec -T postgres pg_dump \
|
||
-U app jiao77_db | gzip > $BACKUP_DIR/backup_$TIMESTAMP.sql.gz
|
||
|
||
# 保留最近 7 天的备份
|
||
find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +7 -delete
|
||
```
|
||
- [ ] 配置每日凌晨 2 点自动备份(crontab)
|
||
- [ ] 测试备份恢复流程
|
||
|
||
#### 5.6 应用备份
|
||
- [ ] 备份应用代码(Git)
|
||
- [ ] 备份配置文件
|
||
- [ ] 备份日志文件(定期轮转)
|
||
|
||
#### 5.7 存储规划
|
||
- [ ] 本地备份:30GB(7 天备份)
|
||
- [ ] 云存储备份:20GB(使用 Aliyun OSS 或 COS)
|
||
- [ ] 监控备份完成状态
|
||
|
||
### 🟠 P1 任务:故障恢复
|
||
|
||
#### 5.8 故障恢复流程
|
||
- [ ] 编写数据库恢复脚本
|
||
```bash
|
||
#!/bin/bash
|
||
# 恢复数据库
|
||
docker-compose exec -T postgres psql -U app jiao77_db < backup.sql
|
||
```
|
||
- [ ] 编写服务重启脚本
|
||
- [ ] 编写完整的应急响应手册
|
||
|
||
#### 5.9 测试和演练
|
||
- [ ] 测试备份恢复(月度)
|
||
- [ ] 测试服务重启(周度)
|
||
- [ ] 演练故障场景(季度)
|
||
|
||
---
|
||
|
||
## 🎯 第六阶段:上线和运维(第 5 周)
|
||
|
||
### 🔴 P0 任务:上线前准备
|
||
|
||
#### 6.1 最终检查清单
|
||
- [ ] 所有功能测试通过
|
||
- [ ] 性能基线建立
|
||
- [ ] 安全审计完成
|
||
- [ ] 备份策略验证
|
||
- [ ] 文档完备
|
||
- [ ] 监控告警配置
|
||
- [ ] 回滚计划准备
|
||
|
||
#### 6.2 SSL 证书配置
|
||
- [ ] 使用 Certbot 申请证书
|
||
```bash
|
||
sudo certbot certonly --standalone -d jiao77.cn -d www.jiao77.cn
|
||
```
|
||
- [ ] 配置自动续期
|
||
```bash
|
||
sudo systemctl enable certbot.timer
|
||
```
|
||
- [ ] 配置 Nginx 使用证书
|
||
|
||
#### 6.3 域名配置
|
||
- [ ] 配置 A 记录指向公网 IP
|
||
- [ ] 配置 CNAME(www)
|
||
- [ ] 配置 MX 记录(邮件,可选)
|
||
- [ ] 测试 DNS 解析
|
||
|
||
#### 6.4 上线检查
|
||
- [ ] SSH 配置(禁用密码登录)
|
||
- [ ] 防火墙规则验证
|
||
- [ ] 资源使用率基线
|
||
- [ ] 日志系统就位
|
||
|
||
### 🔴 P0 任务:正式部署
|
||
|
||
#### 6.5 发布到 VPS
|
||
- [ ] 登录 VPS
|
||
- [ ] 执行部署脚本:`bash scripts/start.sh`
|
||
- [ ] 验证所有服务正常运行
|
||
- [ ] 验证 HTTPS 连接
|
||
- [ ] 验证所有功能可用
|
||
|
||
#### 6.6 上线后验证
|
||
- [ ] 访问前端页面
|
||
- [ ] 测试评论功能
|
||
- [ ] 测试统计功能
|
||
- [ ] 检查日志无错误
|
||
|
||
#### 6.7 持续监控(上线后 24h)
|
||
- [ ] 监控 CPU、内存、磁盘
|
||
- [ ] 监控应用日志
|
||
- [ ] 监控用户反馈
|
||
- [ ] 记录性能数据
|
||
|
||
### 🟠 P1 任务:运维自动化
|
||
|
||
#### 6.8 Cron 任务设置
|
||
- [ ] 每日 02:00 - 数据库备份
|
||
- [ ] 每周日 03:00 - 系统备份
|
||
- [ ] 每小时 - 健康检查
|
||
- [ ] 每 10 分钟 - 监控检查
|
||
|
||
#### 6.9 日常运维任务
|
||
- [ ] 周度备份验证
|
||
- [ ] 月度性能分析
|
||
- [ ] 季度灾难恢复演练
|
||
- [ ] 定期安全更新
|
||
|
||
#### 6.10 长期维护计划
|
||
- [ ] Docker 镜像定期更新
|
||
- [ ] 依赖包定期更新
|
||
- [ ] PostgreSQL 大版本升级计划
|
||
- [ ] 磁盘空间管理计划
|
||
|
||
### 🟠 P1 任务:文档和培训
|
||
|
||
#### 6.11 运维文档
|
||
- [ ] 部署操作手册
|
||
- [ ] 故障排查指南
|
||
- [ ] 备份恢复流程
|
||
- [ ] 监控告警规则
|
||
|
||
#### 6.12 应急响应
|
||
- [ ] 编写应急响应计划
|
||
- [ ] 列出主要故障场景
|
||
- [ ] 编写恢复步骤
|
||
|
||
---
|
||
|
||
## 🎯 可选优化(P2 任务)
|
||
|
||
### 🟡 P2 任务:性能优化
|
||
|
||
#### 7.1 查询优化
|
||
- [ ] 数据库慢查询分析
|
||
- [ ] 添加查询索引
|
||
- [ ] 优化 N+1 查询
|
||
- [ ] 实现查询结果缓存
|
||
|
||
#### 7.2 缓存优化
|
||
- [ ] Redis 缓存热数据
|
||
- [ ] 实现缓存预热
|
||
- [ ] 实现缓存失效策略
|
||
|
||
#### 7.3 API 优化
|
||
- [ ] 减少 API 响应大小
|
||
- [ ] 实现字段选择
|
||
- [ ] 实现 API 聚合
|
||
|
||
---
|
||
|
||
## 🎯 后续扩展(P3 任务)
|
||
|
||
### 🟢 P3 任务:功能扩展
|
||
|
||
#### 8.1 用户系统
|
||
- [ ] 用户认证
|
||
- [ ] 用户资料
|
||
- [ ] 权限管理
|
||
|
||
#### 8.2 社区功能
|
||
- [ ] 评论回复
|
||
- [ ] 用户关注
|
||
- [ ] 排行榜
|
||
|
||
#### 8.3 数据分析
|
||
- [ ] 用户行为分析
|
||
- [ ] 内容热力分析
|
||
|
||
---
|
||
|
||
## 📊 单机配置资源预算
|
||
|
||
### VPS 规格建议
|
||
```
|
||
配置:4核 CPU、8GB 内存、100GB SSD
|
||
操作系统:Ubuntu 20.04 LTS
|
||
带宽:大带宽(>10 Mbps)
|
||
```
|
||
|
||
### 容器资源分配
|
||
```
|
||
PostgreSQL:
|
||
- CPU limit: 1 核
|
||
- Memory limit: 1GB
|
||
- 实际使用:0.5 核、500MB
|
||
|
||
Redis:
|
||
- CPU limit: 0.5 核
|
||
- Memory limit: 512MB
|
||
- 实际使用:0.2 核、256MB
|
||
|
||
API (Node.js):
|
||
- CPU limit: 1 核
|
||
- Memory limit: 1GB
|
||
- 实际使用:0.3 核、300MB
|
||
|
||
Web (Astro):
|
||
- CPU limit: 0.5 核
|
||
- Memory limit: 512MB
|
||
- 实际使用:0.1 核、100MB
|
||
|
||
Nginx:
|
||
- CPU limit: 0.5 核
|
||
- Memory limit: 256MB
|
||
- 实际使用:0.05 核、50MB
|
||
|
||
总计峰值:3 核、3.5GB
|
||
实际使用:1.15 核、1.2GB(预留安全边际)
|
||
```
|
||
|
||
### 存储分配(100GB)
|
||
```
|
||
系统和 Docker:20GB
|
||
PostgreSQL 数据:30GB
|
||
Redis 数据:2GB
|
||
应用代码:5GB
|
||
日志文件:3GB
|
||
备份文件:30GB
|
||
缓冲区:10GB
|
||
```
|
||
|
||
---
|
||
|
||
## ⏱️ 时间表(针对单机优化)
|
||
|
||
```
|
||
第1周 - VPS 准备 + 后端开发 60 小时
|
||
第2周 - 前端开发 + 集成 50 小时
|
||
第3周 - Docker 化 + 部署 40 小时
|
||
第4周 - 监控 + 备份 + 测试 40 小时
|
||
第5周 - 上线 + 优化 30 小时
|
||
──────────────────────────────────
|
||
总计:220 小时 ≈ 5-6 周(单人开发)
|
||
≈ 3-4 周(双人开发)
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 快速开始指南
|
||
|
||
### 立即开始(第 1 天)
|
||
```bash
|
||
# 1. SSH 登录 VPS
|
||
ssh root@<ip>
|
||
|
||
# 2. 安装 Docker 和 Compose
|
||
curl -fsSL https://get.docker.com | sh
|
||
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||
sudo chmod +x /usr/local/bin/docker-compose
|
||
|
||
# 3. 克隆项目
|
||
git clone <repo> /home/app
|
||
|
||
# 4. 配置环境
|
||
cd /home/app
|
||
cp .env.example .env
|
||
# 编辑 .env,配置数据库密码等
|
||
|
||
# 5. 启动应用
|
||
docker-compose up -d
|
||
|
||
# 6. 验证
|
||
curl http://localhost:3000/health
|
||
```
|
||
|
||
### 配置 SSL(第 2 天)
|
||
```bash
|
||
# 使用 Certbot
|
||
sudo apt install certbot
|
||
sudo certbot certonly --standalone -d jiao77.cn -d www.jiao77.cn
|
||
|
||
# 在 docker-compose.yml 中配置证书路径
|
||
# 重启 Nginx
|
||
docker-compose restart nginx
|
||
```
|
||
|
||
### 配置备份(第 3 天)
|
||
```bash
|
||
# 添加 cron 任务
|
||
crontab -e
|
||
|
||
# 添加备份命令
|
||
0 2 * * * cd /home/app && bash scripts/backup.sh
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 单机部署最佳实践
|
||
|
||
### ✅ DO(应该做)
|
||
- ✅ 定期备份数据库
|
||
- ✅ 监控资源使用率
|
||
- ✅ 设置告警规则
|
||
- ✅ 使用 Alpine 镜像减少内存
|
||
- ✅ 配置资源限制
|
||
- ✅ 日志轮转
|
||
- ✅ 定期更新依赖
|
||
|
||
### ❌ DON'T(不应该做)
|
||
- ❌ 不使用 root 运行容器
|
||
- ❌ 不在生产用开发镜像
|
||
- ❌ 不忽视监控告警
|
||
- ❌ 不手动管理依赖版本
|
||
- ❌ 不连续部署到生产(需要测试)
|
||
|
||
---
|
||
|
||
## 🔧 常见问题排查
|
||
|
||
### 问题 1:内存不足
|
||
```bash
|
||
# 检查内存使用
|
||
docker stats
|
||
|
||
# 解决方案:
|
||
# 1. 减少 PostgreSQL 缓冲区
|
||
# 2. 减少 Redis 最大内存
|
||
# 3. 优化 Node.js 堆大小
|
||
```
|
||
|
||
### 问题 2:磁盘满
|
||
```bash
|
||
# 检查磁盘
|
||
df -h
|
||
|
||
# 清理 Docker 镜像
|
||
docker image prune -a
|
||
|
||
# 清理 Docker 容器
|
||
docker container prune
|
||
```
|
||
|
||
### 问题 3:性能下降
|
||
```bash
|
||
# 检查慢查询
|
||
docker-compose logs api | grep "SLOW"
|
||
|
||
# 检查数据库连接
|
||
docker-compose exec postgres psql -c "SELECT count(*) FROM pg_stat_activity;"
|
||
```
|
||
|
||
---
|
||
|
||
## 📈 性能指标目标
|
||
|
||
| 指标 | 目标 | 说明 |
|
||
|-----|------|------|
|
||
| 页面加载时间 | < 2s | 首次加载 |
|
||
| API 响应时间 | < 200ms | 平均响应 |
|
||
| 数据库查询 | < 100ms | 95 分位 |
|
||
| CPU 使用率 | < 60% | 平均 |
|
||
| 内存使用率 | < 75% | 峰值 |
|
||
| 磁盘使用率 | < 80% | 实时 |
|
||
| 可用性 | 99.5% | 月度 |
|
||
|
||
---
|
||
|
||
## 🎯 总结
|
||
|
||
这个方案针对**单机 VPS 环境**进行了全面优化:
|
||
|
||
1. **资源优化** - 使用 Alpine 镜像、资源限制、连接池优化
|
||
2. **部署简化** - Docker Compose 单文件部署、Nginx 反向代理
|
||
3. **监控自动化** - 健康检查、告警规则、自动重启
|
||
4. **备份完善** - 每日备份、7 天保留、恢复流程
|
||
5. **成本低** - 无需复杂基础设施、单机成本 ~¥100/月
|
||
|
||
**建议**:从 P0 任务开始,按阶段稳步推进,确保每个阶段的质量。
|
||
|