9.1 KiB
Jenkins Rust 构建与部署流水线方案
日期:2026-04-23
1. 目标
本方案为当前仓库补齐 3 条 Jenkins 流水线:
构建:只负责在仓库根目录执行npm run deploy:rust:remote -- --skip-upload,生成发布包。部署:只负责把指定发布版本部署到/var/lib/jenkins/deploy/Genarrative/,允许人工按参数启动,并支持按参数决定是否清空 SpacetimeDB 数据。构建并部署:先构建,再把构建出的版本号传给部署流水线并等待部署完成;同时暴露WEB_PORT参数,默认把发布包 Web 端口写成80,并透传是否清库。
本次只补 Jenkins 编排与本地部署脚本,不改现有 Rust 发布包构建逻辑,不恢复旧 server-node 部署链。
2. 执行约束
- 构建产物目录统一使用
build/<版本号>/。 - 默认使用 Jenkins
BUILD_NUMBER作为版本号,避免依赖时间戳;如有需要也允许显式传BUILD_VERSION。 构建与构建并部署在checkout scm后、实际构建前必须执行git reset --hard HEAD与git clean -fd,避免固定源码目录内的 Git 变更和未跟踪文件影响发布包;不使用-x,避免删除node_modules/等忽略目录后与RUN_NPM_CI=false冲突。部署流水线允许人工启动;没有上游触发 cause 时按人工部署处理,不再直接失败。部署流水线仅在存在上游触发 cause 时校验上游作业名与传入的EXPECTED_UPSTREAM_JOB一致;如配置了环境变量GENARRATIVE_ALLOWED_UPSTREAM_JOB,还必须与该值一致。构建并部署在触发部署前先释放自己的构建节点,避免单执行器节点出现死锁。部署不重新构建,不重新上传,不从 Jenkins 插件仓库复制产物,直接使用上游构建节点的本地build/<版本号>/目录。部署流水线读取触发原因时必须使用currentBuild.getBuildCauses(...)这类白名单方法,不能直接访问currentBuild.rawBuild,否则会被 Jenkins Script Security 拦截。- 由于 Jenkins Pipeline 的
build步骤触发下游时,原因类型通常是org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause,实现上需要同时兼容它和经典的hudson.model.Cause$UpstreamCause,否则会把真实的上游触发误判成人工执行。 - 如果线上进程的启停必须经过
sudo,只允许start.sh/stop.sh这两个 hook 使用sudo -n执行,部署目录清空与文件覆盖仍保持普通权限。
3. 节点与工作区要求
这套方案依赖“本地目录发布”,因此有两个前提:
构建并部署与部署必须落到同一台 Ubuntu Jenkins Agent,或者落到同一块共享文件系统。构建并部署触发部署时,必须把SOURCE_NODE_NAME和SOURCE_WORKSPACE_ROOT一并传下去。
仓库中提供的 Jenkinsfile 已按这个约束实现:
构建/构建并部署在指定源码目录内checkout scm并生成build/<版本号>/。构建并部署结束构建节点占用后,再触发部署。部署优先按SOURCE_NODE_NAME调度到同名节点,再读取SOURCE_WORKSPACE_ROOT/build/<版本号>/。
4. 三条流水线定义
4.1 构建
脚本路径:
jenkins/Jenkinsfile.build
核心流程:
checkout scm后执行git reset --hard HEAD与git clean -fd清理工作区。- 可选执行
npm ci。 - 在源码根目录执行:
npm run deploy:rust:remote -- --skip-upload --name <BUILD_VERSION>
- 校验
build/<BUILD_VERSION>/存在。 - 归档
build/<BUILD_VERSION>/**作为 Jenkins 产物。
默认版本号:
BUILD_VERSION = Jenkins BUILD_NUMBER
4.2 部署
脚本路径:
jenkins/Jenkinsfile.deploy
核心流程:
- 读取触发原因;人工启动时跳过上游门禁,上游触发时同时兼容
BuildUpstreamCause与经典UpstreamCause并继续校验上游作业名。 - 校验
BUILD_VERSION、SOURCE_WORKSPACE_ROOT、DEPLOY_DIRECTORY非空。 - 执行:
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
脚本语义:
- 若部署目录已有旧版本且存在
stop.sh,先执行旧版本stop.sh。 - 只删除发布产物白名单中的旧文件,例如
web/、api-server、spacetime_module.wasm、.env*、start.sh、stop.sh、web-server.mjs、README.md。 - 将指定版本目录中的同名发布产物移动到部署目录。
- 如果
CLEAR_DATABASE=true,部署脚本会以./start.sh --clear-database启动新版本;这样发布阶段的spacetime publish会追加-c always。 - 执行新版本
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
核心流程:
checkout scm后执行git reset --hard HEAD与git clean -fd清理工作区。- 复用与
构建相同的构建命令生成build/<BUILD_VERSION>/。 - 归档
build/<BUILD_VERSION>/**。 - 记录当前
NODE_NAME、源码根目录、版本号。 - 构建时额外透传
--web-port <WEB_PORT>,默认生成监听80的发布包。 - 触发
部署流水线,并传递:BUILD_VERSIONSOURCE_WORKSPACE_ROOTSOURCE_NODE_NAMEDEPLOY_DIRECTORYCLEAR_DATABASEEXPECTED_UPSTREAM_JOB
5. Jenkins 参数建议
三条流水线统一建议暴露以下参数:
AGENT_LABEL:默认执行节点标签。GENARRATIVE_WORKSPACE_ROOT:源码根目录;为空时回退到 Jenkins 当前工作区。BUILD_VERSION:发布版本号;为空时回退到BUILD_NUMBER。RUN_NPM_CI:是否在构建前执行npm ci。WEB_PORT:发布包内静态网站监听端口;构建并部署默认值为80。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 解释器。
其中仅 部署 流水线还需要:
SOURCE_WORKSPACE_ROOTSOURCE_NODE_NAMEDEPLOY_DIRECTORYCLEAR_DATABASERUN_DEPLOY_HOOKS_WITH_SUDOEXPECTED_UPSTREAM_JOB
其中仅 构建并部署 流水线还需要:
DEPLOY_JOB_NAMERUN_DEPLOY_HOOKS_WITH_SUDOWEB_PORTCLEAR_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,并分别指向仓库中的脚本路径:
Genarrative-Build->jenkins/Jenkinsfile.buildGenarrative-Deploy->jenkins/Jenkinsfile.deployGenarrative-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. 风险与边界
- 该方案依赖本地目录切换,不适用于“构建节点”和“部署节点”完全隔离且不共享文件系统的 Jenkins 架构。
- 当前
部署采取的是“覆盖固定部署目录”的方式,不包含版本回滚目录管理;如需保留完整历史版本,应在后续单独补一层 release/current 软链接结构。 - 当前
start.sh/stop.sh仍以发布包内脚本为准,不替代systemd、supervisor、nginx、tls与日志轮转治理。