Files
backend/docs/12-部署实际操作记录.md
T
34047007@qq.com a6cd99a4ca
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
feat: initial commit - oncology literature search platform
OncoLit: a multi-tenant oncology literature search, feed, and
collaboration platform. Built with FastAPI + Vue 3 + PostgreSQL.
Includes PubMed pipeline, drug approvals, AI summaries, and
systematic review tools.
2026-07-27 07:59:18 +08:00

314 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SciLit 生产部署实际操作记录
**部署日期:** 2026-07-10 ~ 2026-07-11
**服务器:** 123.207.9.209(腾讯云 · TencentOS 4
**部署方式:** Docker ComposeNginx 前端反代 + Uvicorn 后端 API
---
## 1. 服务器环境
### 1.1 基础信息
- **OS** TencentOS 4RHEL 系,基于 RHEL 9,非 Ubuntu
- **磁盘:** 20GB/dev/vda1),已用 ~9GB(部署含镜像后 ~12GB)
- **CPU/内存:** 未确认,当前 Elasticsearch 分配 1GB,可跑完整 Docker 栈
### 1.2 Docker 安装(非标准流程)
TencentOS 4 不被 Docker 官方安装脚本支持(`get.docker.com` 会报不支持的发行版)。
```bash
# 不能用标准方法:
# curl -fsSL https://get.docker.com | bash # ❌ 失败
# 改用 Tencent 源 + --releasever=9(兼容 CentOS 9
dnf install -y dnf-plugins-core
dnf config-manager --add-repo https://mirrors.cloud.tencent.com/docker-ce/linux/rhel/docker-ce.repo
dnf --releasever=9 install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker
```
### 1.3 Docker 镜像加速配置
国内拉取 `docker.io` 镜像极慢或超时,必须配置镜像加速器:
```json
# /etc/docker/daemon.json
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
```
配置后重启 Docker
```bash
systemctl daemon-reexec && systemctl restart docker
```
### 1.4 验证安装
```bash
docker info # Registry Mirrors 应显示加速地址
docker run hello-world # 拉取测试
docker compose version # 应显示 v2.x
```
---
## 2. SSH 连接
### 2.1 服务器 SSH 配置
服务器 SSH 默认**禁用密码登录 + 禁用 root 登录**(`PasswordAuthentication no`, `PermitRootLogin no`)。
需要在腾讯云控制台 VNC 或 web shell 中修改:
```bash
# /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
systemctl restart sshd
```
### 2.2 本地 SSH 工具
- **Git Bash 的 `ssh`**:密码登录交互模式在自动化场景不可靠
- **`sshpass`**Windows Git Bash 不支持(exit code 127
- **`pexpect`**Windows 下 import 失败
- **`plink`PuTTY**:不支持 OpenSSH 新版私钥格式(`BEGIN OPENSSH PRIVATE KEY`
- **解决方案**:在服务器添加本地生成的公钥到 `~/.ssh/authorized_keys`,用密钥登录
```bash
# 本地生成密钥
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "deploy"
# 将公钥内容添加到服务器 ~/.ssh/authorized_keys
echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... claude-code-deploy' >> ~/.ssh/authorized_keys
# 登录
ssh -i ~/.ssh/id_ed25519 root@123.207.9.209
```
---
## 3. 代码传输与部署目录
### 3.1 传输方式
用 SCP(无 rsync):
```bash
scp -i ~/.ssh/id_ed25519 -r d:/ClaudeCode/* root@123.207.9.209:/root/scilit/
```
注意:
- SCP 不支持 `--exclude`(要用 rsync),大目录(node_modules、.git)需要手动排除
- 传输前清理无关文件可大幅减少时间
- SCP 不会覆盖已存在的同名文件(除非加 `-r` 强制)
### 3.2 部署目录
```
/root/scilit/
├── docker-compose.prod.yml # 生产 compose 配置
├── .env # 环境变量(含敏感信息)
├── backend/ # 后端代码 + Dockerfile
└── frontend/ # 前端代码 + Dockerfile.prod
```
---
## 4. Dockerfile 构建(关键问题和修复)
### 4.1 `elasticsearch-py` 包名错误
**问题:** `pyproject.toml` 中写的是 `elasticsearch-py`,这是旧版/别名。PyPI 上的正确包名是 `elasticsearch`
```bash
# 报错信息
ERROR: Could not find a version that satisfies the requirement elasticsearch-py>=8.14.0
```
**修复:** `pyproject.toml`
```diff
- "elasticsearch-py[async]>=8.14.0",
+ "elasticsearch[async]>=8.14.0",
```
同时添加 `email-validator` 依赖(Pydantic 验证 email 字段需要):
```diff
+ "email-validator>=2.0",
```
### 4.2 PyPI 镜像导致包缺失
**问题:** 腾讯 PyPI 镜像(`mirrors.cloud.tencent.com/pypi/simple/`)缺少 `elasticsearch>=8.14.0`
**解决:** 分两步预装
```dockerfile
COPY pyproject.toml .
# 第一步:腾讯镜像有 elasticsearch 就用,没有会报错
RUN pip install --no-cache-dir -i https://mirrors.cloud.tencent.com/pypi/simple/ "elasticsearch[async]>=8.14.0" "email-validator>=2.0"
# 第二步:腾讯镜像有其余包(fastapi, sqlalchemy, etc.
RUN pip install --no-cache-dir -i https://mirrors.cloud.tencent.com/pypi/simple/ .
```
实测结果:
- 4/4/2026 时腾讯镜像**有** `elasticsearch>=8.14.0`(已验证通过)
- 如果未来腾讯镜像再次缺失,改为两步:官方 PyPI 先装 elasticsearch,腾讯镜像装其余包
### 4.3 Debian apt 源
Docker 基础镜像 `python:3.12-slim` 基于 Debian。腾讯 apt 源地址:
```
http://mirrors.tencent.com/debian → trixie main/updates
http://mirrors.tencent.com/debian-security → trixie-security main
```
Dockerfile 中:
```dockerfile
RUN echo "deb http://mirrors.tencent.com/debian trixie main" > /etc/apt/sources.list \
&& echo "deb http://mirrors.tencent.com/debian trixie-updates main" >> /etc/apt/sources.list \
&& echo "deb http://mirrors.tencent.com/debian-security trixie-security main" >> /etc/apt/sources.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential libpq-dev \
&& rm -rf /var/lib/apt/lists/*
```
注意 `deb.debian.org` 在中国大陆极慢(~100 KB/s),覆盖 sources.list 后**需要确认** `apt-get update` 的输出只从 `mirrors.tencent.com` 获取。如果尚有 `deb.debian.org` 的记录,是 docker build cache 导致,加 `--no-cache` 或使用 `COPY --chmod` 在早期层清掉默认配置。
### 4.4 `alembic/env.py` 找不到 app 模块
**问题:** `alembic env.py``from app.config import settings` 在工作目录(`/app/alembic/`)下找不到同级 `app/` 目录。
**修复:** 在 env.py 顶部添加 sys.path
```python
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
```
同时 `docker-compose.prod.yml``migrate` 服务需要添加 `JWT_SECRET``SPECIALTY` 等环境变量(因为 `app.config.Settings()` 验证配置时会检查这些值):
```yaml
migrate:
environment:
DATABASE_URL: ...
JWT_SECRET: ${JWT_SECRET}
SPECIALTY: ${SPECIALTY:-oncology}
DEBUG: "false"
```
### 4.5 Docker 构建速度优化
| 优化前 | 优化后 |
|-------|--------|
| 单层 `pip install .`,全部从官方 PyPI 下载,~100 KB/s | 分两步:腾讯镜像加速下载(~50 MB/s),全量约 15s |
| 每次修改代码需全部重建 | `COPY pyproject.toml .` + 依赖安装缓存 → 仅改代码时跳过 pip install |
关键思路:**将依赖安装与应用代码分离**,只在 `pyproject.toml` 变更时重跑 pip
```dockerfile
# 先拷贝仅 pyproject.toml → 安装依赖(缓存层)
COPY pyproject.toml .
RUN pip install --no-cache-dir ...
# 最后拷贝全部代码(变更频繁,但不会触发依赖重装)
COPY . .
```
---
## 5. Docker Compose 启动流程
### 5.1 命令
```bash
cd /root/scilit
docker compose -f docker-compose.prod.yml --env-file .env up -d
```
### 5.2 容器启动顺序(depends_on 控制)
```
postgres ─┬→ migrate ─→ backend ─→ frontend
redis ────┤ │
es ───────┤ └→ worker
minio ────┘
```
- `postgres` 必须 health check 通过 → `migrate` 才启动(运行 alembic upgrade
- `migrate` exit 0 → `backend` + `worker` 才启动
- `backend` 健康检查通过 → `frontend` 才启动(虽然 frontend 只是 Nginx 静态文件,无实质依赖)
### 5.3 迁移日志确认
启动后检查:
```bash
docker logs scilit-migrate-1 # 确认所有 migration 版本 applied
docker ps --format 'table {{.Names}}\t{{.Status}}' # 确认全部 healthy
```
### 5.4 健康检查
```bash
# 后端 API
curl http://localhost:8000/health
# → {"status":"ok","specialty":"oncology","db":"ok","version":"0.1.0"}
# 前端
curl http://localhost/
# → <!DOCTYPE html><html lang="zh-CN">...
# 外网
curl http://123.207.9.209/health
```
---
## 6. 本次部署的变更
### 6.1 代码变更
| 文件 | 变更内容 |
|------|---------|
| `docker-compose.prod.yml` | migrate 服务添加 JWT_SECRET/SPECIALTY/DEBUG 环境变量;移除已废弃的`version`属性;添加 COS env vars;新增 `statusline-setup` services 等 |
| `backend/Dockerfile` | 腾讯镜像源覆盖默认 deb.debian.org;两步 pip 策略预装 elasticsearch |
| `backend/pyproject.toml` | `elasticsearch-py``elasticsearch`;新增 `email-validator` |
| `backend/alembic/env.py` | 添加 `sys.path.insert(0, ...)` 解决模块导入 |
| `backend/app/models/literature.py` | 新增 `full_text_path` 字段 |
| 新增 `backend/app/services/cos_client.py` | 腾讯云 COS 客户端 |
| 新增 `alembic/versions/d959d4b8a9f1_*.py` | 添加 full_text_path 列迁移 |
### 6.2 基础设施变更
| 项目 | 之前 | 现在 |
|------|------|------|
| 对象存储 | MinIO(自建) | 腾讯云 COS(生产,可降级到 MinIO) |
| Dockerfile apt 源 | deb.debian.org(极慢) | mirrors.tencent.com~50 MB/s |
| Dockerfile pip 源 | 无/官方 PyPI | mirrors.cloud.tencent.com(腾讯镜像加速) |
| SSH 认证 | 密码 | SSH Key + 密码均可 |
---
## 7. 回滚要点
- Docker image tag 未设置版本号,`scilit-backend:latest` 覆盖上次构建。如需回滚需重建旧代码
- 数据库迁移是递增的,回滚代码后需确认兼容性。如须回退迁移:`alembic downgrade -1`
- COS 迁移不需回滚:`full_text_sections` 字段仍保留,COS 不可用时自动降级到 PG
- `.env` 文件包含敏感信息,如需回滚注意不要覆盖
---
## 8. 注意事项
1. **不要用 `docker compose down -v`**`-v` 会删除所有数据卷(包括 PG 数据库、ES 索引、Redis 数据、MinIO 文件)
2. **env 文件必须安全存放**`.env` 包含 SMTP 密码、JWT 密钥、DeepSeek API Key、PubMed API Key
3. **Elasticsearch 吃内存**:生产已配置 `-Xms1g -Xmx1g`。如果内存紧张可以暂不启动 ES(代码中 `ES_URL` 为空时自动降级到 tsvector)
4. **Docker 日志**:每个容器配置 `logging.driver: json-file` + `max-size: 10m`, `max-file: 3`,防止日志占满磁盘
5. **Nginx 端口 80** 映射到前端容器,通过 iptables/防火墙控制。SSL/443 暂未配置,需上游反代(Caddy/Nginx/CLB