15 KiB
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/QEMU action、基础镜像仓库、依赖源、A 的镜像仓库和 B 的 SSH 端口。BUILD_PLATFORM 必须匹配 B:当前 Runner 为 aarch64、B 为 x86_64,仍应配置 linux/amd64(或使用默认值)。
工作流先通过 docker/setup-qemu-action 注册 amd64/arm64 模拟支持,再创建独立的 docker-container Buildx 构建器。QEMU 初始化需要构建机的 Docker daemon 允许运行特权容器并注册 binfmt;此操作作用于构建机。日志会显示构建机架构、目标平台和构建器支持的平台。Gitea 中禁用该 action 的 GitHub 缓存功能。前后端均显式使用同一个构建器,成功或失败后清理构建器及临时认证目录,不切换 Runner 原有默认构建器。跨架构模拟通常比同架构构建慢。
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 或手动运行工作流会依次执行:
- checkout 当前提交,初始化 QEMU 和独立 Buildx 构建器,按 B 的目标平台构建 backend/web,分别推送到 A 的 Gitea Packages。两个镜像均推送成功才执行 SSH 部署。
scripts/deploy-remote.sh严格验证 B 主机公钥,通过同一 SSH 连接传入应用环境变量、生产 Compose 和部署脚本。- B 为每次尝试创建
DEPLOY_PATH/releases/<提交 SHA>.<随机后缀>/,只保存compose.production.yaml和scripts/deploy-production.sh,无需更新项目源码。 - B 登录 A 的仓库并拉取两个镜像。随后获得 Docker 部署锁,运行新 backend 镜像校验配置和数据库连接。
- 记录旧镜像 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 跨平台构建、QEMU action、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 实际触发效果不属于模拟验收。