feat: 更新生产部署配置,优化锁机制和环境变量管理
Deploy production / deploy (push) Successful in 1m8s

This commit is contained in:
yuxuanhui
2026-09-09 10:27:07 +08:00
parent b8429efa3d
commit f31dda78b4
3 changed files with 32 additions and 19 deletions
+2 -2
View File
@@ -6,8 +6,8 @@ on:
jobs: jobs:
deploy: deploy:
# Register a dedicated runner on the target host with label wq-production:host. # Reuse the runner that deploys zhixing-system to the same Docker host.
runs-on: wq-production runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: https://github.com/actions/checkout@v4 uses: https://github.com/actions/checkout@v4
+7 -5
View File
@@ -1,6 +1,6 @@
# Gitea 生产部署 # Gitea 生产部署
本方案在目标 Linux 服务器上由 Gitea host Runner 构建并启动 Docker Compose,复用已有 PostgreSQL。入口绑定 `127.0.0.1:8112`,由宿主机反向代理提供公网 HTTPS。现有 `compose.yaml`、`compose.public.yaml` 保持独立,不与生产文件叠加。 本方案在目标 Linux 服务器上由已有 Gitea Runner 构建并启动 Docker Compose,复用已有 PostgreSQL。入口绑定 `127.0.0.1:8112`,由宿主机反向代理提供公网 HTTPS。现有 `compose.yaml`、`compose.public.yaml` 保持独立,不与生产文件叠加。
## 1. 填写配置和数据库账号密码 ## 1. 填写配置和数据库账号密码
@@ -41,11 +41,13 @@ WorldQuant 凭据由环境变量管理,启动时加密写入数据库;页面
## 2. 配置 Gitea Runner ## 2. 配置 Gitea Runner
在目标 Docker 宿主机直接运行专用 Runner,注册标签 **`wq-production:host`**,工作流的 `runs-on` 对应 `wq-production`。建议专用 Runner 配置 `runner.capacity: 1`。不要使用指向其他机器的 Docker context;这里的 host 模式也不能被当成“容器化 Runner 自动进入宿主机”。 沿用已成功部署 `zhixing-system` 的运行器标签 **`ubuntu-latest`**,无需注册新的 `wq-production` 运行器。该运行器的执行环境须能通过 Docker CLI/Compose 操作同一台 1Panel 服务器的 Docker daemon;可以复用现有 Docker 连接方式,不强制 host 模式。
Runner 用户需要 Docker 权限,以及写入预先创建的 `/opt/wq-alpha` 目录的权限。该目录仅保存锁文件和版本记录,不保存凭据。宿主机需具备 Git、Bash、Node.js 20(checkout v4)、`flock`(通常由 util-linux 提供)和支持 `up --wait --wait-timeout` 的 Docker Compose v2 或 v5。镜像使用锁文件构建,服务器需能访问 GitHub checkout action、基础镜像仓库和依赖源。本地验证环境为 Docker Engine 29.6.2、Compose 5.3.1;你的 Gitea/Runner 版本需在首次运行核验。 执行环境需要 Git、Bash、Node.js 20(checkout v4)以及支持 `up --wait --wait-timeout` 的 Docker Compose。本流程不需要 `flock` 或预建 `/opt/wq-alpha`。服务器需能访问 checkout action、基础镜像仓库和依赖源。
在 Gitea 仓库启用 Actions,提交并推送这些配置后,`main` 的 push 或手动运行会部署。Runner 应只接收可信仓库的任务,因为它具备生产主机 Docker 权限。凭据通过上述 Secrets 注入,脚本使用 `--env-file /dev/null` 防止误读检出目录的开发 `.env`。部署脚本用 `/opt/wq-alpha/deploy.lock` 实现跨 checkout 的互斥,冲突部署直接失败,可稍后重新运行。 在仓库启用 Actions,`main` push 或手动运行会部署。Secrets 通过部署步骤的环境变量注入,`--env-file /dev/null` 防止误读开发 `.env`。构建可并行;构建完成后,以 Docker 唯一容器名 `wq-alpha-production-deploy-lock` 互斥保护预检、迁移和服务切换。锁容器不启动、不携带凭据,正常结束或失败时删除;竞争失败的任务不会删除其他任务的锁。
如果 Runner 被强制终止或 Docker 断连,可能留下锁容器。先确认没有本项目部署正在执行,再手动运行 `docker rm wq-alpha-production-deploy-lock` 后重试。不要在部署进行时删除锁。
## 3. 配置反向代理并首次运行 ## 3. 配置反向代理并首次运行
@@ -65,7 +67,7 @@ bash scripts/deploy-production.sh
升级包含停机窗口;提前结束或暂停长时间任务。每次发布前通过现有数据库管理工具备份专用库,并在独立安全位置备份加密密钥及必要配置,先在独立库验证恢复。此工作流不会自动备份或自动恢复数据库。 升级包含停机窗口;提前结束或暂停长时间任务。每次发布前通过现有数据库管理工具备份专用库,并在独立安全位置备份加密密钥及必要配置,先在独立库验证恢复。此工作流不会自动备份或自动恢复数据库。
构建及预检失败时旧服务继续运行。停止服务后的迁移或健康检查失败需要人工处理;不要对可能已变更的 schema 直接自动降级。脚本将切换前的镜像 ID/标签记录到 `/opt/wq-alpha/previous-images.txt`,最后成功的提交记录到 `current-release.txt`,保留旧镜像且不执行 prune。失败重试前另存这些记录,避免后续尝试覆盖回退参考。 构建及预检失败时旧服务继续运行。停止服务后的迁移或健康检查失败需要人工处理;不要对可能已变更的 schema 直接自动降级。脚本在 Actions 日志输出切换前的镜像 ID/标签,成功后输出当前提交标识;保留旧镜像且不执行 prune。请保留部署日志作为回退参考,不再依赖宿主机版本记录文件。
如旧代码与当前 schema 兼容,可检出旧提交并指定其镜像标签启动;否则先停止应用,使用经过验证的备份恢复数据库,再用原加密密钥和对应旧版本启动。数据库恢复会丢失备份后的写入,必须人工确认后执行。本配置没有自动数据库降级,也不承诺无停机升级。 如旧代码与当前 schema 兼容,可检出旧提交并指定其镜像标签启动;否则先停止应用,使用经过验证的备份恢复数据库,再用原加密密钥和对应旧版本启动。数据库恢复会丢失备份后的写入,必须人工确认后执行。本配置没有自动数据库降级,也不承诺无停机升级。
+23 -12
View File
@@ -12,22 +12,33 @@ for key in WQ_EMAIL WQ_PASSWORD DATABASE_URL ADMIN_PASSWORD ENCRYPTION_KEY DATAB
exit 1 exit 1
fi fi
done done
state_dir="${DEPLOY_STATE_DIR:-/opt/wq-alpha}"
if [[ "$state_dir" != /* || ! -d "$state_dir" || ! -w "$state_dir" ]]; then
echo 'DEPLOY_STATE_DIR must be an existing writable absolute directory.' >&2
exit 1
fi
command -v flock >/dev/null
# Stable across checkouts; stores only a lock and release metadata, never credentials.
exec 9>"$state_dir/deploy.lock"
flock -n 9 || { echo 'Another production deployment is running.' >&2; exit 1; }
export DEPLOY_TAG="${DEPLOY_TAG:-$(git rev-parse HEAD)}" export DEPLOY_TAG="${DEPLOY_TAG:-$(git rev-parse HEAD)}"
compose=(docker compose --env-file /dev/null -p wq-alpha-production -f compose.production.yaml) compose=(docker compose --env-file /dev/null -p wq-alpha-production -f compose.production.yaml)
trap 'rc=$?; "${compose[@]}" --profile jobs ps -a || true; exit "$rc"' EXIT lock_id=""
cleanup() {
local rc=$?
"${compose[@]}" --profile jobs ps -a || true
# Remove only the lock acquired by this process, never another deployment's lock.
if [[ -n "$lock_id" ]]; then
docker rm "$lock_id" >/dev/null || true
fi
exit "$rc"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
"${compose[@]}" config --quiet "${compose[@]}" config --quiet
"${compose[@]}" --profile jobs config --quiet "${compose[@]}" --profile jobs config --quiet
"${compose[@]}" build backend web "${compose[@]}" build backend web
# Docker enforces unique container names across runner jobs and checkout paths.
# The lock container is never started and receives no deployment credentials.
if ! lock_id=$(docker create --name wq-alpha-production-deploy-lock \
--label "wq.deploy.commit=$DEPLOY_TAG" \
--network none --entrypoint /bin/true "wq-alpha-production-backend:$DEPLOY_TAG"); then
echo 'Cannot acquire deployment lock; check Docker and other active deployments.' >&2
exit 1
fi
# Validate secrets and DB connectivity before interrupting the running version. # Validate secrets and DB connectivity before interrupting the running version.
"${compose[@]}" run --rm --no-deps backend python -c ' "${compose[@]}" run --rm --no-deps backend python -c '
import asyncio import asyncio
@@ -51,10 +62,10 @@ except Exception:
# Record immutable image references before switching; do not prune old images. # Record immutable image references before switching; do not prune old images.
previous_images=$("${compose[@]}" images --quiet) previous_images=$("${compose[@]}" images --quiet)
if [[ -n "$previous_images" ]]; then if [[ -n "$previous_images" ]]; then
docker image inspect --format '{{.Id}} {{json .RepoTags}}' $previous_images > "$state_dir/previous-images.txt" echo "Previous deployment images (retain for rollback):"
docker image inspect --format '{{.Id}} {{json .RepoTags}}' $previous_images
fi fi
"${compose[@]}" stop web backend "${compose[@]}" stop web backend
"${compose[@]}" --profile jobs run --rm --no-deps migrate "${compose[@]}" --profile jobs run --rm --no-deps migrate
"${compose[@]}" up -d --no-build --remove-orphans --wait --wait-timeout 180 backend web "${compose[@]}" up -d --no-build --remove-orphans --wait --wait-timeout 180 backend web
printf '%s\n' "$DEPLOY_TAG" > "$state_dir/current-release.txt"
echo "Production is healthy; release $DEPLOY_TAG" echo "Production is healthy; release $DEPLOY_TAG"