# 部署指南 ## 环境准备 ### 系统要求 - Linux (Ubuntu 22.04+ / Debian 12+ 推荐) - 2+ CPU 核心(启用独立 Worker 建议 4+) - 4GB+ 内存 - 50GB+ 磁盘空间 ### 依赖安装 ```bash # Ubuntu/Debian sudo apt update sudo apt install -y \ build-essential \ pkg-config \ libssl-dev \ libpq-dev \ cmake \ nasm \ libjpeg-dev \ libpng-dev \ libwebp-dev # 安装 Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env # 初始化数据库会用到 psql(建议安装 PostgreSQL client) sudo apt install -y postgresql-client # 安装 Node.js (前端构建) curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs ``` --- ## 本地开发 ### 1. 启动数据库服务 ```bash # 使用 Docker Compose 启动 PostgreSQL 和 Redis docker-compose -f docker/docker-compose.dev.yml up -d ``` `docker/docker-compose.dev.yml`: ```yaml version: '3.8' services: postgres: image: postgres:16-alpine environment: POSTGRES_USER: imageforge POSTGRES_PASSWORD: devpassword POSTGRES_DB: imageforge ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data # 可选:MinIO(S3 兼容,本地开发更接近生产) # minio: # image: minio/minio:RELEASE.2024-01-28T20-20-01Z # command: server /data --console-address ":9001" # environment: # MINIO_ROOT_USER: minioadmin # MINIO_ROOT_PASSWORD: minioadmin # ports: # - "9000:9000" # - "9001:9001" # volumes: # - minio_data:/data volumes: postgres_data: redis_data: minio_data: ``` ### 2. 配置环境变量 ```bash cp .env.example .env ``` `.env.example`: ```bash # 运行模式:建议将 API 与 Worker 分开运行 IMAGEFORGE_ROLE=api # api | worker # 服务配置 HOST=0.0.0.0 PORT=8080 PUBLIC_BASE_URL=http://localhost:8080 RUST_LOG=info,imageforge=debug # 数据库 DATABASE_URL=postgres://imageforge:devpassword@localhost:5432/imageforge # Redis REDIS_URL=redis://localhost:6379 # Worker 并发(每个批量任务内同时处理的文件数) WORKER_CONCURRENCY=4 # JWT(网站/管理后台) JWT_SECRET=your-super-secret-key-change-in-production JWT_EXPIRY_HOURS=168 # API Key API_KEY_PEPPER=please-change-this-in-production # 存储(生产建议 S3/MinIO + 预签名 URL) STORAGE_TYPE=local # local | s3 STORAGE_PATH=./uploads # 预签名下载链接过期(分钟) SIGNED_URL_TTL_MINUTES=60 # S3 配置(如果使用 S3/MinIO) # S3_ENDPOINT=http://localhost:9000 # S3_BUCKET=your-bucket # S3_REGION=us-east-1 # S3_ACCESS_KEY=xxx # S3_SECRET_KEY=xxx # 计费(已确认:Stripe) BILLING_PROVIDER=stripe STRIPE_SECRET_KEY=sk_test_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx # 限制(默认值;最终以套餐/用户覆盖为准) ALLOW_ANONYMOUS_UPLOAD=true ANON_MAX_FILE_SIZE_MB=5 ANON_MAX_FILES_PER_BATCH=5 ANON_DAILY_UNITS=10 MAX_IMAGE_PIXELS=40000000 IDEMPOTENCY_TTL_HOURS=24 # 结果保留(匿名默认;登录用户按套餐 retention_days) ANON_RETENTION_HOURS=24 # 管理员初始账户 ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD=changeme123 ``` ### 3. 初始化数据库 ```bash # 运行首期迁移(可重复执行) psql "$DATABASE_URL" -f migrations/001_init.sql ``` ### 4. 启动开发服务器 ```bash # 后端 API (热重载) cargo install cargo-watch IMAGEFORGE_ROLE=api cargo watch -x run # 后端 Worker(另一个终端,处理异步/批量任务) IMAGEFORGE_ROLE=worker cargo watch -x run # 前端 (另一个终端) cd frontend npm install npm run dev ``` ### 5. Stripe Webhook 本地调试(可选) 本地调试 Stripe 订阅/支付状态,通常需要将 Stripe Webhook 转发到本机: ```bash # 1) 安装并登录 Stripe CLI(按官方文档) # 2) 监听并转发到你的后端回调地址 stripe listen --forward-to http://localhost:8080/api/v1/webhooks/stripe # CLI 会输出一个 whsec_...,写入 .env 的 STRIPE_WEBHOOK_SECRET ``` --- ## 生产部署 > 注意:以下内容为生产部署模板示例;仓库当前首期仅提供开发用 `docker/docker-compose.dev.yml`,生产 compose/Dockerfile/nginx/k8s 等可在开工阶段按需落地并调整。 ### 方案一:Docker Compose(推荐小规模) `docker/docker-compose.prod.yml`: ```yaml version: '3.8' services: api: build: context: .. dockerfile: docker/Dockerfile environment: - IMAGEFORGE_ROLE=api - BILLING_PROVIDER=stripe - PUBLIC_BASE_URL=https://your-domain.com - STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY} - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET} - DATABASE_URL=postgres://imageforge:${DB_PASSWORD}@postgres:5432/imageforge - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET} - STORAGE_TYPE=local - STORAGE_PATH=/app/uploads ports: - "8080:8080" volumes: - uploads:/app/uploads depends_on: - postgres - redis restart: unless-stopped worker: build: context: .. dockerfile: docker/Dockerfile environment: - IMAGEFORGE_ROLE=worker - DATABASE_URL=postgres://imageforge:${DB_PASSWORD}@postgres:5432/imageforge - REDIS_URL=redis://redis:6379 - JWT_SECRET=${JWT_SECRET} - STORAGE_TYPE=local - STORAGE_PATH=/app/uploads volumes: - uploads:/app/uploads depends_on: - postgres - redis restart: unless-stopped postgres: image: postgres:16-alpine environment: POSTGRES_USER: imageforge POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: imageforge volumes: - postgres_data:/var/lib/postgresql/data restart: unless-stopped redis: image: redis:7-alpine volumes: - redis_data:/data restart: unless-stopped nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - /etc/letsencrypt:/etc/letsencrypt:ro depends_on: - api restart: unless-stopped volumes: uploads: postgres_data: redis_data: ``` `docker/Dockerfile`: ```dockerfile FROM rust:1.92-bookworm AS builder WORKDIR /app RUN apt-get update && apt-get install -y --no-install-recommends \ cmake \ nasm \ pkg-config \ && rm -rf /var/lib/apt/lists/* COPY Cargo.toml Cargo.lock ./ COPY src ./src COPY migrations ./migrations COPY templates ./templates RUN cargo build --release # 前端构建阶段 FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm ci COPY frontend ./ RUN npm run build # 运行阶段 FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=builder /app/target/release/imageforge ./imageforge COPY --from=frontend-builder /app/frontend/dist ./static COPY migrations ./migrations RUN mkdir -p uploads ENV HOST=0.0.0.0 ENV PORT=8080 EXPOSE 8080 CMD ["./imageforge"] ``` `docker/nginx.conf`: ```nginx events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; # 日志 access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # 文件上传大小限制 client_max_body_size 100M; # Gzip gzip on; gzip_types text/plain text/css application/json application/javascript; upstream backend { server api:8080; } server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name your-domain.com; ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem; # SSL 配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; # 静态文件 location /static/ { proxy_pass http://backend; expires 30d; add_header Cache-Control "public, immutable"; } # WebSocket location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; } # API 和其他请求 location / { proxy_pass http://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; } } } ``` ### 部署步骤 ```bash # 1. 创建 .env 文件 cat > .env << EOF DB_PASSWORD=your-secure-db-password JWT_SECRET=your-very-long-random-jwt-secret-at-least-32-chars STRIPE_SECRET_KEY=sk_live_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx EOF # 2. 获取 SSL 证书 sudo certbot certonly --standalone -d your-domain.com # 3. 构建并启动 docker-compose -f docker/docker-compose.prod.yml up -d --build # 4. 查看日志 docker-compose -f docker/docker-compose.prod.yml logs -f api ``` --- ### 方案二:Kubernetes(大规模) `k8s/deployment.yaml`: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: imageforge spec: replicas: 3 selector: matchLabels: app: imageforge template: metadata: labels: app: imageforge spec: containers: - name: imageforge image: your-registry/imageforge:latest ports: - containerPort: 8080 env: - name: IMAGEFORGE_ROLE value: api - name: DATABASE_URL valueFrom: secretKeyRef: name: imageforge-secrets key: database-url - name: REDIS_URL valueFrom: secretKeyRef: name: imageforge-secrets key: redis-url - name: JWT_SECRET valueFrom: secretKeyRef: name: imageforge-secrets key: jwt-secret resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "2Gi" cpu: "2000m" livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 10 periodSeconds: 30 readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 5 periodSeconds: 10 --- apiVersion: v1 kind: Service metadata: name: imageforge spec: selector: app: imageforge ports: - port: 80 targetPort: 8080 type: ClusterIP --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: imageforge annotations: kubernetes.io/ingress.class: nginx cert-manager.io/cluster-issuer: letsencrypt-prod spec: tls: - hosts: - your-domain.com secretName: imageforge-tls rules: - host: your-domain.com http: paths: - path: / pathType: Prefix backend: service: name: imageforge port: number: 80 --- apiVersion: apps/v1 kind: Deployment metadata: name: imageforge-worker spec: replicas: 2 selector: matchLabels: app: imageforge-worker template: metadata: labels: app: imageforge-worker spec: containers: - name: imageforge-worker image: your-registry/imageforge:latest env: - name: IMAGEFORGE_ROLE value: worker - name: DATABASE_URL valueFrom: secretKeyRef: name: imageforge-secrets key: database-url - name: REDIS_URL valueFrom: secretKeyRef: name: imageforge-secrets key: redis-url - name: JWT_SECRET valueFrom: secretKeyRef: name: imageforge-secrets key: jwt-secret resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "4Gi" cpu: "4000m" ``` --- ## 监控与日志 ### Prometheus 指标 应用暴露 `/metrics` 端点: ```rust // 在代码中添加指标 use prometheus::{Counter, Histogram}; lazy_static! { static ref COMPRESSION_REQUESTS: Counter = Counter::new( "imageforge_compression_requests_total", "Total number of compression requests" ).unwrap(); static ref COMPRESSION_DURATION: Histogram = Histogram::with_opts( HistogramOpts::new( "imageforge_compression_duration_seconds", "Time spent compressing images" ) ).unwrap(); } ``` ### Grafana 仪表板 监控项目: - 请求量 / QPS - 响应时间 P50/P95/P99 - 错误率 - 压缩任务队列长度 - CPU / 内存使用率 - 磁盘使用率 ### 日志聚合 使用 ELK Stack 或 Loki: ```yaml # docker-compose 添加 Loki loki: image: grafana/loki:2.9.0 ports: - "3100:3100" command: -config.file=/etc/loki/local-config.yaml promtail: image: grafana/promtail:2.9.0 volumes: - /var/log:/var/log - ./promtail-config.yml:/etc/promtail/config.yml command: -config.file=/etc/promtail/config.yml ``` --- ## 备份策略 ### 数据库备份 ```bash #!/bin/bash # backup.sh DATE=$(date +%Y%m%d_%H%M%S) BACKUP_DIR=/backups # PostgreSQL 备份 docker exec postgres pg_dump -U imageforge imageforge | 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://your-bucket/backups/ ``` 添加到 crontab: ```bash 0 3 * * * /path/to/backup.sh ``` ### 上传文件备份 如果使用本地存储,定期同步到 S3: ```bash aws s3 sync /app/uploads s3://your-bucket/uploads --delete ``` --- ## 故障排查 ### 常见问题 **1. 数据库连接失败** ```bash # 检查 PostgreSQL 状态 docker-compose logs postgres # 测试连接 docker exec -it postgres psql -U imageforge -d imageforge -c "SELECT 1" ``` **2. 压缩失败** ```bash # 检查应用日志 docker-compose logs api | grep ERROR docker-compose logs worker | grep ERROR # 检查磁盘空间 df -h ``` **3. 内存不足** ```bash # 查看内存使用 docker stats # 调整容器内存限制 ``` **4. 上传超时** ```bash # 检查 Nginx 配置 # client_max_body_size 和 proxy_read_timeout ``` ### 健康检查端点 ``` GET /health { "status": "healthy", "database": "connected", "redis": "connected", "storage": "available", "uptime": 3600 } ```