Files
worldquant-alpha-system/docs/deployment-gitea.md
T
yuxuanhui 99bc36439e
Deploy production / deploy (push) Failing after 8s
Refactor Gitea production deployment process to utilize SSH for remote operations
- Updated deployment specification to reflect the new architecture involving servers A, B, and C.
- Revised README to describe the new deployment method using Gitea Runner and SSH.
- Modified `compose.production.yaml` to remove build context and use image tags directly.
- Enhanced deployment documentation to clarify configuration steps and environment variable requirements.
- Introduced `deploy-remote.sh` script for handling remote deployment tasks over SSH.
- Added unit tests for deployment scripts to ensure robustness and error handling.
- Updated `deploy-production.sh` to streamline image pulling and deployment processes.
2026-09-24 23:47:07 +08:00

14 KiB
Raw Blame History

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 规则。以上示例产生两个包:

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 一致。工作流严格校验主机身份,不会在每次部署时自动信任扫描结果。

生成新安装的加密密钥:

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,按实际账号、密码、端口和库名替换):

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:

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 手动删除:

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 scripts/deploy-production.sh

新流程产生的发布目录只含部署文件,手动执行仍需注入完整环境。若旧 schema 不兼容,先停止应用、人工确认后从经过验证的备份恢复数据库,再使用原加密密钥和对应旧版本启动;恢复会丢失备份后的写入。不要仅为回退重跑旧的“同机构建”工作流。

以下命令在 B 执行,不依赖凭据环境变量:

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、Docker 登录、Compose pull、Compose run、Compose up。

5. 1Panel 夜间全量目录同步

在 1Panel 的计划任务中建立 Shell 脚本任务,执行周期由 1Panel 设置,例如每天深夜执行。每次命令明确指定一个范围:

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 官方文档。启动前在网页完成 WorldQuant 连接,人工验证仍在网页处理。

CLI 只创建持久化任务并等待,不启动另一个同步执行器;后端必须正在运行。相同范围的活动全量任务复用同一任务 ID。任务先完整更新数据集清单,再逐个同步所有字段,每个数据集独立完整发布。普通单集失败保留上一版并继续其他数据集;鉴权和网络连接问题暂停全量任务。任务面板可查看阶段、当前数据集、字段分页位置、成功/失败统计,并取消或重试。

1Panel 执行日志保存 CLI 输出的任务 ID、状态变化、检查点和最终汇总。也可使用 docker logs --tail=100 wq-alpha-production-backend-1 排查执行器;命令不输出平台认证信息。不要公开包含研究数据的日志。

失败或部分失败时按日志中的任务 ID 从检查点继续,已成功发布的数据集不会重抓:

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 实际触发效果不属于模拟验收。