feat: backend native executor implement

This commit is contained in:
2026-08-16 01:27:30 +08:00
parent fbce91d797
commit 390e0565d3
44 changed files with 6430 additions and 31 deletions
+140 -7
View File
@@ -36,6 +36,18 @@ CI 构建一次镜像
native 过渡链路中,backend 保持同一次 CI 生成的 JAR 及其 SHA-256。frontend 保持同一次 CI 生成的源归档及其 SHA-256;当前 repack 会把该归档展开到客户 ZIP 的 `dist/`,因此客户侧最终以 manifest 逐文件声明的 `dist/...` 路径、长度和 SHA-256 为安装身份。
开发环境 backend 支持显式直传 JAR,不要求 Jenkins 生成 repack ZIP
```text
yms-daemon update --service backend --native-jar /home/yms/tmp/<完整JAR文件名>.jar
```
`--native-jar` 与 repack 的 `-f` 互斥。CLI 通过 Unix Socket 的 `inputType` 精确传递 `native-jar``repack-zip`,daemon 不通过扩展名推断输入类型。直传 JAR 的文件名按不透明字符串处理,不解析版本;daemon 完整读取 JAR、计算 SHA-256,并以 `direct/<SHA-256前12位>/<原始JAR文件名>` 作为 `/home/yms/lib/releases` 下的不可变相对路径。目录名精确截取完整小写十六进制 SHA-256 的前 12 位,事务幂等和内容校验仍使用完整 SHA-256,目录冲突时拒绝覆盖。
本地 Unix Socket 的 update 响应采用连续 JSON 消息:过程消息的 `kind` 精确为 `progress`,最终消息的 `kind` 精确为 `result`。CLI 普通模式实时输出校验、事务、槽位准备、systemd 启动、Actuator 健康检查、gateway 切流、drain、旧 unit 停止和提交阶段。精确参数 `--quite` 关闭正常过程与成功结果输出,错误仍写入 stderr,退出码语义不变;CLI 断开不取消服务端持久化事务。
backend native 的零停机重启命令精确为 `yms-daemon restart --service backend`,并支持 `--quite`。restart 读取当前兼容 JAR 指向的精确 release,创建独立事务,将同一 release 绑定到非活动槽,启动、健康检查、切流、drain 并停止旧槽;每次新调用都会执行一次真实槽位轮转,不使用历史 update 的内容幂等键。存在未完成 restart 事务时,同一命令恢复该事务;存在其他未完成事务时拒绝新建 restart。
## 2. 已确认的精确事实
### 2.1 仓库与组件
@@ -96,6 +108,10 @@ docker pull --platform <platform> <imageRef>
docker image save --platform <platform> ...
```
当前镜像 tag 的实际示例为 `20260814-093609-d7ed70f0-v1.1.8.1`,末尾 `v1.1.8.1` 可以不存在。daemon 将完整 tag 作为不透明字符串透传,不拆分日期、提交号或业务版本,也不使用 tag 判定镜像身份;镜像身份仍以本次更新明确给出的 digest 为准。
当前 `archive_artifact` 已经分别保存 digest ref 形式的 `imageRef` 和原始 `imageTag`,但 repack 的 `artifact-selection.json` 只输出 `imageRef`,未输出 `imageTag`。最终 repack 必须利用这两个已保存值生成完整 tagged image ref,以该完整引用执行 `docker image save`,并在 daemon manifest 中直接给出该引用和独立的 `imageDigest`;daemon 不负责拼接仓库名和 tag,也不得从其中一个值推导另一个值。
### 2.4 当前前端镜像
`YMSwellClient/scripts/deploy/Dockerfile` 已经执行:
@@ -184,6 +200,86 @@ YMS_FLYWAY_ENABLED=true
当前返回值没有整包 SHA-256。PC 下载完成后的强身份校验需要发布中台扩展整包 SHA-256,并与包列表或下载令牌响应一同返回;精确字段名在修改 `deploy` 前同时冻结。
### 2.8 当前客户 `/home/yms` 目录
2026-08-15 提供的客户服务器目录结果确认以下现状:
- `/home/yms/bin` 保存现有运维和更新脚本。
- `/home/yms/bin/env` 保存现有环境配置。
- `/home/yms/lib/releases` 保存 backend 历史 JAR。
- `/home/yms/lib/glory-soft-yms.jar` 是指向 `/home/yms/lib/releases` 中当前 JAR 的软链接。
- `/home/yms/client-releases` 保存 frontend 历史版本目录。
- `/home/yms/client` 是指向 `/home/yms/client-releases` 中当前 frontend 版本的软链接。
- `/home/yms/tmp` 已存在。
- `/home/yms/deploy/deploy-sync.sh` 仍然存在于现场。
这表明当前 native backend 和 frontend 已经具有“版本存储目录 + 当前入口软链接”的目录基础。该事实不直接冻结 native 双槽的 systemd unit、槽位名称和切流方式;零停机执行器仍须保证两个并行实例分别引用明确版本,不能在运行期间只依赖一个全局当前软链接表达两个实例状态。
native backend 的 systemd 模板名称已冻结为:
```text
/etc/systemd/system/yms-backend@.service
```
模板实例标识固定使用端口号,完整 unit 名称为:
```text
yms-backend@8080.service
yms-backend@8081.service
```
两个并行实例对应的槽位 JAR 软链接路径固定为:
```text
/home/yms/lib/glory-soft-yms-8080.jar
/home/yms/lib/glory-soft-yms-8081.jar
```
当前人工运维兼容入口固定保留 `/home/yms/lib/glory-soft-yms.jar`,并指向当前接流版本。daemon 不把该单一兼容入口同时作为两个槽位的版本身份。
当前客户服务器执行 `command -v systemctl` 的精确输出为 `/bin/systemctl`,该现场的 daemon 配置使用此绝对路径。其他服务器如果精确输出 `/usr/bin/systemctl`,由该服务器的本机配置明确记录 `/usr/bin/systemctl`;daemon 不在两个路径之间自行选择。
native backend 模板使用 `Type=simple`,不再调用 `yms-service.sh` 或其他启动脚本。冻结后的 `ExecStart` 为:
```text
/usr/bin/java -Xms1024m -Xmx10240m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/yms/dump -XX:ErrorFile=/home/yms/log/hs_err_pid%p.log -XX:+ExitOnOutOfMemoryError -jar /home/yms/lib/glory-soft-yms-%i.jar --server.port=%i
```
模板继续读取现有 `/home/yms/bin/env/yms.env`,工作目录为 `/home/yms/lib`stdout 和 stderr 进入 journal。模板中 systemd 对字面量 `%p` 需要写为 `%%p`JVM 最终接收到的参数仍为 `-XX:ErrorFile=/home/yms/log/hs_err_pid%p.log``TimeoutStopSec` 固定为 `150min`,与应用 `spring.lifecycle.timeout-per-shutdown-phase` 的默认 `150m` 对齐,不能沿用旧 unit 的 `30` 秒强制停止上限。
RPM 安装阶段创建 `/home/yms/dump`,属主和用户组固定为 `root:root`,权限固定为 `0755`。RPM 同时安装 tmpfiles 配置,并在安装事务中执行对应的目录创建;unit 不通过 `ExecStartPre` 临时创建目录。
### 2.9 第一阶段本机配置
daemon 本机配置文件固定为 `/etc/yms-daemon/yms-daemon.toml`。第一阶段已经冻结并实现 backend native 配置:
```toml
[backend]
type = "native"
release_dir = "/home/yms/lib/releases"
active_jar = "/home/yms/lib/glory-soft-yms.jar"
systemctl_path = "/bin/systemctl"
[backend.slot.8080]
unit = "yms-backend@8080.service"
jar = "/home/yms/lib/glory-soft-yms-8080.jar"
health_endpoint = "http://127.0.0.1:8080/yms/actuator/health"
[backend.slot.8081]
unit = "yms-backend@8081.service"
jar = "/home/yms/lib/glory-soft-yms-8081.jar"
health_endpoint = "http://127.0.0.1:8081/yms/actuator/health"
```
字段规则固定为:
- table 和 key 必须与上例逐字一致;大小写不同、未知字段、未知槽位和重复字段全部拒绝。
- `backend.type` 第一阶段只接受精确值 `native`container 配置在其完整 table 和 key 冻结后扩展,daemon 不根据现场状态自动选择。
- `release_dir``active_jar`、两个槽位的 `unit``jar``health_endpoint` 必须与上例完全相同。
- `systemctl_path` 必须由现场明确填写干净的绝对路径;当前现场填写 `/bin/systemctl`,精确检查结果为 `/usr/bin/systemctl` 的服务器填写 `/usr/bin/systemctl`
- loader 不补默认值,不改写路径,不改写大小写,不根据端口拼接 unit、JAR 路径或健康地址。
- RPM 安装示例文件位于 `packaging/etc/yms-daemon/yms-daemon.toml`;RPM spec 接入时将其安装到固定路径。
## 3. 总体架构
daemon 内部先选择服务器已经明确登记的组件执行器,再进入相同的事务、健康检查、切流和回滚流程:
@@ -287,6 +383,20 @@ Jenkins / deploy
组件准备、健康检查、切流、验证和回滚实现必须完全复用。开发环境必须持续覆盖 native 和 container 两条执行路径,不能只验证目标态 Docker executor。
### 3.4 YMS 在线上传兼容入口
现有 YMS 在线更新入口继续接收更新 ZIP,但不再解压更新包、查找 `deploy-sync.sh` 或由 JVM 执行更新。YMS 与宿主机 daemon 的更新包共享收件目录固定为:
```text
/home/yms/tmp
```
native 部署下,YMS 和 daemon 直接使用该宿主机目录。Docker standalone 部署下,backend blue/green 容器把宿主机 `/home/yms/tmp` bind mount 到容器内同名绝对路径,避免容器路径与宿主机路径转换。backend blue/green 容器明确以 root 用户运行,不依赖基础镜像的默认用户。
该目录只承担更新包交接,不保存 daemon 的 SQLite、事务内部文件或日志。YMS 完成文件写入并持久化后再发布完整文件;daemon 只接管已经完整发布的普通文件,并在接管成功后创建或关联更新事务。daemon 不通过目录监听自动执行更新,更新仍须经过显式认证和事务提交。
`/home/yms/tmp` 的属主和用户组固定为 `root:root`,权限固定为 `0755`。内部子目录、临时文件命名、容量限制、保留时间和异常文件清理规则仍需冻结。现有客户环境已经使用 `/home/yms/tmp`,实施时必须先核对现存文件用途,不能清空或覆盖既有文件。
## 4. 进程和命令入口
RPM 全局安装单一二进制:
@@ -378,6 +488,12 @@ yms-daemon update --service nodeSsr -f xxx.zip
开发环境由 Jenkins 构建步骤调用同一 `yms-daemon` CLI 快速触发更新。在线更新的具体参数尚未冻结;它必须进入同一个内部更新请求模型,但不强制先生成包含完整镜像层的 ZIP。
第一阶段 backend native 命令已经实现。CLI 把相对 `-f` 路径解析为当前工作目录下的绝对路径,只通过 `/run/yms-daemon/yms-daemon.sock` 提交给 `serve`;CLI 进程不直接读取 SQLite、不调用 systemd、不改写 Nginx。当前只接受精确值 `--service backend`,其他 service 在对应执行器完成前明确拒绝。
当前 repack 兼容层读取 ZIP 根目录的 `artifact-selection.json`,并按 deploy 当前实际字段 `backendArtifacts[].artifactKind/type/selectedType/fileName/sha256` 选择唯一 native backend JAR。ZIP 条目、JSON table/key、重复字段、大小写变化、路径逃逸和符号链接全部严格拒绝。当前 deploy manifest 尚无文件长度字段,daemon 使用 ZIP 条目的实际解压长度并用 manifest SHA-256 校验内容;manifest 增加明确长度字段后再升级协议。
第一次从旧 native unit 迁移时,daemon 根据 Nginx managed upstream 的实际接流端口和 systemd 实际状态,在对应的旧 unit 与新模板 unit 中要求恰好一个正在运行。当前映射固定为 8080 对应 `yms.service`、8081 对应 `ymsback.service`;现场第一次更新从旧 `ymsback.service@8081` 切换到 `yms-backend@8080.service`,成功后停止旧 unit。后续只在两个 `yms-backend@.service` 实例之间轮换。
### 4.4 `rollback`
```bash
@@ -918,6 +1034,7 @@ daemon 不通过文件名、扩展名、目录名称或正则表达式推断组
- `native` 使用 `files`,每个文件都必须声明 `path``size``sha256`
- native frontend 的 `files` 必须枚举 repack 后客户 ZIP 的 `dist/` 下每个普通文件;示例中的两个文件不代表完整文件集合。
- `container` 使用 `artifacts`,并声明 `platform``path``size``sha256``imageRef``imageDigest`
- `imageRef` 必须是 repack 明确生成的完整 tagged image ref,其中 tag 使用发布归档保存的原始值,允许不包含末尾业务版本段;daemon 不拼接该引用。
- container `platform` 必须与服务器精确平台相同。
- `path` 必须是 ZIP 内明确的相对路径。
- `path` 禁止绝对路径、`..`、空路径、重复路径和符号链接。
@@ -1490,7 +1607,7 @@ Kubernetes executor 不在每个业务 Pod 中运行 daemon。它负责:
- [x] 确认第一版服务端 SQLite 使用单连接、rollback journal 和 `synchronous=EXTRA`
- [ ] 冻结 frontend 旧资源兼容方案。
- [ ] 冻结本机组件运行类型配置 schema。
- [ ] 冻结 native backend 双实例 systemd 模板和版本目录
- [x] 冻结 native backend 模板 `/etc/systemd/system/yms-backend@.service`、实例 `yms-backend@8080.service``yms-backend@8081.service`、release 目录和两个槽位 JAR 路径
- [ ] 冻结 native frontend 双版本目录。
- [ ] 冻结 backend 蓝绿期间 Flyway、任务、消费者和 Session 约束。
- [ ] 冻结 gateway 配置目录、容器命名、network 和 labels。
@@ -1506,13 +1623,18 @@ Kubernetes executor 不在每个业务 Pod 中运行 daemon。它负责:
### 阶段二:开发环境最小闭环
- [ ] 实现 `serve``update``rollback` 和查询命令。
- [x] 实现 `serve`本地 Unix Socket 和 backend native `update` 命令。
- [x] 实现 backend native 的 repack ZIP `-f` 与开发环境直传 JAR `--native-jar` 两种显式互斥输入,并让二者进入同一事务执行链。
- [x] 实现 update Unix Socket 流式过程消息和 CLI 实时阶段输出,并提供精确静默参数 `--quite`
- [x] 实现 `yms-daemon restart --service backend` 的同版本零停机槽位轮转、流式进度和未完成事务恢复。
- [ ] 实现 `rollback`、查询命令以及 frontend、nodeSsr、all 更新入口。
- [ ] 实现本地 Unix Socket、Linux advisory `flock` 和进程内更新互斥。
- [ ] 实现服务端禁用 CGO 的 SQLite 驱动、单连接配置、rollback journal 和 `synchronous=EXTRA` 启动校验。
- [ ] 实现服务端事务、步骤、幂等和 WSS 事件的 SQLite 持久化约束。
- [ ] 实现临时写入、文件 flush、摘要校验、原子 rename 和父目录 flush。
- [ ] 实现统一组件执行器和 gateway controller 接口。
- [ ] 实现本机显式部署配置及 `type` 严格校验
- [x] 实现 backend native 本机 TOML 配置、逐字键树校验、`type` 严格校验和 8080/8081 槽位查询
- [ ] 在精确 schema 冻结后扩展 frontend、nodeSsr 和 container 本机配置。
- [ ] 实现 Docker Engine API 客户端和兼容性检查。
- [ ] 实现镜像 digest 校验。
- [ ] 实现 blue/green 统一生命周期。
@@ -1524,7 +1646,7 @@ Kubernetes executor 不在每个业务 Pod 中运行 daemon。它负责:
### 阶段三:native/hybrid 与应用兼容改造
- [ ] 实现 backend native systemd executor。
- [x] 实现 backend native systemd executor 内核:不可变 JAR、非活动槽位绑定、systemd 启停、Actuator 健康检查、事务恢复和切流前补偿;实际双实例 unit 与槽位路径仍由冻结后的本机配置接入
- [ ] 实现 frontend native directory executor。
- [ ] 实现 host Nginx gateway controller。
- [ ] 实现 native/container 混合 `all` 事务。
@@ -1619,6 +1741,17 @@ Kubernetes executor 不在每个业务 Pod 中运行 daemon。它负责:
36. SQLite 状态提交和外部操作分步执行,SQLite 写事务中禁止等待 systemd、Docker、gateway、健康检查或 drain。
37. gateway 配置、systemd、Docker 和本地文件是恢复时的实际状态依据;SQLite 记录执行意图和历史,不能取代现场核对。
38. 服务端 SQLite 损坏时保留当前业务运行状态并拒绝新更新,不能用空数据库自动覆盖。
39. YMS 与宿主机 daemon 的更新包共享收件目录固定为 `/home/yms/tmp`,属主和用户组固定为 `root:root`,权限固定为 `0755`Docker backend blue/green 容器使用同名绝对路径 bind mount,并明确以 root 用户运行;该目录不保存 daemon 的 SQLite、事务内部文件或日志。
40. native backend 模板固定为 `/etc/systemd/system/yms-backend@.service`,实例标识固定为 `8080``8081`,完整 unit 名称固定为 `yms-backend@8080.service``yms-backend@8081.service`,槽位 JAR 软链接固定为 `/home/yms/lib/glory-soft-yms-8080.jar``/home/yms/lib/glory-soft-yms-8081.jar`;当前接流版本的人工运维兼容入口固定保留 `/home/yms/lib/glory-soft-yms.jar`
41. 当前客户服务器的 systemctl 可执行文件绝对路径为 `/bin/systemctl`;其他服务器使用其本机明确配置的绝对路径,daemon 不自行尝试替代路径。
42. native backend unit 使用 `Type=simple` 直接执行 `/usr/bin/java`,不调用启动脚本;堆转储目录固定为 `/home/yms/dump`,其属主和用户组固定为 `root:root`、权限固定为 `0755` 并由 RPM 创建;JVM 错误日志固定为 `/home/yms/log/hs_err_pid%p.log`stdout 和 stderr 进入 journal`TimeoutStopSec` 固定为 `150min`
43. daemon 本机配置文件固定为 `/etc/yms-daemon/yms-daemon.toml`,格式固定为 TOML;使用纯 Go 的 `github.com/pelletier/go-toml/v2` 严格解码,并在解码前逐字校验完整键树,拒绝大小写变化、未知字段、未知槽位和重复字段,保持 `CGO_ENABLED=0`
44. 第一阶段 backend native 配置精确使用 `[backend]``[backend.slot.8080]``[backend.slot.8081]`;key、必填规则和精确值以 2.9 节为准,loader 不提供配置默认值。
45. daemon 服务端路径固定为:Unix Socket `/run/yms-daemon/yms-daemon.sock`、进程锁 `/run/yms-daemon/yms-daemon.lock`、SQLite `/var/lib/yms-daemon/yms-daemon.db`、事务工作目录 `/var/lib/yms-daemon/work`、本地日志 `/var/log/yms-daemon/yms-daemon.log`
46. 当前 native 过渡 gateway 固定管理 `/etc/nginx/nginx.conf` 中唯一一组 `# yms-update managed upstream begin/end` 标记,Nginx 可执行文件固定为 `/usr/sbin/nginx`systemd unit 固定为 `nginx.service`;配置完整原子替换后执行 `/usr/sbin/nginx -t``systemctl reload`,任一步失败恢复替换前完整配置。
47. 第一次旧 unit 运行态迁移固定映射为 8080=`yms.service`、8081=`ymsback.service`;Nginx 当前接流端口对应的旧 unit 和 `yms-backend@<port>.service` 必须恰好一个处于运行态,否则拒绝更新。
48. backend native 更新输入分为 `repack-zip``native-jar`;前者由 `-f` 提交,后者由 `--native-jar` 提交,二者互斥且不通过扩展名推断。直传 JAR 使用完整文件 SHA-256 作为幂等身份,在 `/home/yms/lib/releases` 下保存为 `direct/<SHA-256前12位>/<原始JAR文件名>`;目录名精确取完整小写十六进制 SHA-256 的前 12 位,完整 SHA-256 继续用于幂等和内容校验,目录冲突时拒绝覆盖。直传 JAR 复用与 repack ZIP 相同的双槽、健康检查、切流、提交和补偿流程。
49. backend native 零停机重启命令固定为 `yms-daemon restart --service backend`,可附加 `--quite`。restart 使用当前接流 release 创建独立事务并轮转到非活动槽,不调用 `systemctl restart`,也不重新上传 JAR;当前 JAR 已在 release 目录时直接复用,兼容入口仍是普通文件时按内容身份导入 release 目录。未完成 restart 由同一命令恢复,其他未完成事务阻止新 restart。
## 18. 当前必须继续研讨的精确问题
@@ -1626,14 +1759,14 @@ Kubernetes executor 不在每个业务 Pod 中运行 daemon。它负责:
2. frontend 旧资源采用版本命名空间、上一槽 fallback 还是组合方案。
3. backend 新旧版本并行时 Flyway 的单次执行方式。
4. backend 定时任务、PowerJob、消费者、Session 和缓存的实际行为。
5. 本机组件运行类型配置的精确 schema 和固定路径
6. native backend 双实例 systemd unit、JAR 版本目录和端口的精确值
5. `/etc/yms-daemon/yms-daemon.toml` 中 frontend、nodeSsr 和 container 的精确 table、key 和必填规则
6. native backend 从现有 `/home/yms/bin/env/yms.env` 迁移到 `/home/yms/conf` 外挂配置的时机和兼容规则
7. native frontend 双版本目录的精确值。
8. gateway 容器镜像、配置目录、Docker network、blue/green 容器名和 label 的精确值。
9. host Nginx 和 OpenResty 配置 reload 成功的确认方式。
10. SSE 和上传的最大 drain 时间与强制结束规则。
11. 三个组件的旧槽保留时间、磁盘清理和镜像清理规则。
12. daemon OS 锁文件、服务端 SQLite、事务文件、上传暂存和日志的固定路径与权限
12. `/home/yms/tmp` 的内部子目录、容量限制、保留时间和异常文件清理规则
13. Docker Engine 支持的最低版本和 API 版本范围。
14. RPM 是安装 Docker Engine、声明依赖还是只做运行前检查。
15. 现有 native 到 daemon 管理 native 双槽的迁移命令、路径、权限和失败恢复流程。