a3cb6dbacf
This reverts commit 5a7f39726b.
176 lines
14 KiB
Markdown
176 lines
14 KiB
Markdown
# Gitea 生产部署
|
||
|
||
服务器分工:**A 提供 Gitea 和 Packages 镜像仓库,B 运行应用,C 运行 PostgreSQL**,B 与 C 通过内网连接。Gitea Runner 构建前后端镜像并推送到 A,然后通过 SSH 在 B 拉取镜像、检查配置与数据库、执行迁移并启动服务。Runner 可以在 A 或其他构建机上,无需访问 B 的 Docker socket。
|
||
|
||
生产使用独立的 `compose.production.yaml`,只引用镜像,不在 B 构建。B 上的 backend 和 migrate 通过 `DATABASE_URL` 连接 C,使用 Compose 默认网络,不依赖外部数据库 Docker 网络。Web 绑定 `127.0.0.1:8112`,由 B 的宿主机反向代理提供 HTTPS。`compose.yaml`、`compose.public.yaml` 的本地构建方式不变,不与生产文件叠加。
|
||
|
||
## 1. 配置 Gitea Secrets 和 Variables
|
||
|
||
进入 **仓库 → 设置 → Actions → Secrets / Variables**。现有应用 Secrets 继续使用,新增镜像仓库和 SSH 配置。
|
||
|
||
### Secrets
|
||
|
||
| 名称 | 必填 | 填写内容 |
|
||
| --- | --- | --- |
|
||
| `PROD_HOST` | 是 | B 的 SSH 主机名或 IP,不带协议、用户名和端口 |
|
||
| `PROD_USER` | 是 | B 的 SSH 登录用户,能直接执行 Docker 命令,无需交互式 sudo |
|
||
| `PROD_SSH_KEY` | 是 | 专用 SSH 私钥完整内容,保留换行;对应公钥放入 B 用户的 `~/.ssh/authorized_keys`;使用无需交互输入口令的部署密钥 |
|
||
| `PROD_KNOWN_HOSTS` | 是 | 经核验的 B 主机公钥,采用 OpenSSH `known_hosts` 格式,见下文 |
|
||
| `PROD_PORT` | 否 | B 的 SSH 端口,默认 `22` |
|
||
| `REGISTRY_USERNAME` | 是 | 有权访问镜像 owner 的 Gitea 用户名 |
|
||
| `REGISTRY_PASSWORD` | 是 | 该用户的 Gitea Personal Access Token,至少具备 `write:package` 权限(包含拉取权限),并有目标 owner 的包访问权限 |
|
||
| `DATABASE_URL` | 是 | `postgresql+asyncpg://数据库账号:数据库密码@C的内网IP或域名:数据库端口/数据库名`,须从 B 的容器内可连接 |
|
||
| `ADMIN_PASSWORD` | 是 | 初始管理员密码,至少 12 字符,与数据库密码独立 |
|
||
| `ENCRYPTION_KEY` | 是 | Fernet 密钥,升级必须保持不变 |
|
||
| `WQ_EMAIL` | 是 | WorldQuant 登录邮箱 |
|
||
| `WQ_PASSWORD` | 是 | WorldQuant 登录密码,按原样填写,不加引号 |
|
||
|
||
### Variables
|
||
|
||
| 名称 | 必填 | 填写内容 |
|
||
| --- | --- | --- |
|
||
| `IMAGE_PREFIX` | 是 | `A的仓库地址/owner/镜像名前缀`,全小写,不带 `https://` 或标签;当前仓库可填 `tea.bcc-life.cn/sakibcc/worldquant-alpha-system` |
|
||
| `DEPLOY_PATH` | 是 | B 上的绝对部署目录,例如 `/opt/wq-alpha`;SSH 用户须可写 |
|
||
| `BUILD_PLATFORM` | 否 | B 的 CPU 平台,默认 `linux/amd64`;ARM64 服务器填 `linux/arm64` |
|
||
| `PUBLIC_ORIGIN` | 是 | 实际 HTTPS 来源,例如 `https://alpha.your-domain.com`,不带路径或末尾 `/` |
|
||
| `ADMIN_USERNAME` | 否 | 初始管理员账号,默认 `admin` |
|
||
| `MCP_ENABLED` | 否 | `true` 启用 MCP,默认关闭 |
|
||
|
||
镜像命名遵循 Gitea 的 `registry/owner/image:tag` 规则。以上示例产生两个包:
|
||
|
||
```text
|
||
tea.bcc-life.cn/sakibcc/worldquant-alpha-system-backend:<完整提交 SHA>
|
||
tea.bcc-life.cn/sakibcc/worldquant-alpha-system-web:<完整提交 SHA>
|
||
```
|
||
|
||
`REGISTRY_USERNAME` 是登录用户,`IMAGE_PREFIX` 中的 owner 是包所有者,两者在组织仓库中可能不同。Packages 属于该用户或组织,可在 Gitea 中关联到当前仓库。默认使用同一个 Token 推送和远程拉取,B 的部署进程因此也会接收该 Token。
|
||
|
||
主机公钥应从可信的 B 控制台取得,并与 SSH 扫描结果核对。例如在可信管理终端运行 `ssh-keyscan -p 22 B_HOST`,通过 B 的控制台核对其指纹后,将核验过的完整行保存为 `PROD_KNOWN_HOSTS`。非默认端口的主机字段应为 `[B_HOST]:PORT`;主机名必须与 `PROD_HOST` 一致。工作流严格校验主机身份,不会在每次部署时自动信任扫描结果。
|
||
|
||
生成新安装的加密密钥:
|
||
|
||
```bash
|
||
python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
|
||
```
|
||
|
||
`DATABASE_URL` 中账号和密码的特殊字符须 URL 编码,例如 `@` → `%40`、`#` → `%23`、`/` → `%2F`。在 Gitea 输入框填写值本身,不加包裹引号或 `KEY=` 前缀;其他密码中的 `$`、引号和换行原样填写,SSH 传递会进行 Bash 转义。不要开启 `set -x` 或打印凭据。
|
||
|
||
端口与 AI 限制继续使用 Compose 默认值:`WEB_PORT=8112`、`AI_REQUEST_LIMIT=12`、`AI_TOOL_LIMIT=12`、`AI_OUTPUT_TOKENS=4096`、`AI_TIMEOUT=180`。需要调整时修改 Compose 默认值;工作流不读取这些同名 Gitea Variables。
|
||
|
||
`DATABASE_URL` 示例(假设 C 的内网 IP 为 `10.0.0.30`,按实际账号、密码、端口和库名替换):
|
||
|
||
```text
|
||
postgresql+asyncpg://wq_user:YOUR_PASSWORD@10.0.0.30:5432/wq_alpha
|
||
```
|
||
|
||
本版本不再读取或校验 `DATABASE_NETWORK`,Gitea 中的旧变量可以删除。不要用 C 上的容器名或 Docker 网络名作为跨主机连接地址。
|
||
|
||
已有安装需沿用数据库和 `ENCRYPTION_KEY`;修改管理员环境变量不会重置已有密码。WorldQuant 凭据启动时加密写入数据库,`WQ_EMAIL` 必须与已有绑定账户一致;修改 `WQ_PASSWORD` 后重新部署即可更新。生产不读取 `account.json` 或 `.env`。
|
||
|
||
## 2. 准备 A、Runner、B 和 C
|
||
|
||
### A:Gitea Packages
|
||
|
||
启用仓库 Actions 和 Gitea Packages。Runner 与 B 都须能解析并访问 `IMAGE_PREFIX` 中的仓库地址;HTTPS 证书须被两端 Docker 信任。Gitea 前的反向代理须正确转发 `/v2/` 并允许镜像层上传。本流程不自动修改 Docker 的 insecure registry 或 TLS 设置。
|
||
|
||
### Runner:构建与 SSH
|
||
|
||
继续使用 `ubuntu-latest` 标签,该标签对应的**实际执行环境**需要 Git、Bash、Node.js 20(checkout v4)、Docker CLI/Buildx、OpenSSH 客户端、tar、base64,并能连接构建机的 Docker daemon。容器模式 Runner 也需要具备这些工具和 Docker 连接能力。
|
||
|
||
Runner 需要访问 checkout action、基础镜像仓库、依赖源、A 的镜像仓库和 B 的 SSH 端口。`BUILD_PLATFORM` 必须匹配 B;若与构建机架构不同,需事先在构建机配置对应的跨架构构建能力。
|
||
|
||
### B:运行环境
|
||
|
||
安装 Bash、tar、base64、Docker Engine 和支持 `pull --policy`、`run --pull`、`up --wait --wait-timeout` 的 Docker Compose 插件。SSH 用户需要直接操作 B 的本机 Docker daemon;不需要 Git、Node.js、Python 或源码 checkout。脚本使用临时 `DOCKER_CONFIG`,不要依赖用户配置目录中的命名 Docker context;使用本机默认 socket。
|
||
|
||
在 B 创建部署目录并交给部署用户,例如将示例用户 `deploy` 换成实际的 `PROD_USER`:
|
||
|
||
```bash
|
||
sudo install -d -m 700 -o deploy -g deploy /opt/wq-alpha
|
||
```
|
||
|
||
将部署公钥加入该用户的 `authorized_keys`,核对 B 的反向代理及应用容器到 C 数据库端口的连通性。B、C 主机内网互通不能替代容器内的连接验证。
|
||
|
||
### C:数据库
|
||
|
||
在 C 创建专用数据库和账号,赋予该库应用 schema 的建表与迁移权限。PostgreSQL 须监听内网可访问的接口,防火墙和 `pg_hba.conf` 须允许 B 的连接来源。默认 Docker bridge 出站通常使用 B 的主机地址,若配置了额外路由或 NAT,以 C 实际收到的来源地址为准。
|
||
|
||
若 PostgreSQL 运行在 C 的 Docker 容器中,须发布到 C 的内网地址,例如 `10.0.0.30:5432:5432`;`DATABASE_URL` 使用 C 的内网 IP 和发布端口。强制 TLS 的实例还需按数据库证书及连接要求配置客户端参数。部署流程只在 B 执行迁移,通过数据库连接访问 C,无需 C 的 SSH 凭据。
|
||
|
||
## 3. 部署流程与首次验收
|
||
|
||
`main` push 或手动运行工作流会依次执行:
|
||
|
||
1. checkout 当前提交,在 Runner 构建 backend/web,分别推送到 A 的 Gitea Packages。两个镜像均推送成功才执行 SSH 部署。
|
||
2. `scripts/deploy-remote.sh` 严格验证 B 主机公钥,通过同一 SSH 连接传入应用环境变量、生产 Compose 和部署脚本。
|
||
3. B 为每次尝试创建 `DEPLOY_PATH/releases/<提交 SHA>.<随机后缀>/`,只保存 `compose.production.yaml` 和 `scripts/deploy-production.sh`,无需更新项目源码。
|
||
4. B 登录 A 的仓库并拉取两个镜像。随后获得 Docker 部署锁,运行新 backend 镜像校验配置和数据库连接。
|
||
5. 记录旧镜像 ID/标签,停止 web/backend,执行一次性迁移,再使用已拉取的镜像启动并等待健康检查。后端保持一个实例、一个 worker。
|
||
|
||
Compose 项目名固定为 `wq-alpha-production`,所以独立发布目录不会创建第二套应用或卷。迁移使用同一 backend 镜像;停止服务后不再访问仓库或构建。迁移或健康检查失败会让 SSH 和 Actions 返回失败。
|
||
|
||
应用凭据通过 SSH 标准输入传递,不写入发布目录或 `.env`;Compose 显式使用 `--env-file /dev/null`。SSH 私钥与 Docker 登录信息使用权限受限的临时目录,正常结束或失败时清理,不覆盖用户原有 Docker 登录配置。进程被强制杀死时可能残留临时文件;拥有 Runner、B 或 Docker 管理权限的人仍可读取进程和容器运行时配置。
|
||
|
||
Docker 唯一容器名 `wq-alpha-production-deploy-lock` 保护预检、迁移及服务切换;锁容器不运行、不携带凭据。并发部署竞争失败会退出,且只清理自己取得的锁。Runner 被强制终止或 Docker 断连时可能留下锁;确认没有部署正在执行后,可在 B 手动删除:
|
||
|
||
```bash
|
||
docker rm wq-alpha-production-deploy-lock
|
||
```
|
||
|
||
B 的宿主机 HTTPS 反向代理应转发到 `http://127.0.0.1:8112`。独立 bridge 容器中的 `127.0.0.1` 不指向宿主机;使用 1Panel/OpenResty 时需确认其网络模式。AI 流式响应需要关闭代理缓冲,并设置足够的读取超时。
|
||
|
||
首次部署建议手动运行工作流,确认 Packages 中有两个提交标签、B 上容器健康,再通过公网检查 `/api/v1/health`、登录、写请求和 Cookie。容器健康检查不替代 DNS、HTTPS 和真实 Runner 验收。
|
||
|
||
## 4. 升级、备份和失败处理
|
||
|
||
每次升级包含停机窗口;提前暂停或结束长时间任务。发布前通过数据库管理工具备份专用库,并在独立安全位置备份加密密钥及必要配置。此工作流不会自动备份或自动恢复数据库。
|
||
|
||
构建、推送、建立 SSH 连接、拉取或预检阶段失败时不会停止旧服务。停止服务后的迁移、健康检查失败或 SSH 中断需要在 B 核对实际状态并人工处理;不要对可能已变更的 schema 自动降级。Actions 日志记录切换前的镜像 ID/标签,成功后输出当前提交。两端均不执行 image prune;保留需要回退的 Packages 标签及 B 上的发布目录。
|
||
|
||
提交标签用于定位版本;同一提交重跑会重新构建并覆盖同名标签,严格核对历史产物时使用日志中的镜像 ID/digest。回退须先确认旧代码与当前 schema 兼容,然后从安全渠道注入对应配置、`IMAGE_PREFIX`、旧 `DEPLOY_TAG` 和仓库凭据,在匹配旧版本的发布目录运行:
|
||
|
||
```bash
|
||
bash scripts/deploy-production.sh
|
||
```
|
||
|
||
新流程产生的发布目录只含部署文件,手动执行仍需注入完整环境。若旧 schema 不兼容,先停止应用、人工确认后从经过验证的备份恢复数据库,再使用原加密密钥和对应旧版本启动;恢复会丢失备份后的写入。不要仅为回退重跑旧的“同机构建”工作流。
|
||
|
||
以下命令在 B 执行,不依赖凭据环境变量:
|
||
|
||
```bash
|
||
docker ps -a --filter label=com.docker.compose.project=wq-alpha-production
|
||
docker logs --tail=100 wq-alpha-production-backend-1
|
||
docker logs --tail=100 wq-alpha-production-web-1
|
||
```
|
||
|
||
不要公开含业务信息的日志,普通维护不要使用 `down -v`。发布目录可在确认无需回退后人工清理,不影响 named volumes;数据库备份和恢复在数据库管理端进行。
|
||
|
||
配置依据:[Gitea Container Registry](https://docs.gitea.com/usage/packages/container/)、[Docker 登录](https://docs.docker.com/reference/cli/docker/login/)、[Compose pull](https://docs.docker.com/reference/cli/docker/compose/pull/)、[Compose run](https://docs.docker.com/reference/cli/docker/compose/run/)、[Compose up](https://docs.docker.com/reference/cli/docker/compose/up/)。
|
||
|
||
## 5. 1Panel 夜间全量目录同步
|
||
|
||
在 1Panel 的计划任务中建立 Shell 脚本任务,执行周期由 1Panel 设置,例如每天深夜执行。每次命令明确指定一个范围:
|
||
|
||
```bash
|
||
docker exec wq-alpha-production-backend-1 \
|
||
python -m app.cli catalog-sync \
|
||
--region USA --universe TOP3000 --delay 1
|
||
```
|
||
|
||
`--instrument-type` 默认 `EQUITY`,`--wait-timeout` 默认 `21600` 秒(六小时)。容器已持有部署时注入的环境,定时脚本无需再次填写数据库、密码或加密密钥;不要加 `-it`。容器命令继承环境的行为见 [Docker exec 官方文档](https://docs.docker.com/reference/cli/docker/container/exec/)。启动前在网页完成 WorldQuant 连接,人工验证仍在网页处理。
|
||
|
||
CLI 只创建持久化任务并等待,不启动另一个同步执行器;后端必须正在运行。相同范围的活动全量任务复用同一任务 ID。任务先完整更新数据集清单,再逐个同步所有字段,每个数据集独立完整发布。普通单集失败保留上一版并继续其他数据集;鉴权和网络连接问题暂停全量任务。任务面板可查看阶段、当前数据集、字段分页位置、成功/失败统计,并取消或重试。
|
||
|
||
1Panel 执行日志保存 CLI 输出的任务 ID、状态变化、检查点和最终汇总。也可使用 `docker logs --tail=100 wq-alpha-production-backend-1` 排查执行器;命令不输出平台认证信息。不要公开包含研究数据的日志。
|
||
|
||
失败或部分失败时按日志中的任务 ID 从检查点继续,已成功发布的数据集不会重抓:
|
||
|
||
```bash
|
||
docker exec wq-alpha-production-backend-1 \
|
||
python -m app.cli catalog-sync --resume-job TASK_ID --wait-timeout 21600
|
||
```
|
||
|
||
退出码:`0` 全部成功,`1` 失败/部分失败/取消,`2` 参数错误,`3` 需要连接或人工验证,`4` 等待超时。超时只结束 CLI 等待,后台任务继续;重复执行同范围命令可继续等待活动任务。恢复前先处理连接或验证问题。服务重启后由原执行器恢复持久化检查点;定时调度仅由 1Panel 负责。
|
||
|
||
真实平台协议和 1Panel 实际触发效果不属于模拟验收。
|