forked from wangbo/easyai
feat(deploy): 为更新组件增加定时维护与双保险
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# updater 双保险维护验收
|
||||
|
||||
日期:2026-10-08。代码位于 `/Users/wangbo/projects/easyai`。本记录补充前序 Watchtower 宿主机维护验收,描述本轮新增的 updater 独立定时更新通道。
|
||||
|
||||
## 结果
|
||||
|
||||
宿主机定时任务每六小时分别维护 Watchtower 和已启用的 updater;原 Watchtower 每 24 小时维护 updater 的机制保留。无需新增容器或再注册另一条宿主机任务。
|
||||
|
||||
每项维护分别拉取、按需重建和验证健康状态。某项失败仍继续另一项,同时记录失败并返回非零结果。不会因 Watchtower 不可用而跳过 updater。旧部署未启用 updater 时保持原更新模式。
|
||||
|
||||
验收时尚未提交或推送源码;本轮真实运行结果不代表已部署至线上。本轮没有实际注册本机 cron 或 Windows 计划任务。旧机器需由运维执行一次新版部署脚本接入;发布源码本身不能给机器安装宿主机任务。
|
||||
|
||||
## 修改前真实复现
|
||||
|
||||
独立 Compose 项目 `easyai-updater-insurance-20261008`,Watchtower 1.22.3、updater 1.1.0,本机 registry 发布 updater 1.2.0。主动停止本次自建的 Watchtower,并把该项目 Watchtower 镜像设置为不存在的标签,模拟 Watchtower 恢复失败。
|
||||
|
||||
分别运行原 Bash 和 PowerShell 定时入口:均在 Watchtower 拉取失败时退出;updater 继续运行旧版本 1.1.0,容器及镜像均未变化。
|
||||
|
||||
证据:`before.json`、`before-bash.log`、`before-powershell.log`,原入口快照保存在 `before-scripts/`。这说明原定时维护只负责 Watchtower,不能在其失效时直接维护 updater。
|
||||
|
||||
## 本轮变更及影响范围
|
||||
|
||||
- 修改 `scripts/update-infrastructure.sh` 和 `scripts/Update-Infrastructure.ps1`:独立处理两个组件,失败分别记录,最后汇总结果;保持部署目录、Docker 登录配置、并发锁及只重建指定服务的行为。
|
||||
- 修改 Bash/PowerShell 任务安装器的提示文案,保留原任务标识、每六小时周期、去重及关闭方式,不重复注册任务。
|
||||
- 更新 `.env.sample` 和 README,说明 updater 两条维护路径。
|
||||
- 扩展 `scripts/test-infrastructure-auto-update.py`,覆盖两种入口、两种 Compose 命令路径、独立失败、legacy 跳过及健康检查。
|
||||
|
||||
主要调用方仍为 `start.sh/update.sh/start.ps1/update.ps1` 安装的定时任务。没有修改业务自动更新开关,也没有改动 updater、后端、前端业务代码及已有任务存储格式。此前工作区中其他未提交改动均保留。
|
||||
|
||||
## 实际运行结果
|
||||
|
||||
| 场景 | 实际结果 |
|
||||
| --- | --- |
|
||||
| Watchtower 已停止且拉取失败,Bash 定时入口 | updater 实际升级 1.1.0 → 1.2.0;Watchtower 仍停止,证明该升级不依赖它;返回失败以保留 Watchtower 故障结果 |
|
||||
| 相同条件,PowerShell 定时入口 | updater 实际升级 1.1.0 → 1.2.0;同样独立执行并报告部分失败 |
|
||||
| updater 历史任务 | Bash 独立更新后,实际任务 API 仍能读取原有成功任务及其消息 |
|
||||
| updater 拉取失败 | updater 容器未停止或重建;Watchtower 仍恢复健康;整体报告失败 |
|
||||
| 正常维护及镜像无变化 | 两组件均健康;镜像和配置无变化时容器 ID 不变 |
|
||||
| legacy profile 未启用 updater | Bash、PowerShell 均不创建或启动 updater |
|
||||
| 原 Watchtower 更新通道 | 重新开启轮询并发布 updater 新标签,Watchtower 仍实际更新 updater 1.1.0 → 1.2.0 |
|
||||
| 业务服务对照 | 全程容器 ID 不变,没有被维护入口直接重建 |
|
||||
|
||||
实际运行验收耗时 27.58 秒,不含镜像准备及修改前复现。证据:`after.json`、各场景 `.log`、`watchtower-final.log`。这是自建 registry、真实 Compose 和真实更新组件的运行结果。业务服务为独立 Node 对照容器,并未部署整套业务及数据库。
|
||||
|
||||
## 定向检查
|
||||
|
||||
- `python3 scripts/test-infrastructure-auto-update.py`:通过,19.496 秒。覆盖 Bash/PowerShell、Compose v2/v1 命令路径、单项失败不影响另一项、全部拉取失败不重建、legacy 跳过、并发排除、健康探测,以及原有迁移和 cron 去重保护。
|
||||
- `pwsh -NoProfile -File scripts/Test-InfrastructureUpdateTask.ps1`:通过,0.18 秒。验证注册参数及去重、关闭行为;计划任务 API 为测试替身。
|
||||
- Bash/PowerShell 语法、ShellCheck、diff 空白检查:通过,合计 0.72 秒。PowerShell UTF-8 BOM 保持不变,供 Windows PowerShell 5.1 使用。
|
||||
|
||||
证据:`validation.json`、`quality-checks.json` 及本轮工具输出。没有运行全量前端质量门禁,本轮未涉及前端业务代码。
|
||||
|
||||
## 验证边界
|
||||
|
||||
Windows 宿主机真实计划任务注册、生产六小时和 24 小时长周期、客户私有仓库网络及权限尚未验证。Compose v1 是命令适配行为验收,实际容器验收使用 Docker Compose v5.3.1。PowerShell 实际容器入口在 macOS 上运行。
|
||||
|
||||
历史终态任务持久化已实际验证;本轮没有在业务更新执行到一半时替换 updater 的故障演练,也不据此宣称所有活动任务恢复已验收。
|
||||
|
||||
Compose 根据启用的 profile 选择可维护服务,运行 `config --services` 已实测关闭 profile 时不包含 updater。避免直接显式启动未启用的 updater,因显式指定服务会自动启用其 profile。[Docker 官方说明](https://docs.docker.com/compose/how-tos/profiles/)
|
||||
|
||||
本方案仍需 Docker 和镜像仓库可用;镜像启动失败不会自动回滚。每项维护有健康等待并报告失败,后续定时任务继续检查。
|
||||
|
||||
本次已清理四个自建验收容器及独立网络,原本机 `test-watchtower-1` 保持健康。证据:`cleanup.json`。
|
||||
|
||||
原始运行日志与入口快照保存在本机证据目录:`/Users/wangbo/.codex/visualizations/2026/10/07/01a11505-cae4-76e0-9ba9-235f6962b2b4/updater-double-insurance-20261008`。本目录附带修改前、修改后及定向检查的结构化结果。
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"passed": true,
|
||||
"bashUpdatedUpdaterWithWatchtowerStoppedAndPullFailed": true,
|
||||
"powershellUpdatedUpdaterWithWatchtowerStoppedAndPullFailed": true,
|
||||
"failedServiceReportedWhileOtherServiceMaintained": true,
|
||||
"persistedJobRetained": true,
|
||||
"updaterPullFailurePreservedContainerAndWatchtowerRecovered": true,
|
||||
"unchangedImagesNoRecreate": true,
|
||||
"legacyProfileDoesNotStartUpdater": true,
|
||||
"originalWatchtowerChannelStillUpdatesUpdater": true,
|
||||
"businessContainerUnchanged": true,
|
||||
"seconds": 27.58,
|
||||
"before": [
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-watchtower",
|
||||
"id": "f177e0655e789190f9f06533e1035d166b5924db7d2afc1d1e242abb43b1bd0c",
|
||||
"image": "sha256:6ba1e78a0c9ce1ae5389ecf8a78426b82beaf3357c71d52548f7d05fd92eebb7",
|
||||
"running": false
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-updater",
|
||||
"id": "9837e6b0328798dbbad17ff310ef8d6d27761c1d152d374a91e56fdb3009c31e",
|
||||
"image": "sha256:275e9ff7fb453d16feabfb9b92e1cd603f49c434bf2daa80a9f5ad6f505bffcb",
|
||||
"running": true
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-business",
|
||||
"id": "6b733cf4ee75e2136430bb1b53ec49c1685d3958ad7cebe862b4a52b139e847f",
|
||||
"image": "sha256:5bbd38fd80132923f2ea205d02836fefdf7eb3555fd93f0bb58e26a899c0e493",
|
||||
"running": true
|
||||
}
|
||||
],
|
||||
"afterIndependentTimer": [
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-watchtower",
|
||||
"id": "f177e0655e789190f9f06533e1035d166b5924db7d2afc1d1e242abb43b1bd0c",
|
||||
"image": "sha256:6ba1e78a0c9ce1ae5389ecf8a78426b82beaf3357c71d52548f7d05fd92eebb7",
|
||||
"running": false
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-updater",
|
||||
"id": "f4b5029ae1bd5b9d33ef472039b3e938242ac68c5d3a36019035a745532a0734",
|
||||
"image": "sha256:d7e4d04d4e614d537a08ee24f58317a3d6734e3514ca61eafc92bd1bb7a8d6ef",
|
||||
"running": true
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-business",
|
||||
"id": "6b733cf4ee75e2136430bb1b53ec49c1685d3958ad7cebe862b4a52b139e847f",
|
||||
"image": "sha256:5bbd38fd80132923f2ea205d02836fefdf7eb3555fd93f0bb58e26a899c0e493",
|
||||
"running": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"reproduced": true,
|
||||
"watchtowerStopped": true,
|
||||
"watchtowerPullFailed": true,
|
||||
"updaterRemainedOld": true,
|
||||
"bashExitCode": 1,
|
||||
"powershellExitCode": 1,
|
||||
"containers": [
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-watchtower",
|
||||
"id": "f177e0655e789190f9f06533e1035d166b5924db7d2afc1d1e242abb43b1bd0c",
|
||||
"image": "sha256:6ba1e78a0c9ce1ae5389ecf8a78426b82beaf3357c71d52548f7d05fd92eebb7",
|
||||
"running": false
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-updater",
|
||||
"id": "9837e6b0328798dbbad17ff310ef8d6d27761c1d152d374a91e56fdb3009c31e",
|
||||
"image": "sha256:275e9ff7fb453d16feabfb9b92e1cd603f49c434bf2daa80a9f5ad6f505bffcb",
|
||||
"running": true
|
||||
},
|
||||
{
|
||||
"name": "/easyai-updater-insurance-20261008-business",
|
||||
"id": "6b733cf4ee75e2136430bb1b53ec49c1685d3958ad7cebe862b4a52b139e847f",
|
||||
"image": "sha256:5bbd38fd80132923f2ea205d02836fefdf7eb3555fd93f0bb58e26a899c0e493",
|
||||
"running": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"ownContainersRemoved": [
|
||||
"/easyai-updater-insurance-20261008-watchtower",
|
||||
"/easyai-updater-insurance-20261008-updater",
|
||||
"/easyai-updater-insurance-20261008-business",
|
||||
"/easyai-updater-insurance-20261008-registry"
|
||||
],
|
||||
"ownNetworkRemoved": true,
|
||||
"originalLocalTestWatchtowerHealthy": true
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"BashSyntax": true,
|
||||
"PowerShellSyntax": true,
|
||||
"ShellCheck": true,
|
||||
"DiffWhitespace": true,
|
||||
"seconds": 0.72
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"infrastructureTests": {
|
||||
"passed": true,
|
||||
"seconds": 19.496
|
||||
},
|
||||
"powershellTaskContract": {
|
||||
"passed": true,
|
||||
"seconds": 0.18
|
||||
},
|
||||
"windowsPowerShellEncoding": {
|
||||
"bomPreserved": true
|
||||
},
|
||||
"fullQualityGateRun": false
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
# 在线更新发布源修复与测试验收(2026-10-07)
|
||||
|
||||
本次修复已在 `https://demo.51easyai.com` 完成真实浏览器验收。候选镜像和测试发布源只用于测试环境;验收时尚未提交代码;该测试验收未更新正式 `latest` 镜像和仓库根目录的正式发布清单。
|
||||
|
||||
## 已复现问题与根因
|
||||
|
||||
1. 测试站运行 `3.22.0.20260930.3`,页面从官方站运行版本接口检测到 `3.23.1.20261006`。
|
||||
2. 浏览器点击“立即更新”,任务 `bcaa8c58-3628-42fc-af02-bc5f97bbc1c2` 返回 `no_update`,实际版本和核心容器均未改变;页面仍提示有新版本。
|
||||
3. 更新器实际读取 `/var/lib/easyai-updater/release-manifest.json`,该路径由 `data/deployment-config/legacy-compose.json` 覆盖,内容仍指向旧版镜像。更新器不获取远程发布清单。
|
||||
4. 页面检测来源与执行来源不同;前端没有核对任务实际提供的版本,误把旧清单的“无需更新”作为新版已经完成更新。
|
||||
5. 正式部署仓库的发布清单也仍为 `3.22.0.20261002.2`;镜像仓库有后端 `3.23.1.20261006` 标签,但没有同版本前端标签。单纯改成本地另一条清单路径不能解决发布信息不一致。
|
||||
|
||||
## 最小修复及兼容性
|
||||
|
||||
- 更新器默认从部署仓库 `main` 分支获取正式清单;私有源使用 `UPDATE_MANIFEST_URL`,离线部署须显式选择 `UPDATE_MANIFEST_SOURCE=local`。默认远程模式忽略历史本地路径覆盖,获取失败不会退回旧清单。
|
||||
- 受控模式的版本检测通过更新器获取可安装版本,不再取官方站当前运行版本;旧 Watchtower 模式保留原协议和检测缓存。
|
||||
- 新版客户端提交检测到的版本与发布标识;执行前重新获取清单,目标发生变化时拒绝安装另一版本。旧客户端只传 `action` 的请求继续兼容。
|
||||
- 任务保存完整清单,恢复及回滚使用同一快照。前端核对成功或“无需更新”的实际版本,避免错误提示;普通更新、强制更新和管理员新版提醒成功后均刷新检测结果。
|
||||
- 只对更高版本显示新版提示;受控更新拒绝自动回退到更低版本,包括强制更新。发布源不可用时显示“暂时无法检查”和“重新检查”。
|
||||
- 主要调用方:系统设置更新卡片、管理员新版提醒、任务恢复入口、自动更新调度及旧手动接口;未增加业务 Provider 或改变自动更新开关。
|
||||
|
||||
责任代码与本次改动位置:
|
||||
|
||||
- 后端 `apps/easyai-updater/src/release-source.ts`、`update-runner.ts`、`main.ts`:发布源、目标固定、快照恢复与内部发布信息接口。
|
||||
- 后端 `apps/easyai-server-main/src/app.service.ts`、`system-update/managed-update.service.ts`、`system-update/system-update.controller.ts`:统一检测来源、兼容请求及目标字段传递。
|
||||
- 前端 `composables/system-update/useSystemUpdateJob.ts`:核对更新结果与检测目标;`pages/admin/sys/operationConfig.vue` 和 `components/product-update/ProductUpdateAdminTouchpoint.vue`:版本比较、检测异常提示及成功后刷新。
|
||||
- 部署仓库 `docker-compose.yml`、`.env.sample`:默认远程发布源与显式离线选项;原正式清单保留不变。
|
||||
|
||||
## 真实运行验收
|
||||
|
||||
先部署保留旧版本号的修复版控制入口作为测试基线;前端基线使用独立镜像,使实际任务能够验证网页容器替换。最终安装的镜像来自本次工作区构建。候选清单见同目录 `release-manifest.candidate.json`,不作为正式发布清单。
|
||||
|
||||
| 场景 | 结果 |
|
||||
| --- | --- |
|
||||
| 管理员通过真实登录界面进入系统设置并点击立即更新 | 只提交一次请求;任务 `5a2968c5-9615-4e1f-9d88-79ada0f6aa06` 成功,页面约 88.9 秒完成反馈 |
|
||||
| 版本与容器核对 | `3.22.0.20260930.3` → `3.24.0.20261007`;后端和网页容器实际替换,实时服务镜像无需变化 |
|
||||
| 页面刷新 | 显示 `3.24.0.20261007`、已是最新,不重复弹出新版提醒 |
|
||||
| 同版本普通更新 | 返回 `no_update`,不重复替换容器 |
|
||||
| 强制更新确认后取消 | 不创建任务 |
|
||||
| 发布源真实返回 HTTP 503 | 页面显示暂时无法检查,不能误报已是最新;恢复源后点击重新检查恢复正常 |
|
||||
| 发布源改成旧正式清单 | 不提示有新版;点击强制更新后,任务 `92182a3e-1948-4d82-99bc-aacb7c1c601f` 在镜像拉取前被拒绝,原因 `release_older_than_current` |
|
||||
| 配置保留 | 核心服务环境变量、完整容器配置(除镜像)、端口、挂载路径及权限、网络地址和别名保持一致;宿主机 `.env` 与 `docker-compose.yml` 哈希不变 |
|
||||
| 自动更新设置 | 验收前后均为开启,未改变原开关 |
|
||||
| 更新器实际重启 | 已完成任务完整保留,恢复后仍提供同一测试发布信息;四个相关容器均健康 |
|
||||
|
||||
首次候选镜像补装运行依赖时移除了内部共享包,启动检查失败后恢复了原服务。补齐共享包并通过加载检查后,重新部署健康基线,再进行上述正式浏览器验收;没有把失败启动计为升级成功。
|
||||
|
||||
## 定向检查
|
||||
|
||||
本地检查使用 Node `22.22.0`、Yarn `1.22.22`,共 79 个不同的定向行为用例通过:
|
||||
|
||||
| 检查 | 结果与耗时 |
|
||||
| --- | --- |
|
||||
| updater Node 测试 | 14 个通过,约 1.6 秒;覆盖真实 HTTP 发布源、源失败、目标变化、快照恢复及旧版回退保护 |
|
||||
| 后端 system-update Jest | 38 个通过,约 2.3 秒;包含新旧检测协议、实际 DTO 校验、自动更新调度和旧手动接口 |
|
||||
| 前端任务与实际挂载测试 | 首轮 13 个通过,约 49 秒;追加后的设置与更新处理函数 5 个通过,约 4 秒,其中新增强制更新用例 1 个 |
|
||||
| 版本工具和产品版本测试 | 13 个通过,约 0.13 秒 |
|
||||
| 部署脚本隔离 dry-run | 通过,约 4.3 秒,确认默认远程源及原部署契约 |
|
||||
| 后端及更新器构建 | 通过,约 25 秒 |
|
||||
| 最终前端生产构建 | 通过,约 168 秒 |
|
||||
|
||||
隔离环境中的旧实现会失败:前端将旧版 `no_update` 误判成功;更新器六个新增发布源场景不能满足预期。相同失败用例在修复后通过。HTTP 发布源测试使用受控 Docker 替身,真实容器替换另外通过测试站验收;没有用源码断言或构建替代运行验收。
|
||||
|
||||
## 证据与测试环境维护
|
||||
|
||||
完整本地证据目录:`/Users/wangbo/.codex/visualizations/2026/10/07/01a11505-cae4-76e0-9ba9-235f6962b2b4/system-update-diagnosis`。
|
||||
|
||||
- `acceptance-summary.json`、`browser-upgrade-acceptance.json`、`browser-acceptance.json`:实际请求、任务、版本及异常场景。
|
||||
- `acceptance-update-success.png`、`acceptance-final-card.png`、`acceptance-feed-unavailable.png`、`acceptance-older-release-blocked.png`:真实页面结果。
|
||||
- `config-before-browser.json`、`config-after-browser.json`、`mount-verification.json`、`final-verification.json`:配置、健康及任务持久化核对。Docker 返回的挂载数组顺序不稳定,已按内容核对路径和权限一致。
|
||||
- `*-tests.log`、`*-build*.log`、`updater-before-tests.log`、`frontend-before-tests.log`:定向检查与修复前失败记录。
|
||||
|
||||
测试环境保留内部测试发布源容器 `easyai-update-release-feed`,无新增宿主机端口。任务专用配置位于 `/root/easyai/data/update-acceptance-20261007`,其中 `accepted.json` 和更新后的 `baseline.json` 指向实际验收镜像;`rollback.json` 保留原镜像引用。维护测试站时须使用原 Compose、原 legacy override 与该目录下 `accepted.json` 的组合,不能将该测试清单误发布成正式清单。
|
||||
|
||||
## 未验证范围及正式发布条件
|
||||
|
||||
- 未发布生产,未执行全站业务回归或全量质量门禁;健康检查和更新入口验收不能代表全部业务兼容性。
|
||||
- 定时自动更新实际到点执行未验收,调度逻辑已做定向测试;自动更新设置保持原值。
|
||||
- 已测试持久化快照恢复,并实际重启验证已完成任务;没有在真实容器替换过程中故意中断 updater。旧 updater 未带清单快照的在途任务跨版本迁移尚未验收,升级更新器前应先确认没有执行中的更新任务。
|
||||
- 正式发布时须一起交付 updater 1.2.0、后端和前端,并在镜像完整发布后同步正式清单的版本、发布标识、不可变摘要及大小。只改官方站版本或只推送 `latest` 仍无法代表客户可安装的新版本。
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"channel": "stable",
|
||||
"releaseId": "update-source-acceptance-20261007-r3",
|
||||
"productVersion": "3.24.0.20261007",
|
||||
"platform": "linux/amd64",
|
||||
"publishedAt": "2026-10-07T06:45:45.630545+00:00",
|
||||
"managedUpdateAllowed": true,
|
||||
"scheduledUpdateAllowed": true,
|
||||
"rollbackSafe": true,
|
||||
"minimumUpdaterVersion": "1.2.0",
|
||||
"minimumDeploymentSchemaVersion": 2,
|
||||
"requiredFreeBytes": 8589934592,
|
||||
"components": [
|
||||
{
|
||||
"key": "server-main",
|
||||
"image": "registry.cn-shanghai.aliyuncs.com/comfy-ai/comfy-server",
|
||||
"digest": "sha256:b334362777cfc2a1064a264cf10dac2766b32674627d98775be8a2d9ceb7da5b",
|
||||
"imageSizeBytes": 2747248955,
|
||||
"buildId": "test-source-20261007"
|
||||
},
|
||||
{
|
||||
"key": "ws-gateway",
|
||||
"image": "registry.cn-shanghai.aliyuncs.com/easyaigc/wsgateway",
|
||||
"digest": "sha256:a2b5fb67384c7f0eca7c7a768b5deacb4c0d2195dfbeb6e16ef40d6dc097bf25",
|
||||
"imageSizeBytes": 1982472178,
|
||||
"buildId": "test-source-20261007"
|
||||
},
|
||||
{
|
||||
"key": "web",
|
||||
"image": "registry.cn-shanghai.aliyuncs.com/comfy-ai/one-ai",
|
||||
"digest": "sha256:9ea88a929e743dcba2c7a38402c913d011af99b3ccc63a9cf8b4f97e4abda4d5",
|
||||
"imageSizeBytes": 886998757,
|
||||
"buildId": "test-source-20261007"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user