资讯动态

NocoBase 2.0自动更新实践:定时拉取与构建部署脚本

发布时间:2026/10/15 7:05:07 来源:尧图企业网站定制
NocoBase 2.0 beta 的 next 分支更新有多频繁只有亲手拉过的人才有发言权。我最初是每天早上手动git pull然后装依赖、编译、重启服务一套流程走下来少说二三十分钟要是赶上依赖大改或者构建报错一上午就搭进去了。后来实在受不了这种重复劳动干脆把Git 拉取 next 分支 自动编译 自动重启整个流程做成了每天凌晨定时执行的任务。这篇文章就把我这套折腾下来的完整方案、脚本设计思路、以及跑了一段时间踩到的坑一次性说清楚给同样想尝鲜 NocoBase 2.0 又不想被重复劳动拖垮的朋友一个可直接抄作业的参考。1. 这轮折腾的真正动机2.0 beta 的迭代节奏比想象中快1.1 每天的拉-装-编-启循环有多低效NocoBase 2.0 的 beta 阶段代码变动非常活跃尤其是 next 分支基本就是开发主干几乎每天都有新的 commit。表面上看起来只是改了几行代码但这些改动往往牵一发而动全身。我实际遇到的情况是头天晚上还跑得好好的环境第二天一早就发现某个插件接口变了、某个依赖版本升级了、甚至连数据库表结构都有调整。如果我不及时跟进最新代码等到想用某个新功能的时候光排查为什么我跟官方文档的行为对不上就能耗掉大半天。更浪费时间的其实是那个机械式循环登录服务器git pull然后祈祷不要冲突yarn install等依赖解析执行构建等着打包重启服务祈祷能一次起来打开浏览器验证一下这套流程本身没有什么技术难度但架不住天天重复。我算了一笔账就算每次只花二十分钟一个月下来就是十个小时。十个小时用来写写自己的插件、研究一下 NocoBase 的源码结构不知道划算了多少倍。这就是我决定做自动化的根本原因。1.2 自动化到底是替我省时间还是给我找事在动手之前我想先把这个账算清楚。自动化这套东西你是需要写脚本、配定时任务、处理各种意外情况的这本身也有成本。我的判断标准很简单如果这套流程我预计要跑超过两个月那自动化就值得。因为一次性把脚本写好的成本摊到两个月甚至更长时间里比每天手动操作划算得多。而且自动化的价值不只是省时间更重要的是确定性。手动操作的确定性有多差呢我举个例子有时候我上午拉完代码下午官方又推了新的提交那我的环境就不是最新的了。但如果定时任务定在凌晨执行那么我每天早晨面对的环境永远是截至当天凌晨的最新代码。这对于想跟踪 2.0 beta 进展的人来说价值非常大。另外还有一个容易被忽略的点手动操作容易漏步骤。比如有时候我git pull完就忘了重新构建或者构建完忘了重启结果跑到一个半新不旧的状态出了问题都没法确认是哪一段导致的。自动化脚本不存在这个问题因为每一步都是强制执行的日志也清清楚楚。所以我的结论是对于 天天都要做、流程固定、越做越烦 的操作自动化是稳赚的。接下来我先把手工流程的每个关键点摸清楚确认了这条链路的脾气再写脚本不迟。2. 手动流程走一遍先确认每个环节的脾气2.1 环境准备与依赖选型在写任何自动化脚本之前我建议你先完整地手动跑通一遍流程。这一步的意义不在于你会不会手动操作而在于让你确认每个命令在你的机器环境下表现是否正常。我搭建这台测试服务器的时候环境是这样准备的操作系统使用的 Ubuntu Server安装好基础的 build 工具链Node.js 版本需要严格满足项目要求我用的版本管理器来切换避免系统版本污染包管理器我选择的是 Yarn因为 NocoBase 的官方文档和仓库脚本对 Yarn 的支持最完整数据库用的 PostgreSQL因为从长远看 2.0 的很多特性在 PostgreSQL 上支持得最好这些前置条件里面最容易出问题的是 Node.js 版本。我遇到过某些依赖在特定 Node 版本下编译报错的情况后来统一用一个长期维护版本问题少了很多。数据库初始化这里我也不展开了但提醒一句如果你和我一样是在已有环境上重新拉代码数据库迁移这块要特别注意后面的坑位专门讲。2.2 拉取 next 分支的正确姿势拉取代码这一步最忌讳的就是无脑git pull。原因很简单——next 分支是开发分支它可能在你本地落后很多的情况下强制推送过历史这时候普通 pull 就会产生一堆莫名其妙的冲突。我用的方案是这样的# 先切到 next 分支确保本地没有游离的 HEAD git checkout next # 拉取远程的最新状态 git fetch origin # 用 reset 方式把本地分支强制对齐到远程 git reset --hard origin/next这套组合命令对本地代码的改动是毁灭性的。如果你在本地改过东西会被直接覆盖掉。所以我在执行自动化之前专门做了两条规定这台服务器只是用来跑 NocoBase 测试环境的不在里面做任何代码开发本地如果有临时改动一律先 commit 到单独的功能分支再合并否则 reset 之后哭都来不及提示git reset --hard不是银弹。如果你的本地仓库存在比较重要的未推送提交请务必提前做好备份或者避免使用这个方案。2.3 编译与启动命令的实际表现NocoBase 这种前后端一体的项目编译流程比普通的前端项目复杂一些。它需要同时处理服务端代码和客户端构建。我这边实际执行的命令大致如下# 安装依赖遇到 lock 文件变更会自动解析 yarn install # 完整构建 yarn build构建的耗时取决于服务器性能。我现在这台机器上全套下来大概在 10 到 15 分钟之间波动。这个时间其实不算长但对于定时任务来说我必须在脚本里设置足够的超时时间否则构建还没跑完就被系统杀掉那就白跑了。启动服务这块我记得官方文档里有 dev 模式和生产模式的区分。为了模拟真实环境我选择了生产模式启动也就是构建完成后用yarn start --quickstart这类方式直接拉起来。这一步特别费内存曾经我因为同时开着旧服务和新构建导致服务器内存告急直接卡死所以在后面的自动化脚本里特意设计了先停旧服务再构建的顺序。手动跑通一遍之后我就对这条链路的花费心里有数了。接下来要做的就是把这一整串动作交给一段脚本。3. 把手工流程转成一个可维护的自动化脚本3.1 脚本的整体结构与执行顺序设计脚本的时候我给自己订了几条原则每一步都要有独立的日志输出方便失败后定位任何一步失败整个流程必须中止不能带伤继续脚本要能够重复执行即使某次跑到一半被中断下次执行也能正常恢复基于这几条原则我最终的脚本结构分成四个阶段Git 更新阶段拉取最新代码确认是否有新提交依赖与构建阶段安装依赖并完成编译服务重启阶段停止旧服务启动新服务健康检查阶段验证新服务是否真正可用这里的顺序设计是有讲究的。我没有把停止旧服务放在最前面而是放在构建完成之后就是为了减少服务不可用的时间窗口。虽然定时任务通常会在凌晨执行外界访问量本来就少但能少断一会儿是一会儿。3.2 Git 层的处理绝不硬拉先检查再更新我的更新脚本是这样写的#!/usr/bin/env bash PROJECT_DIR/path/to/nocobase LOG_FILE/var/log/nocobase-auto-update.log BRANCHnext # 记录日志的函数 log() { echo $(date %Y-%m-%d %H:%M:%S) - $1 $LOG_FILE } log 开始自动更新流程 cd $PROJECT_DIR || exit 1 # 检查是否有更新的提交 git fetch origin $BRANCH LOCAL_REV$(git rev-parse HEAD) REMOTE_REV$(git rev-parse origin/$BRANCH) if [ $LOCAL_REV $REMOTE_REV ]; then log 没有新的提交跳过本次更新 exit 0 fi # 有更新执行强制对齐 git reset --hard origin/$BRANCH log 代码已更新: $LOCAL_REV - $REMOTE_REV没有新提交就直接退出这个设计非常关键。我见过不少人写 crontab 的时候不管有没有更新都执行一遍yarn install和yarn build结果浪费大量 CPU 和内存。有了这个判断逻辑大多数凌晨的执行都会在几秒钟内快速结束只有真正有新提交的时候才走完整流水线。3.3 构建层的处理锁文件、缓存与超时守护构建阶段是整个流程中最脆弱的环节依赖安装可能失败、编译可能报错、服务器内存可能不够。我做三个防护措施第一依赖安装要区分情况。如果yarn.lock文件有变化说明依赖树发生了变动这时候我直接走完整安装如果没有变化可以用yarn install --frozen-lockfile这种模式速度更快也更稳定。第二构建命令需要设置超时保护。我是用timeout命令包住整个构建过程比如timeout 3600 yarn build if [ $? -ne 0 ]; then log 构建失败或超时流程终止 exit 1 fi这里的 3600 秒是我根据机器性能留出的余量。如果你的机器比较弱建议把这个值调大否则构建到一半被中断下次还要全部重来。第三注意服务器的磁盘空间和内存余量。构建过程会生成大量临时文件和产物如果磁盘满了任何操作都会失败。我专门写了一个磁盘检查DISK_USAGE$(df / | awk NR2 {print $5} | sed s/%//) if [ $DISK_USAGE -gt 85 ]; then log 磁盘使用率过高: ${DISK_USAGE}%终止流程请人工处理 exit 1 fi3.4 应用层的处理优雅下线与拉起构建完成之后才是重启的阶段。我一开始是直接杀掉旧进程再启动新的但发现这样偶尔会留下残留的子进程导致端口还是被占用新的服务反而起不来。后来我改成了优雅停机的思路# 如果有旧服务在运行先发送终止信号等待退出 if [ -f $PROJECT_DIR/pidfile ]; then OLD_PID$(cat $PROJECT_DIR/pidfile) if kill -0 $OLD_PID 2/dev/null; then kill $OLD_PID sleep 10 # 10 秒后如果还活着强制终止 if kill -0 $OLD_PID 2/dev/null; then kill -9 $OLD_PID fi fi fi # 清理端口占用以防万一 fuser -k 13000/tcp || true这个脚本一开始给了优雅停止 10 秒的等待时间正常情况下够 NocoBase 完成收尾了。10 秒后还没退出才会强制执行强杀。用fuser -k清理端口是最后一道保险避免端口被残留进程占用导致新服务启动失败。启动部分我没有直接用yarn start裸奔而是用进程守护工具来托管。这样即使服务崩溃了也能自动拉起比裸进程可靠得多。4. 交给系统去定时定时器与守护编排4.1 定时执行方案的选择脚本写好了接下来就是解决每天自动执行的问题。常见的方案无非是 cron 和 systemd timer我自己最终选的是 systemd timer原因后面说。如果你更习惯用 cron最简单的写法是这样0 4 * * * /usr/local/bin/nocobase-update.sh /var/log/nocobase-cron.log 21每天凌晨 4 点整执行一次。这个时间点是我综合考虑了官方团队通常的提交时间以及服务器负载之后选的但这只能算是一个经验值大家可以根据自己的时区和网络情况调整。cron 的好处是简单、哪里都有、不用学新东西。但它的缺点也很明显——没有天然的依赖管理服务崩溃了没人管日志也要自己手动处理。4.2 systemd 定时器和服务的组合玩法我最终用的是 systemd 方案具体分为一个 service 单元和一个 timer 单元。service 单元负责真正执行脚本[Unit] DescriptionNocoBase Auto Update Service [Service] Typeoneshot ExecStart/usr/local/bin/nocobase-update.sh StandardOutputappend:/var/log/nocobase-systemd.log StandardErrorappend:/var/log/nocobase-systemd.logtimer 单元负责触发它[Unit] DescriptionTimer for NocoBase Auto Update [Timer] OnCalendar*-*-* 04:00:00 Persistenttrue [Install] WantedBytimers.target这套组合里Persistenttrue是个很有用的参数。它表示如果系统在凌晨 4 点处于关机状态那么开机之后会立即补跑一次任务不会出现机器没开机就错过更新的情况。相比纯 cronsystemd 方案的好处是服务状态可查询、日志统一管理、还能用systemctl status随时看执行结果。这对于一个要长期跑的自动化任务来说省心很多。4.3 通知机制失败必须第一时间知道自动化任务最容易出现的问题是失败而无人知晓。我一开始跑了一个多星期翻日志的时候才发现中间有两次构建失败但那时候已经不知道该对应哪个代码版本了。所以我在脚本末尾加了一个简单的通知逻辑失败的时候直接把错误信息发到常用的通讯工具比如邮件或者 IM webhook。这样每天早上只要看一眼手机就知道昨晚的更新是成功还是失败。if [ $? -ne 0 ]; then curl -s -X POST https://your-webhook-url \ -H Content-Type: application/json \ -d {\text\: \NocoBase 自动更新失败请检查日志\} exit 1 fi这不牵扯到复杂的基础设施一个 webhook 请求就够了。关键是让人能在第一时间介入而不是等自己发现的时候已经积压了几天的问题。5. 自动流水线里最容易踩的坑与排查记录5.1 端口冲突旧进程没死透新进程起不来这是我这套流水线运行以来遇到最多的一个问题。现象是脚本日志里显示构建成功、服务也执行了启动命令但浏览器访问却打不开。查了半天发现问题的根源在于旧进程的残留子进程还在绑定端口。我原来的停止方案是用kill结束主进程但这个项目启动之后可能会 fork 出若干个子进程主进程退出了子进程还占着端口不放。之后新服务尝试监听同一个端口直接报EADDRINUSE。最终的解决方案已经在前面出现过了就是先用kill -0检查进程是否还活着等 10 秒然后再用fuser -k强制清理端口。这套三重保险基本把这个问题压下去了。5.2 依赖变更导致的构建失败next 分支的开发节奏决定了依赖变动非常频繁。有时候仅仅是一个依赖的小版本升级就能让整个构建在某个插件上挂掉。我遇到过的典型报错是某个原生模块需要重新编译但由于本机的 Node 版本或者工具链不对编译失败。这种情况手动处理都很烦躁更别说是凌晨自动跑了。我的排查链路是这样的先看构建日志定位是在哪个插件的编译阶段挂掉的检查是不是因为 Node.js 版本变了跟官方要求的版本对不上查看项目仓库近期有没有调整过依赖引入方式例如从某个老包切换到新包如果是原生模块的问题尝试清理掉整个 node_modules 和构建缓存重新来一遍rm -rf node_modules rm -rf .cache yarn install yarn build有没有一劳永逸的办法呢说实话没有。因为 next 分支本身就包含着实验性变更构建失败在某种程度上是正常的。我能做的是让脚本在这种情况下自动终止发出告警而不是带着失败的结果硬启动一个旧版本的服务造成新旧代码混跑的诡异状态。5.3 磁盘空间与日志增长的隐患自动更新跑久了最隐蔽的问题其实是磁盘。每次构建都会产生新的产物日志文件也在不断膨胀。我第一次发现这个问题是某天脚本报磁盘空间不足无法写入日志。从那以后我在脚本里加了两条日志清理策略超过 7 天的日志文件自动压缩并删除保留最近 5 次构建产物旧的构建产物自动清理这些都是用一行 find 命令就能完成的动作但没了它们再稳定的流水线也会被慢吞吞地挤垮。5.4 数据库迁移的提醒前面手动流程我提到过数据库的事情这里展开说。NocoBase 2.0 的 beta 阶段数据库表结构的变动不算罕见。有些新版本在启动时会自动做迁移有些则需要在启动前手动执行迁移命令。我遇到过最尴尬的情况是代码更新成功、构建成功、服务也起来了但业务数据查询报错原因是表结构跟新的代码对不上。解决方法是固定下来启动前必须额外执行一步初始化或迁移指令。现在我的流水线脚本里只有检测到数据库相关变更时才会执行额外迁移步骤通过对比变更文件的方式直接把这个环节也纳入自动化避免手工漏执行。注意数据库迁移操作在自动化场景里要慎之又慎。如果迁移失败千万不要重试先把日志保存好早晨起来人工处理。自动化的边界要清楚不是所有动作都适合交给机器去反复折腾的。5.5 网络不稳定拉取失败的正确处理Git 拉取依赖外网网络波动在所难免。我的脚本目前在 fetch 失败时会直接退出并报警。这里有一个容易被忽略的细节如果git fetch失败而代码又没有更新我会保持默认退出不重试。有人可能会问为什么不重试因为 fetch 失败时大概率是网络不稳定重试偶尔能成功但如果是 DNS 或者配置问题重试多少次都没用。与其在主流程里写一个复杂的重试逻辑不如把它交给外层的定时机制。反正我们的定时是每天一次如果某天凌晨失败第二天凌晨还会再跑。如果真的担心可以再加一个每小时的轻量检查任务只做 fetch 判断不拉代码这样不至于浪费构建资源又能提高时效性。这套自动流水线我跑了一阵子之后最大的感受就是省心。我甚至都不太需要记得它是哪天更新的每天早上看一眼前一天的运行通知确认今天的环境状态就好。对于想长期跟踪 NocoBase 2.0 beta 的人来说与其每天花半小时在机械操作上不如花一个周末把整个流程自动化掉然后把精力留给真正值得研究的东西。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑