Files
Genarrative/docs/technical/JENKINS_RUST_BUILD_DEPLOY_PIPELINES_2026-04-23.md

9.1 KiB
Raw Blame History

Jenkins Rust 构建与部署流水线方案

日期:2026-04-23

1. 目标

本方案为当前仓库补齐 3 条 Jenkins 流水线:

  1. 构建:只负责在仓库根目录执行 npm run deploy:rust:remote -- --skip-upload,生成发布包。
  2. 部署:只负责把指定发布版本部署到 /var/lib/jenkins/deploy/Genarrative/,允许人工按参数启动,并支持按参数决定是否清空 SpacetimeDB 数据。
  3. 构建并部署:先构建,再把构建出的版本号传给 部署 流水线并等待部署完成;同时暴露 WEB_PORT 参数,默认把发布包 Web 端口写成 80,并透传是否清库。

本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 server-node 部署链。

2. 执行约束

  1. 构建产物目录统一使用 build/<版本号>/
  2. 默认使用 Jenkins BUILD_NUMBER 作为版本号,避免依赖时间戳;如有需要也允许显式传 BUILD_VERSION
  3. 构建构建并部署checkout scm 后、实际构建前必须执行 git reset --hard HEADgit clean -fd,避免固定源码目录内的 Git 变更和未跟踪文件影响发布包;不使用 -x,避免删除 node_modules/ 等忽略目录后与 RUN_NPM_CI=false 冲突。
  4. 部署 流水线允许人工启动;没有上游触发 cause 时按人工部署处理,不再直接失败。
  5. 部署 流水线仅在存在上游触发 cause 时校验上游作业名与传入的 EXPECTED_UPSTREAM_JOB 一致;如配置了环境变量 GENARRATIVE_ALLOWED_UPSTREAM_JOB,还必须与该值一致。
  6. 构建并部署 在触发 部署 前先释放自己的构建节点,避免单执行器节点出现死锁。
  7. 部署 不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地 build/<版本号>/ 目录。
  8. 部署 流水线读取触发原因时必须使用 currentBuild.getBuildCauses(...) 这类白名单方法,不能直接访问 currentBuild.rawBuild,否则会被 Jenkins Script Security 拦截。
  9. 由于 Jenkins Pipeline 的 build 步骤触发下游时,原因类型通常是 org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause,实现上需要同时兼容它和经典的 hudson.model.Cause$UpstreamCause,否则会把真实的上游触发误判成人工执行。
  10. 如果线上进程的启停必须经过 sudo,只允许 start.sh / stop.sh 这两个 hook 使用 sudo -n 执行,部署目录清空与文件覆盖仍保持普通权限。

3. 节点与工作区要求

这套方案依赖“本地目录发布”,因此有两个前提:

  1. 构建并部署部署 必须落到同一台 Ubuntu Jenkins Agent或者落到同一块共享文件系统。
  2. 构建并部署 触发 部署 时,必须把 SOURCE_NODE_NAMESOURCE_WORKSPACE_ROOT 一并传下去。

仓库中提供的 Jenkinsfile 已按这个约束实现:

  1. 构建 / 构建并部署 在指定源码目录内 checkout scm 并生成 build/<版本号>/
  2. 构建并部署 结束构建节点占用后,再触发 部署
  3. 部署 优先按 SOURCE_NODE_NAME 调度到同名节点,再读取 SOURCE_WORKSPACE_ROOT/build/<版本号>/

4. 三条流水线定义

4.1 构建

脚本路径:

jenkins/Jenkinsfile.build

核心流程:

  1. checkout scm 后执行 git reset --hard HEADgit clean -fd 清理工作区。
  2. 可选执行 npm ci
  3. 在源码根目录执行:
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
  1. 校验 build/<BUILD_VERSION>/ 存在。
  2. 归档 build/<BUILD_VERSION>/** 作为 Jenkins 产物。

默认版本号:

BUILD_VERSION = Jenkins BUILD_NUMBER

4.2 部署

脚本路径:

jenkins/Jenkinsfile.deploy

核心流程:

  1. 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容 BuildUpstreamCause 与经典 UpstreamCause 并继续校验上游作业名。
  2. 校验 BUILD_VERSIONSOURCE_WORKSPACE_ROOTDEPLOY_DIRECTORY 非空。
  3. 执行:
scripts/jenkins-deploy-release.sh \
  --source-dir <SOURCE_WORKSPACE_ROOT>/build/<BUILD_VERSION> \
  --deploy-dir /var/lib/jenkins/deploy/Genarrative \
  [--clear-database] \
  --hook-with-sudo

脚本语义:

  1. 若部署目录已有旧版本且存在 stop.sh,先执行旧版本 stop.sh
  2. 只删除发布产物白名单中的旧文件,例如 web/api-serverspacetime_module.wasm.env*start.shstop.shweb-server.mjsREADME.md
  3. 将指定版本目录中的同名发布产物移动到部署目录。
  4. 如果 CLEAR_DATABASE=true,部署脚本会以 ./start.sh --clear-database 启动新版本;这样发布阶段的 spacetime publish 会追加 -c always
  5. 执行新版本 start.sh

如果 RUN_DEPLOY_HOOKS_WITH_SUDO=true,第 1 步和第 4 步会改为 sudo -n 调用;这要求 Jenkins 运行用户提前配置免密 sudo否则部署会直接失败不会进入交互式密码提示。

这样可以满足“发布文件直接覆盖”的要求,同时保留部署目录里像 spacetimedb-data/logs/run/ 这类运行态目录,不会因为部署被整体删除。发布白名单内的 .env.env.local 仍会以构建产物中的文件为准。

4.3 构建并部署

脚本路径:

jenkins/Jenkinsfile.build-and-deploy

核心流程:

  1. checkout scm 后执行 git reset --hard HEADgit clean -fd 清理工作区。
  2. 复用与 构建 相同的构建命令生成 build/<BUILD_VERSION>/
  3. 归档 build/<BUILD_VERSION>/**
  4. 记录当前 NODE_NAME、源码根目录、版本号。
  5. 构建时额外透传 --web-port <WEB_PORT>,默认生成监听 80 的发布包。
  6. 触发 部署 流水线,并传递:
    • BUILD_VERSION
    • SOURCE_WORKSPACE_ROOT
    • SOURCE_NODE_NAME
    • DEPLOY_DIRECTORY
    • CLEAR_DATABASE
    • EXPECTED_UPSTREAM_JOB

5. Jenkins 参数建议

三条流水线统一建议暴露以下参数:

  1. AGENT_LABEL:默认执行节点标签。
  2. GENARRATIVE_WORKSPACE_ROOT:源码根目录;为空时回退到 Jenkins 当前工作区。
  3. BUILD_VERSION:发布版本号;为空时回退到 BUILD_NUMBER
  4. RUN_NPM_CI:是否在构建前执行 npm ci
  5. WEB_PORT:发布包内静态网站监听端口;构建并部署 默认值为 80
  6. CLEAR_DATABASE:部署阶段是否清空 SpacetimeDB 数据后再发布 wasm默认 false

如果当前 Jenkins 没有额外配置独立 Agent而是直接在控制器自身执行任务AGENT_LABEL 应填写 built-in。 如果 Jenkins 进程以默认 jenkins 用户运行,部署目录建议直接放在 /var/lib/jenkins/deploy/Genarrative 这类 Jenkins 自有目录下,避免再依赖 /home/ubuntu/* 的额外写权限。 如果目标 Ubuntu 的 Jenkins sh 默认实际落到 /bin/sh -> dash,而流水线脚本又使用了 set -euo pipefail,则必须显式通过 bash -lc 执行命令,不能直接依赖 Jenkins 默认 sh 解释器。

其中仅 部署 流水线还需要:

  1. SOURCE_WORKSPACE_ROOT
  2. SOURCE_NODE_NAME
  3. DEPLOY_DIRECTORY
  4. CLEAR_DATABASE
  5. RUN_DEPLOY_HOOKS_WITH_SUDO
  6. EXPECTED_UPSTREAM_JOB

其中仅 构建并部署 流水线还需要:

  1. DEPLOY_JOB_NAME
  2. RUN_DEPLOY_HOOKS_WITH_SUDO
  3. WEB_PORT
  4. CLEAR_DATABASE

如果你选择启用 RUN_DEPLOY_HOOKS_WITH_SUDO=true,推荐提前在服务器上增加一份最小 sudoers 配置,例如:

jenkins ALL=(root) NOPASSWD: /var/lib/jenkins/deploy/Genarrative/start.sh
jenkins ALL=(root) NOPASSWD: /var/lib/jenkins/deploy/Genarrative/stop.sh

这样可以把提权范围收敛到固定部署目录下的启停脚本,而不是把整个部署流程都交给 sudo

6. 推荐 Job 命名

建议在 Jenkins 中创建以下 3 个 Pipeline Job并分别指向仓库中的脚本路径

  1. Genarrative-Build -> jenkins/Jenkinsfile.build
  2. Genarrative-Deploy -> jenkins/Jenkinsfile.deploy
  3. Genarrative-Build-And-Deploy -> jenkins/Jenkinsfile.build-and-deploy

同时给 Genarrative-Deploy 配置环境变量:

GENARRATIVE_ALLOWED_UPSTREAM_JOB=Genarrative-Build-And-Deploy

如果 Job 在 Jenkins Folder 下,值应填写完整上游作业名,例如:

game/Genarrative-Build-And-Deploy

7. 文件清单

本方案对应的仓库文件:

jenkins/Jenkinsfile.build
jenkins/Jenkinsfile.deploy
jenkins/Jenkinsfile.build-and-deploy
scripts/jenkins-deploy-release.sh

8. 风险与边界

  1. 该方案依赖本地目录切换,不适用于“构建节点”和“部署节点”完全隔离且不共享文件系统的 Jenkins 架构。
  2. 当前 部署 采取的是“覆盖固定部署目录”的方式,不包含版本回滚目录管理;如需保留完整历史版本,应在后续单独补一层 release/current 软链接结构。
  3. 当前 start.sh / stop.sh 仍以发布包内脚本为准,不替代 systemdsupervisornginxtls 与日志轮转治理。