docs(deploy): 生产部署手册-elane(含代码 review 结论:配置清单/隐患/待办);meida_backend 子模块指针 → 90349f7(深度诊断+邮箱登录+admin 看板)
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
# 生产部署手册:elane.xiangsilian.com
|
||||
|
||||
> 创建日期:2026-09-14 | 适用:meida_backend 首次公网部署
|
||||
> 目标服务器:elane.xiangsilian.com → 175.178.100.240(腾讯云 Ubuntu,已有 nginx)
|
||||
> 前置:代码 review 已完成(12 项结论全部核实),本文所有命令均按当前代码核实过。
|
||||
|
||||
---
|
||||
|
||||
## 0. 部署前必读(三个硬提醒)
|
||||
|
||||
1. **⚠️ 华为登录还差一步 AGC 配置。** 登录系统已开发完成:邮箱验证码(前后端全
|
||||
链路可用)+ 华为账号登录(端侧已接入,后端验签早就绪)。唯一缺口:AGC 开通
|
||||
Account Kit 后把 **client_id** 填进 App 的 `entry/src/main/module.json5`
|
||||
(替换 `TODO_AGC_CLIENT_ID` 占位值),并确保 AGC 上的包名/证书指纹与本机
|
||||
签名一致。在此之前华为登录按钮会提示「暂未开放」,邮箱登录可正常使用。
|
||||
2. **⚠️ 备案**:`elane.xiangsilian.com` 是现有备案下**新增的子域名**。需在腾讯云
|
||||
控制台「备案 → 变更备案」中提交变更(主体不变、免费、线上办理、约 1~2 周)。
|
||||
部署不依赖备案完成,但华为应用市场审核会核对。
|
||||
3. **⚠️ 服务器安全组**:放行 443;**不要**对公网开放 55432/56379(数据库端口)。
|
||||
|
||||
## 1. 目标架构
|
||||
|
||||
```
|
||||
App ──https──▶ elane.xiangsilian.com:443 (nginx + Let's Encrypt 证书)
|
||||
└─ /api/v1/ ──▶ 127.0.0.1:8666 (gunicorn/uvicorn)
|
||||
└─ /healthz、/readyz ──▶ 127.0.0.1:8666(监控用,可只内网)
|
||||
本机进程:PostgreSQL 16+pgvector(docker, 127.0.0.1:55432)
|
||||
Redis 7(docker, 127.0.0.1:56379)
|
||||
celery worker -Q cpu,io(systemd)
|
||||
数据卷: pgdata / redisdata(docker 卷)+ /home/ubuntu/meida_data/{uploads,exports}
|
||||
```
|
||||
|
||||
说明:本部署为**单进程 api + 单 worker**(当前业务全部走 cpu/io 队列,gpu 队列
|
||||
尚未启用,见 review §10)。不上 K8s,与架构文档 §14.1 的 V1 形态一致。
|
||||
|
||||
## 2. 服务器初始化
|
||||
|
||||
```bash
|
||||
# 系统要求:Ubuntu 22.04+,2C4G 起步(报告生成是 LLM IO 密集型,不吃 CPU)
|
||||
|
||||
# 2.1 Docker(如未装)
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
sudo usermod -aG docker $USER && newgrp docker
|
||||
|
||||
# 2.2 uv(Python 包管理,项目要求 Python >= 3.12)
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# 2.3 weasyprint 系统依赖(PDF 导出用,缺了首次导出必炸——review 重点项)
|
||||
sudo apt-get update && sudo apt-get install -y \
|
||||
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
|
||||
|
||||
# 2.4 nginx + certbot(机器上已有 nginx 则跳过安装,直接进 §5)
|
||||
sudo apt-get install -y nginx certbot python3-certbot-nginx
|
||||
```
|
||||
|
||||
## 3. 拉代码 + 数据库
|
||||
|
||||
```bash
|
||||
git clone https://git.xiangsilian.com/xsl/meida_backend.git ~/meida_backend
|
||||
cd ~/meida_backend
|
||||
|
||||
# 3.1 改数据库口令:编辑 deploy/docker-compose.dev.yml
|
||||
# POSTGRES_PASSWORD: meida → 换成强口令(记为 <PG_PASS>)
|
||||
# 并把两个端口映射绑到本机(防公网暴露):
|
||||
# "55432:5432" → "127.0.0.1:55432:5432"
|
||||
# "56379:6379" → "127.0.0.1:56379:6379"
|
||||
|
||||
# 3.2 起 PG + Redis(只起数据层,不起应用)
|
||||
docker compose -f deploy/docker-compose.dev.yml up -d postgres redis
|
||||
|
||||
# 3.3 应用依赖(生产:不装 dev 依赖、锁死版本)
|
||||
uv sync --no-dev --frozen
|
||||
```
|
||||
|
||||
## 4. 生产 .env
|
||||
|
||||
`cp .env.example .env` 后按下表修改。**带 ⚠️ 的行漏配会导致安全事故或拒启动**。
|
||||
|
||||
```bash
|
||||
# ── 环境(最关键:prod 自动关闭 dev-token 登录和 /api/v1/docs)──
|
||||
APP_ENV=prod # ⚠️ 不设这行 = dev-token 对外敞开
|
||||
LOG_LEVEL=INFO
|
||||
LOG_JSON=true
|
||||
|
||||
# ── 数据依赖(默认值指向 localhost:55432/56379,口令与 3.1 一致)──
|
||||
DATABASE_URL=postgresql+asyncpg://meida:<PG_PASS>@127.0.0.1:55432/meida
|
||||
REDIS_URL=redis://127.0.0.1:56379/0
|
||||
CELERY_BROKER_URL=redis://127.0.0.1:56379/1
|
||||
CELERY_RESULT_BACKEND=redis://127.0.0.1:56379/2
|
||||
|
||||
# ── 鉴权 ──
|
||||
JWT_SECRET=<执行 openssl rand -hex 32 生成> # ⚠️ <32 字符或占位符会拒启动(有意保护)
|
||||
DEV_AUTH_ENABLED=false # 双保险(APP_ENV=prod 本身已关)
|
||||
|
||||
# ── LLM(DeepSeek 主 / 智谱备,key 从开发机 .env 抄)──
|
||||
DEEPSEEK_API_KEY=sk-...
|
||||
ZHIPU_API_KEY=...
|
||||
# LLM_DAILY_BUDGET_CENTS=100000 # ⚠️ 当前是占位值(¥1000/天),按实测成本重算后再定
|
||||
|
||||
# ── 存储:无 COS 走本地磁盘降级(当前形态)──
|
||||
# COS_* 全部留空;上传走 POST /api/v1/uploads/direct
|
||||
UPLOAD_DIR=/home/ubuntu/meida_data/uploads # ⚠️ 绝对路径,相对 cwd 在 systemd 下不可靠
|
||||
REPORT_EXPORT_DIR=/home/ubuntu/meida_data/exports
|
||||
|
||||
# ── 登录通道 ──
|
||||
# 华为账号(后端已支持;HUAWEI_AUDIENCE 不配则华为登录 40102,是有意设计)
|
||||
# HUAWEI_AUDIENCE=6917613748100335131
|
||||
# HUAWEI_AUDIENCE 对应 AGC 应用的 App ID(前置开通清单已登记)
|
||||
#
|
||||
# 邮箱验证码(正式兜底通道,前后端已实现,2026-09-14):
|
||||
# ⚠️ EMAIL_PROVIDER=fake 会把验证码明文打进日志,仅限本地开发,生产必须 smtp
|
||||
EMAIL_PROVIDER=smtp
|
||||
SMTP_HOST=smtp.qq.com # 或 smtp.163.com
|
||||
SMTP_PORT=465
|
||||
SMTP_USER=your_sender@qq.com
|
||||
SMTP_PASS=<邮箱 SMTP 授权码> # QQ/163 邮箱设置里生成,不是登录密码
|
||||
SMTP_USE_SSL=true
|
||||
MAIL_FROM=your_sender@qq.com # 留空则用 SMTP_USER
|
||||
# 频控默认值(对齐短信口径):同邮箱 60s 冷却 / 1h≤5 / 24h≤10,IP 24h≤20,设备 24h≤10
|
||||
#
|
||||
# 短信通道:已废弃(个人主体办不下签名),SMS_* 保持默认不启用即可
|
||||
```
|
||||
|
||||
> 注:短信通道已废弃不做。`SMS_PROVIDER` 保持默认 fake 即可——短信端点未接路由、
|
||||
> App 端无入口,永远不会被触发;但生产日志审计时留意别让任何人把它接到路由上。
|
||||
|
||||
## 5. 迁移 + 内测配额口径
|
||||
|
||||
```bash
|
||||
cd ~/meida_backend
|
||||
|
||||
# 5.1 迁移(alembic 走 DATABASE_URL;0003 会自动灌 bl_quota_rule 种子,
|
||||
# 含 full_report 行——全新库不需要手动补)
|
||||
uv run alembic upgrade head
|
||||
|
||||
# 5.2 (可选,内测期放宽;上架前必须改回)开发库当前口径:
|
||||
docker exec meida-postgres psql -U meida -d meida -c \
|
||||
"UPDATE bl_quota_rule SET base_limit=base_limit*10, task_bonus_max=task_bonus_max*10, hard_cap=hard_cap*10;"
|
||||
```
|
||||
|
||||
## 6. 进程常驻(systemd)
|
||||
|
||||
```bash
|
||||
sudo tee /etc/systemd/system/meida-api.service <<'EOF'
|
||||
[Unit]
|
||||
Description=meida api
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/meida_backend
|
||||
Environment=PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
ExecStart=/home/ubuntu/.local/bin/uv run --no-sync gunicorn apps.api.main:app \
|
||||
--worker-class uvicorn.workers.UvicornWorker --workers 2 \
|
||||
--bind 127.0.0.1:8666 --timeout 120 --graceful-timeout 30 --access-logfile -
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo tee /etc/systemd/system/meida-worker.service <<'EOF'
|
||||
[Unit]
|
||||
Description=meida celery worker (cpu,io)
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/meida_backend
|
||||
Environment=PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
ExecStart=/home/ubuntu/.local/bin/uv run --no-sync celery -A apps.worker.celery_app worker \
|
||||
-Q cpu,io --pool=solo --loglevel=INFO
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now meida-api meida-worker
|
||||
|
||||
# 验证(本机)
|
||||
curl -s http://127.0.0.1:8666/healthz # {"code":0,...,"status":"ok"}
|
||||
curl -s http://127.0.0.1:8666/readyz # 依赖就绪(查 PG+Redis)
|
||||
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8666/api/v1/auth/dev-token \
|
||||
-H 'Content-Type: application/json' -d '{"device_key":"x"}' # 必须是 404(dev-token 已关)
|
||||
```
|
||||
|
||||
要点:
|
||||
- `--no-sync` 防止 systemd 环境下 uv 试图联网解析依赖;依赖已在 §3.3 装好。
|
||||
- 不需要 celery beat(全仓库无定时任务;temp 目录清理等上线后再说,见 §8)。
|
||||
- worker 与 api 的 UPLOAD_DIR/REPORT_EXPORT_DIR 是同一绝对路径,天然共享,无需挂卷。
|
||||
|
||||
## 7. nginx + HTTPS
|
||||
|
||||
```bash
|
||||
# 7.1 签证书(自动改 nginx 配置 + 配置续期)
|
||||
sudo certbot --nginx -d elane.xiangsilian.com
|
||||
|
||||
# 7.2 编辑 /etc/nginx/sites-enabled/ 下 elane 的 server 块,追加反代:
|
||||
```
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name elane.xiangsilian.com;
|
||||
# certbot 自动填 ssl_certificate / ssl_certificate_key
|
||||
|
||||
location /api/v1/ {
|
||||
proxy_pass http://127.0.0.1:8666;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
client_max_body_size 20m; # 上传上限与 UPLOAD_MAX_BYTES 对齐
|
||||
proxy_read_timeout 120s; # 报告生成等长请求
|
||||
}
|
||||
|
||||
# 监控探活(可选;不配则只在服务器本机 curl)
|
||||
location = /healthz { proxy_pass http://127.0.0.1:8666/healthz; }
|
||||
location = /readyz { proxy_pass http://127.0.0.1:8666/readyz; }
|
||||
|
||||
# /metrics 不要暴露公网(代码里它不受 PROMETHEUS_ENABLED 控制,见 §8)
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
|
||||
# 7.3 安全组:腾讯云控制台放行 443/tcp;确认 55432/56379/8666 未对公网开放
|
||||
|
||||
# 7.4 公网验证
|
||||
curl -s https://elane.xiangsilian.com/healthz
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://elane.xiangsilian.com/api/v1/quota # 401 = 服务活了
|
||||
curl -s -o /dev/null -w '%{http_code}\n' https://elane.xiangsilian.com/api/v1/docs # 404 = swagger 已关
|
||||
```
|
||||
|
||||
## 8. 已知限制与待办(代码 review 结论,部署后按此跟进)
|
||||
|
||||
| # | 事项 | 处置 |
|
||||
|---|---|---|
|
||||
| 1 | **华为登录差 client_id**:端侧已接入(占位拦截不 crash),需 AGC 开通 Account Kit 后填 `module.json5` | 用户在 AGC 操作(前置开通清单 §1),拿到 client_id 替换 `TODO_AGC_CLIENT_ID` |
|
||||
| 2 | ~~SMS_PROVIDER 默认 fake~~ 短信已废弃;邮箱通道已上线 | 生产 `.env` 用 `EMAIL_PROVIDER=smtp` + 真实邮箱授权码 |
|
||||
| 3 | CORS 中间件不存在 | 原生 App 不受影响;admin 后台浏览器访问前必须加白名单 |
|
||||
| 4 | COS temp 桶 2 小时生命周期 | 未接 COS 前, uploads 目录只增不减,需 cron 清理:如 `find /home/ubuntu/meida_data/uploads -type f -mtime +2 -delete`(temp 场景文件 2 小时后可删) |
|
||||
| 5 | 数据备份脚本缺失(FR-SYS-09) | 最低限度:`pg_dump` 每日 cron + `meida_data/` 快照 |
|
||||
| 6 | `LLM_DAILY_BUDGET_CENTS` 是占位值 | 用真实 token 成本重算后改 .env |
|
||||
| 7 | `/metrics` 无条件暴露 | 已按 §7 不反代公网;要监控可后期加 IP 白名单 |
|
||||
| 8 | DEBUG 配置项是死代码 | 无行为影响,忽略即可 |
|
||||
| 9 | 本地上传的照片无对外 URL(已知缺口) | 报告场景服务端自用不受影响;需回显时接 COS |
|
||||
| 10 | 运营后台(行为分析)需设 `ADMIN_TOKEN` 才装配 | 端点 `/admin/api/v1/users`、`/users/{id}`、`/events`、`/stats/overview`(X-Admin-Token 鉴权,PII 掩码后输出);admin 是独立应用需单独进程:`uv run uvicorn "apps.admin.app:create_admin_app" --host 127.0.0.1 --port 8001`,nginx 按需反代(注意先解决 #3 CORS) |
|
||||
|
||||
## 9. 故障速查
|
||||
|
||||
| 症状 | 排查 |
|
||||
|---|---|
|
||||
| `systemctl status meida-api` 启动即失败 | 多半是 JWT_SECRET 太短/占位符(config.py 启动校验)——`journalctl -u meida-api -n 50` 看原因 |
|
||||
| readyz 非 2xx | PG/Redis 没起:`docker compose -f deploy/docker-compose.dev.yml ps` |
|
||||
| 报告卡在生成中 | 看 worker:`journalctl -u meida-worker -f`;LLM key 失效是最常见原因 |
|
||||
| PDF 导出失败报 weasyprint 相关错 | §2.3 的系统库没装 |
|
||||
| nginx 502 | api 没起 / 8666 端口不对;`ss -ltnp \| grep 8666` |
|
||||
| 华为登录 40102 | HUAWEI_AUDIENCE 没配(预期行为,配 AGC App ID) |
|
||||
|
||||
## 10. 上线前最终检查单
|
||||
|
||||
- [ ] 变更备案通过(elane.xiangsilian.com 在备案系统可见)
|
||||
- [ ] dev-token 返回 404、docs 返回 404、metrics 不公网可达
|
||||
- [ ] JWT_SECRET / PG 口令均为强随机值,`.env` 权限 `chmod 600`
|
||||
- [ ] 华为登录真机回归通过(client_id 已填入 module.json5、AGC 指纹与签名一致)
|
||||
- [ ] 邮箱验证码登录真机回归通过(生产 EMAIL_PROVIDER=smtp 实测收到邮件)
|
||||
- [ ] 账号注销功能可用(华为审核硬性要求)
|
||||
- [ ] 隐私政策/用户协议可公开访问,App 内有入口
|
||||
- [ ] 配额规则从内测口径改回产品口径(若执行过 §5.2)
|
||||
- [ ] pg_dump 定时备份已配并演练过一次恢复
|
||||
- [ ] App `DEFAULT_BASE_URL` 切换为 `https://elane.xiangsilian.com`,Release 签名出包
|
||||
Reference in New Issue
Block a user