146 lines
34 KiB
Markdown
146 lines
34 KiB
Markdown
# 工作日志 · 2026-09(第 20 片)
|
||||
|
|
|
|||
|
|
> ⚠️ **本目录日志已按【月】分片**(2026-09-15 用户定,单文件 ≤50 KB):同月续片 `2026-09.md` / `2026-09-下.md` / `2026-09-下2.md` / `2026-09-下3.md` / `2026-09-下4.md` / `2026-09-下5.md` / `2026-09-下6.md` / `2026-09-下7.md` / `2026-09-下8.md` / `2026-09-下9.md` / `2026-09-下10.md` / `2026-09-下11.md` / `2026-09-下12.md` / `2026-09-下13.md` / `2026-09-下14.md` / `2026-09-下15.md` / `2026-09-下16.md` / `2026-09-下17.md` / `2026-09-下18.md` / `2026-09-下19.md` / `2026-09-下20.md` / `2026-09-下21.md` / `2026-09-下22.md` / `2026-09-下23.md`
|
|||
|
|
> **写入约定**:一律 append 到**当月最后一片**;该片超 50 KB ⇒ 新建 `2026-09-下N.md`;⛔ **不要再按日新建 `2026-09-DD.md`**。
|
|||
|
|
> **覆盖来源**:2026-09-14.md
|
|||
|
|
> **上一片**:`2026-09-下18.md` **下一片**:`2026-09-下20.md`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
## 13:2x–13:5x · 「重不重」→ 官方推荐库里有现成的:**已装上轻量文件预览插件**(不重新开发)
|
|||
|
|
|
|||
|
|
**用户原话**:「univer 太重了,有没有别的方式做个插件让用户点击对话中的文件,在会话栏旁边的窗口中打开展示」→ 追加:「**需要查官方推荐库是否有类似插件,是重新开发还是改造**」。
|
|||
|
|
|
|||
|
|
**侦察(只读,全部实测)**:
|
|||
|
|
- 「官方推荐库」入口 = `Awesome-DeepSeek-Harness-Plugins`(数据源 cordis.run 插件索引,331 个)+ npm `keywords:dsh-plugin`。
|
|||
|
|
- dsh 客户端**没有官方 viewer 注册点**;但布局里有 `details` 列(会话栏旁、可拖宽)与 `shell.overlay` 浮层;官方 `dsh-client-ui-deliverables` 已把「本轮产出文件」渲染成**可点击 chips**(点击走 chat 的 `openFile`)。
|
|||
|
|
- 平台已有 `GET /api/fs/download?path=`(requireAuth + 限定调用者根内)且**跨域放行**(`access-control-allow-origin: https://<user>.alotbuy.com` + `allow-credentials: true`,实测 200/536 KB)⇒ 纯客户端方案可行。
|
|||
|
|
|
|||
|
|
**候选体检(判据 = 依赖的官方包**是否存在/是否被角色补丁禁** + 版本范围 + 体积)**:
|
|||
|
|
| 候选 | 结论 |
|
|||
|
|
|---|---|
|
|||
|
|
| ✅ **`@softspark/dsh-file-preview` v2.0.0(**65 KB tgz**)** | peer **精确锁 `0.1.2-rc.1`(=我们的 dsh 版本)**;inject 4 个(locale/api-remotes/api-session-controller/ui-renderer)**本平台全有且未被禁**;`apply`+`inject` 合规;读文件走**官方 host Remote seam**(不碰平台 API);patch 自述「**包住 `remote.session.openWorkspacePath` 接管对话里的文件打开手势**,卸载即恢复原生」 |
|
|||
|
|
| ❌ `dsh-file-viewer` v0.3.5 | inject 含 **`@deepseek-ai/dsh-client-runtime`(本平台不存在)** ⇒ 会**静默挂死** |
|
|||
|
|
| ❌ `@deepseek-ai/dsh-client-ui-sidebar-documentpreview` | 面向 **0.1.5** 系(依赖 `dsh-client-ui-sidebar-right`,我们只有 `-ui-sidebar`)⇒ 版本不匹配(R1 不升 dsh) |
|
|||
|
|
| ❌ `dsh-file-review` v0.8.1 | inject 含 **`ui-settings-plugins`(被平台角色补丁禁)** ⇒ 静默挂死 |
|
|||
|
|
| ❌ `dsh-at-file` / `@linxin666/...aionui-panel` | 同样注入不存在的 `dsh-client-runtime` |
|
|||
|
|
|
|||
|
|
**落地**:admin `POST /api/plugins/business` 导入候选池 → **`compat.level: ok`、`trustedOverride: false`**(预检全过,无需声明信任)→ `mine/apply` 启用 → 实例重启探活 `success`。
|
|||
|
|
**验证(L2 机制级)**:实装 `node_modules/@softspark/dsh-file-preview` ✓;**页面客户端插件 46 → 47**,`@softspark/dsh-file-preview` 行 inject **4 个全部可满足(缺 = 无)** ✓。
|
|||
|
|
**待 L5**:用户硬刷新后点对话里的文件,应弹只读预览弹窗(我这边渲染不了,需你实测)。
|
|||
|
|
**体积对比**:65 KB vs univer 42 MB ⇒ **约 1/650**;且**未动 univer 任何一处**(两者可并存)。
|
|||
|
|
|
|||
|
|
### 14:0x–14:2x · 「按你的建议处理」→ 逐条处置(含**否决我自己的一条**)
|
|||
|
|
- **新插件能力边界(读包内 client.js 实测)**:✅ 文本类(md/txt/json/yaml/csv/js/ts/py/sh/sql/html/css/xml)、图片(png/jpg/jpeg/gif/webp/svg)、PDF(kinds: text/html/svg/image/pdf);❌ xlsx/docx/pptx/.univer(命中 `unsupported`,另有 too large 上限)。host 半边 = 会话授权 + 只读工作区(workspace/path.resolve/base64)⇒ 权限收窄 ✓。
|
|||
|
|
- **三条建议处置**:① L5 归用户;② **不装 36 MB 的 Office 组合** —— 改走已有能力(`univer_import` 自 **0.2.28** 起 glibc 已通 ⇒ xlsx 导进 `.univer` 即在线看),"xlsx 能不能在会话里看"的**答案已从"不能"变成"可以"**;③ **⛔ 我自己否决了"停用 univer"** —— 它承载用户明说的核心用法(AI 生成 md/doc/excel + Univer 在线看),且 univer 网关**按需启动 + 空闲自停**(常驻成本主要是 42 MB 磁盘)⇒ 要停的前置 = 先把 `office-file-generation` 技能迁出 univer,等用户明确说停再做。
|
|||
|
|
- 落档:档案 89 追加「能力边界 + 处置表」;四件套 rc=0、双端一致(仍只剩别人那个 `dsh-opensource-release/SKILL.md`)。
|
|||
|
|
|
|||
|
|
### 17:0x–17:2x · 只读调研:K8S 部署能力 + 上千人能否集群(**未改任何文件**)
|
|||
|
|
- **结论:k8s 是「代码内置的模式 B」,不是待开发项**。开关 = `DSHS_DEPLOY_MODE=local|k8s`(`src/config.ts:18/244`)+ `DSHS_DB_URL` 非空即切 Postgres(`src/db/index.ts:19`)。三处可切换点:`server.ts:260`(Spawner)/ `fs/provider.ts:32`(UserFs)/ `db/index.ts`(DB)。
|
|||
|
|
- **模块清单**(新增于本调研):`supervisor/k8s-spawner.ts`(每用户 Pod/Svc/NP/Secret/ConfigMap + watchdog Job + files Pod)、`supervisor/leader.ts`(Lease 抢主 + fencing)、`supervisor/reconcile.ts`(期望态收敛 + informer 崩溃观察)、`fs/k8s-user-fs.ts`、`db/pg.ts`、`tcp-bridge.ts`、`web/file-service.ts`、`deploy/*.yaml`(**11 个**,ACK 全套)、上游 `docs/k8s.md`(设计稿,**本机未 check out**)、`docs/k8s-deploy.md`(ACK/k3s 实跑踩坑,含 CNPG 3 实例 + Phase 3 联调)。
|
|||
|
|
- ⚠️ **本项目从未在生产用它**:档案 19 §C8 已标注「上游 k8s/PG/leader 代码在本部署未使用、未验证」;`03-路线图` 决策 3 = 1.8G/2 核 → **1–3 人**。
|
|||
|
|
- **local 模式不能集群(判据)**:实例态在 `LocalSpawner` 的内存 Map(mains/children/lastActive/熔断)、DB 是单文件 SQLite、每用户随机回环端口 + iptables portGuard(主机本地)、`ensure-*.cjs` 直接改主机上 profile 目录、**无实例归属记录** ⇒ 起两个 dshs 会重复拉起同一用户(无互斥/心跳)⇒ 多台只能是「独立孤岛」,不是集群。
|
|||
|
|
- **切到 k8s ≠ 改配置**(这是我们平台自研能力的真实缺口,逐条读码确认):① `landModels`(档案 87)用 `writeHomeFile` **直写宿主机 `$DSH_HOME`** + chown ⇒ 控制面在 k8s 下**没有用户卷**(`spawner.ts` 头注释明说)⇒ 模型设置层失效;② `ensurePickerProfile` / `ensure-role-profile-patch.cjs` 同因失效;③ `restartAndProbe`(档案 34 插件探活)在 K8sSpawner 里是 `ok:true` **空实现**;④ `breakerInfo`/`quotaInfo`(档案 78/84 观测面)只有 LocalSpawner 有 ⇒ 实例内配额显示/熔断态无数据源;⑤ `touch`/`launchToken` no-op;⑥ 备份/清理(ws-cleanup·session-gc·purge-trash·backup.sh)全是主机侧脚本。
|
|||
|
|
- **上千人容量算式**(未写进文档,供后续立项):每活跃用户 request ≈ 0.55 vCPU / 1.15 GiB(DSH Pod 500m/1Gi + files Pod 50m/128Mi)⇒ **1000 并发 ≈ 550 vCPU / 1.15 TiB ⇒ 8C16G 节点约 11–12 个用户/节点 ≈ 85–90 节点**;30% 并发(300 活跃)≈ 25 节点。对象数每用户最多 8 个 ⇒ 1000 用户 ~8000 对象,**现 `deploy/06-quota.yaml` 的 `count/pods: 60` 必须换成分片命名空间**。
|
|||
|
|
- **k8s 路径上 1000 人级待补洞**:reconcile 每 10s **全量 list pods + 逐用户串行 ensure/reap**(O(N) API 调用);`endpointFor` **每请求 readNamespacedPod**(无 informer 缓存);leader 单点(需按 userId hash 分片);files Pod **创建后无回收**(只在 DSH Pod 侧有 idle reap);NAS RWX 单文件系统 shard;控制面副本数需按长连接数扩;Prometheus/Loki 仅方案未接入。
|
|||
|
|
- 参考落点:本机代码仓 `D:\github\dsh_shenxian`(注意其 `docs/` = 上游 7 篇,与本项目文档库是两回事,见档案 19 §C10)。
|
|||
|
|
|
|||
|
|
#### ⚠️ 17:1x 自我更正(上一条里我把 DB 错列为限制,用户当场纠正)
|
|||
|
|
- **用户原话口径**:「可以改为连接数据库集群,**数据库不是限制**」⇒ 成立。`DSHS_DB_URL` 非空即切 PG(`db/index.ts:19`),`db/adapter.ts` 头注释已声明「routes depend only on this — never on better-sqlite3 or pg directly」⇒ **DB 层早有抽象,是配置项不是天花板**。
|
|||
|
|
- **更关键的一处实测**:`dsh_instances`(实例归属/期望态表)**只有 k8s 路径在写** —— 全仓 grep:`db.upsertInstance`/`deleteInstance`/`findUserInstance`/`listInstancesByRole` 的调用方**只有** `k8s-spawner.ts`(168/176/183/285) 与 `reconcile.ts`;**`orchestrator.ts` 对 `this.db.` 零引用**(`LocalSpawner` 构造函数根本不收 db)⇒ **换库单独做 = 零收益**(库共享了,但没人往里写归属)。
|
|||
|
|
- **修正后的真限制(4 条)**:① 实例状态在进程内存(`mains`/`children`/`lastActive`/熔断态)② 回环端口 + iptables `portGuard`(owner-match 主机本地)③ bwrap + uid + systemd scope(主机本地)④ `ensure-*.cjs` 直改**主机上**的 profile 目录;**加**⑤ 归属表不写库。
|
|||
|
|
- **绿框(反而不是障碍的两项)**:DB 可切 PG 集群;uid = `baseUid + row_id` **由 DB 决定 ⇒ 跨机天然一致**(这是以后真要做分片的手里已有的好牌)。
|
|||
|
|
- **若硬要走「local + PG + 自制集群」**,需新建三层:归属/心跳(≈ k8s 的 dsh_instances + Lease,用主机语义重写 reconcile)、host→owner 路由层(现在是本机代理 + 单机 nginx)、共享存储(NAS/NFS,否则只能"分片固定"不能转移)⇒ 结论不变:**做出来是「分片集群」(固定归属 + 有界转移),拿不到 k8s 的任意调度/秒级重建,工作量却已接近重写一遍 reconcile+leader**。
|
|||
|
|
|
|||
|
|
#### 17:2x 用户提「manager/worker(1 台门户+状态,N 台承载实例)」→ **可行,且跨机面比预想的通**(实测核对,纠正上面"要新建三层"的过重表述)
|
|||
|
|
- ✅ **`Spawner` 接口就是为多后端留的口子**(`spawner.ts` 头注释原话:route layer depends only on this interface, so both backends coexist behind the same API)⇒ 加第 3 种 `RemoteSpawner` **不用动路由层**。
|
|||
|
|
- ✅ **代理层 TCP 目标与 Host 头是分开的**:`proxy.ts:299` `host: endpoint.host`(连接目标)vs `proxy.ts:108` `out.host = \`127.0.0.1:${port}\``(信任栅栏)⇒ **跨机代理不需要改信任逻辑**。
|
|||
|
|
- ✅ **"Host 头端口必须等于实例端口"这个坑不存在**(读官方源码定论):`deepseek-harness/packages/client/connection/src/api-request-trust.ts:103` = `isLoopbackHostname(hostUrl.hostname)` —— **loopback 判定只看主机名、不看端口**;Origin/Sec-Fetch 已被 `proxy.ts:30 STRIP_HEADERS` 剥掉(含 origin/referer/sec-fetch-*/x-forwarded-*)⇒ 转发端口 ≠ 实例端口照样过。**⇒ 跨机代理在协议层已经通了。**
|
|||
|
|
- ✅ **worker 侧暴露实例的原语已存在**:`dshs tcp-bridge <listen> <target>`(`src/tcp-bridge.ts` + `cli.ts:241`),就是 k8s sidecar 用的那条 ⇒ worker 上一条命令即可把 127.0.0.1:随机端口 暴露到内网。
|
|||
|
|
- ⚠️ **真障碍只剩三条(与"写不写库"无关)**:
|
|||
|
|
1. **worker 上"那只手"**(唯一结构性新增)—— (i) worker 也跑 dshs 连同一 PG(零 agent,但用户永绑一台、无跨机拉起)|(ii) 写 worker agent + `RemoteSpawner`(最贴 manager/worker,但新程序+新部署形态)|(iii) worker 跑单节点 k3s(= 退化成模式 B)。
|
|||
|
|
2. **权限扩大(触 R5)**:dsh **硬禁** `--host 0.0.0.0`(官方 `packages/bundle/web-app/src/startup.ts:74-76` 原文 "it would expose remote code execution to the network")⇒ 必须靠 tcp-bridge/nginx 暴露到内网 ⇒ **同 VPC 任意主机可直连实例端口**(只剩 dsh 自己的 launch-token/cookie 一层)⇒ 要 nft 只放行 manager IP + 桥只绑内网网卡,**必须出权限影响评估**。
|
|||
|
|
3. **文件面是第二个跨机面(易漏)**:`userFs` 现在直读本机 `dataRoot/users/<id>/ws` ⇒ manager 要读任意用户工作区 ⇒ 复用 `dshs file-service`(`fs/k8s-user-fs.ts` + `web/file-service.ts`,**k8s 版已有现成实现**)或工作区放共享存储。
|
|||
|
|
- ⚠️ **自动转移的前提 = 共享 home,且这是最贵一步**:DSH_HOME 含 sessions/skills/credentials/profile/node_modules ⇒ 必须共享存储;而 ① dsh **chokidar watch 整个 home**(已踩过的坑)→ NFS inotify 不可靠 ② 每用户 profile 里自己的 `node_modules`(数千小文件)→ NFS 冷启动退化。
|
|||
|
|
- **分层建议**:先「**静态分片、不做转移**」(**不需要归属表、不需要心跳** —— 写库只在要动态调度/转移时才有意义)→ 再「共享 home + 心跳租约」→ 要弹性/秒级重建直接用模式 B。
|
|||
|
|
- **自制 manager/worker 的真实优势(相对模式 B,值得记住)**:bwrap/uid/scope 隔离 **+ 我们全部自研能力(模型落地/角色补丁/picker/插件探活/配额观测/熔断)原样保留**,不用像模式 B 那样逐条重写。**判据**:能接受"worker 挂了其上用户短暂不可用"⇒ 自制更划算;必须自动转移+弹性 ⇒ 模式 B。
|
|||
|
|
- **待办(若用户要推进)**:可在 47.77.182.89 做 5 分钟实证 —— 起一个实例 + `dshs tcp-bridge` 暴露到内网 IP,再用带 `Host: 127.0.0.1:<非实例端口>` 的 curl 打 `/api/*`:**403 = 栅栏拒(理论被推翻)/401 = 栅栏已放行**(同时验证"栅栏不校验对端 IP"这一前提)。
|
|||
|
|
|
|||
|
|
#### 17:2x 用户明确意图 = **跨服务器迁移** + 「实例信息可放用户专属云存储」⇒ 结论修正:**NFS 顾虑大幅减弱**(读官方源码实证)
|
|||
|
|
- **⚠️ 更正我们长期的错误说法**:MEMORY.md 写的「dsh 用 chokidar **watch 整个 home**」**不准确**。实测全仓只有 **3 个 watcher,且无一个递归整 home**:
|
|||
|
|
1. `packages/credentials/credentials-local/src/index.ts:585` —— 监视**单个文件** `$DSH_HOME/.credentials.yaml`(`awaitWriteFinish` + debounceMs);
|
|||
|
|
2. `packages/settings/settings-file/src/index.ts:239` —— 同款,监视 settings **文件**;
|
|||
|
|
3. `packages/skill/skill-filesystem/src/index.ts:488` —— 监视 **skill 根**,`depth: 1`。
|
|||
|
|
⇒ 当年那次 root 600 备份文件引发的崩溃循环,**成因不能用"整 home 被 watch"解释**(结论待查,别再当已知事实引用)。**运维规则本身不变**:平台备份一律 `/opt/dsh/backups/`,绝不落用户 home。
|
|||
|
|
- ✅ **`usePolling` 官方逃生门已找到**:`skill-filesystem` 有显式配置 `watchUsePolling ?? false` + `watchPollIntervalMs`(`index.ts:610-619`)⇒ skill 层在 NFS 上**可开轮询**。credentials/settings 两个只暴露 `watch:boolean` + `debounceMs`(**无 usePolling**),但两者都在启动时 `loadInitial()` 必读(`credentials-local:580`)⇒ NFS 上最坏只损失**热重载**,重启即恢复(且我们的 `landModels` 是 **spawn 前写盘**,冷启动路径不受影响)。
|
|||
|
|
- ⇒ **「home 放共享存储」从"很危险"降级为"代价可控"**:真正剩下的未知项只有「NFS 上同机写、inotify 是否触发热重载」(**不再是阻断项**)+ 「每用户 profile 的 `node_modules`(数千小文件)冷启动慢」(可用分层规避:profile 由镜像提供 + 只把 sessions/skills/credentials/ws 放共享存储)。
|
|||
|
|
- **迁移协议四件必需(缺一不可,照抄 k8s 语义即可)**:① 租约 CAS(`UPDATE … SET holder=$me, epoch=epoch+1 WHERE user_id=? AND (holder IS NULL OR heartbeat_at < now()-ttl)`,affected=1 才算抢到 → 参照 `leader.ts:200-216` 的 replace+RV 乐观并发)② fencing(老 holder 可能只是网络慢,每次操作带 epoch;`k8s-spawner.ts:112 stampFencing` 可照抄)③ 优雅让出 drain(实例在写 sessions,必须 SIGTERM→等落盘→再迁;现为 SIGTERM→grace→SIGKILL,`docs/blueprint.md:40`)④ **单写者保证**(缺它则脑裂双写同一 home → 会话日志损坏,宁可不做自动迁移)。
|
|||
|
|
- **代理层在迁移上是优势**:`endpointFor` **每请求解析**(无状态)⇒ 归属切换**即时生效**,不需要任何粘性会话配置。
|
|||
|
|
- **建议的"用户专属云存储"正确用法**:**权威归属放 DB**(对象存储无原子 CAS、无跨用户扫描、时间戳判活不可靠);用户专属目录里可放一份**归属/实例清单快照**(如 `<user>/.platform/instance.json`)作为 **DB 的容灾重建源**,而**不是**权威源。
|
|||
|
|
|
|||
|
|
### 17:3x · 产出《集群化改造方案 Manager/Worker》草案(含连接保障与故障恢复)
|
|||
|
|
- **产物**:`E:\ProgramData\AI技能\aliyun-dsh-server\集群化改造方案_Manager-Worker_20260914.md`(11 节:架构 / 网络拓扑 / 数据模型 / 关键流程 / 实现清单 / P0-P2 问题点 / 备份 / 成本 / 待拍板 / 实测清单 / **连接保障与故障恢复**)。
|
|||
|
|
- **本机新查到的两条关键事实(写进方案)**:
|
|||
|
|
1. **`/etc/passwd` 是 bwrap 白名单里的必挂项**(`orchestrator.ts:640`,为 `os.userInfo()` / uid→name)⇒ 多机后每台 Worker 的 `/etc/passwd` 必须有该 uid 条目(或 `os.userInfo()` 不再被调用)⇒ **P2-1 待 5 分钟实测**(`setpriv --reuid <数字>` 是否无需建号)。
|
|||
|
|
2. **现状备份实测值**(`02-运维手册 §C.8`):`/opt/dsh/backup.sh` 每周日 05:00 全量 tar.gz + 60 天保留 + `.backup` 一致性快照;首跑 **8.7 MB / 6919 条目** ⇒ 现在全量无压力,**用户数上去后瓶颈在 `profiles/web/node_modules` 的数千小文件**。
|
|||
|
|
- **备份结论(回答用户"有没有更高效方式")**:**单靠文件备份不是最高效**。推荐**四层**:① 共享存储卷快照(NAS/ESSD,近零 CPU)② restic 增量去重 → 对象存储(异地)③ PG PITR(WAL 归档)④ 平台配置 tar(体积小,保持现状);**L2 只备「不可重建」子集** = `sessions/` + `credentials` + 用户 `skills/` + `ws/`,**排除 `profiles/web/node_modules`**(可由 `dsh.profile.bundles` 清单 + 镜像版本重建)⇒ 单用户体积从数百 MB 降到数 MB。
|
|||
|
|
- **连接保障设计(新增 §11)核心 6 条**:① 设计原则「**连接不是关键路径**」(权威在 PG + 数据在共享存储 ⇒ 断连不坏数据,最坏只是不可访问)② 单向拨入(Worker **不反向连 Manager、不连 DB**,控制口是唯一被拨入口)③ **半开连接防护**(复用 `proxy.ts:450-455` 的 `agent.destroy()` + 新 Agent + 短连接重试;跨机后 NAT 静默丢空闲连接会让 2026-09-09 那类挂起更严重)④ **幂等键**(`operation_id` = epoch + 序号;Worker 按 userId 幂等,与 `AlreadyRunningError` 对齐)⑤ agent **只接受白名单动作**(否则 Worker 沦陷 = 全集群沦陷)⑥ **心跳兼任 fencing 广播**(Manager 在心跳响应下发 `expected_epoch`,Worker 发现自己过期就**自杀 self-fencing** ⇒ 防双写最后一道防线)。
|
|||
|
|
- **恢复矩阵**:10 个场景(控制超时 / 心跳失败未过 TTL(只告警不接管)/ 超 TTL 接管(用户中断 **30–60 s**)/ 计划内先迁后停 / agent 崩后对账 / Manager 挂 1 台(无感)/ **Manager 全挂(门户中断但实例仍在跑)** / 实例崩(Worker 自愈,Manager 不参与)/ **PG 不可用(拒新冷启动 + 归属缓存 60 s 保在线用户)** / 脑裂(fencing + 自愈))。
|
|||
|
|
- **对账(reconcile)改进点**:控制主 30 s 一次,**`GET /instances` 一次拿回整机列表**,而非 k8s 版 `reconcile.ts` 逐用户 O(N) 调用;**一期自动接管默认关闭**(告警 + 人工确认),与 R9 教训一致。
|
|||
|
|
- **护栏复用**:`src/supervisor/firewall.ts` 的 `createPortGuard()`(iptables OUTPUT owner-match)**在 Worker 上语义不变、零改动复用**;控制口(9000)与代理口(9001-9999)分离。
|
|||
|
|
- ⚠️ 方案是**草案放项目根**(未入文档库):`档案只增不改` ⇒ 定稿前不宜冻结为正式档案;定稿时需抢全局执行锁 + 原子占号。
|
|||
|
|
|
|||
|
|
### 17:3x · 三条决策已定 + 补 §12(存储选型)/ §13(PG 承载)详解
|
|||
|
|
- **已定(用户拍板)**:① Manager **门户双活 + 控制主备**,**且必须支持单活部署**(同一套代码 1..N 台;单活只需注意:会话必须落 PG(已满足)+ 长连接断开后前端能重连(**待实测**,可用档案 50 的注入位兜底);**禁止任何"必须 2 台"的硬假设**)② 存储**做成可插拔后端**(本地盘 / 自建 NFS / 云 NAS / 云块存储都能接)③ PG 一期**自建**(独立机 + 四层备份),300 人后再评估迁 RDS。
|
|||
|
|
- **存储四形态辨析(写进 §12,易混点)**:① 本地盘(最快、**不能多机共享**)② 块存储 ESSD/CBS(快、**一块盘只挂一台**、秒级快照、**必须与实例同可用区** ⇒ 会锁死 Worker 调度)③ **文件存储 NAS/CFS(RWX 多机同挂 = "位置无关"的实现方式**、小文件性能一般)④ 对象存储 OSS/COS/S3(HTTP API,**不是文件系统**)。⇒ 我原来那两个选项 = ③ vs ② 之争。
|
|||
|
|
- 🔴 **关键约束:对象存储绝不能挂成 `dataRoot`** —— s3fs/ossfs 缺 POSIX 语义(rename/append/锁不一致),对我们是致命的(chokidar watch + 原子写 + append-only 会话日志)⇒ **只能做备份层**。
|
|||
|
|
- ✅ **"两种都支持"成本极低**:所有用户路径从 `config.dataRoot` 派生 ⇒ **指向挂载点即切换后端,业务代码零改动**;要新增的只有**能力探测 + 可观测降级**(原子 rename/fsync、`statfs` 识别 NFS ⇒ 自动开 `skill-filesystem.watchUsePolling`、启动时小文件延迟采样预警、inode 余量)。做法照档案 88 的「按序探测 + 可观测降级」。
|
|||
|
|
- ✅ **推荐"按目录分层挂载"**(比整卷选一种更优):`profiles/**/node_modules` **不入共享层**(镜像+清单可重建,也是备份 L2 排除项)|`sessions`/`credentials`/`skills` → 共享层|`ws/` → 共享层或块存储加速。⚠️ NAS 通用型若小文件起不来,**第一选择是换**极速型/CFS Turbo(不是放弃共享存储)。
|
|||
|
|
- **PG 三个坑(写进 §13)**:① **跨云绝对不要**(入口在腾讯云,若 DB 在另一朵云 ⇒ 每条 SQL 10–30 ms 抖动被放大到每个请求;**必须与 Manager 同 VPC**)② **连接数**(RDS 小规格常限 100;复核是否全走池)③ **`db/sqlite.ts`(+`repo.ts`) 与 `db/pg.ts` 是两套独立实现** ⇒ PG 回归必须**同一组 smoke 跑两边对比**,比"改个 URL"重得多。迁移路径:自建→RDS 用**逻辑复制**在线迁(停机秒级)。
|
|||
|
|
- 定项以外的技术细节(端口段、TTL 数值、对账周期、备份频率)**由实现侧自决,不再上抛**。
|
|||
|
|
|
|||
|
|
### 17:4x · 用户要求「一/二期方案必须互相兼容」→ 新增 §14(阶段兼容性设计)
|
|||
|
|
- **总原则(写进文档)**:**分期只分「自动化程度与规模」,不分「机制、数据结构、协议」** —— 机制与结构一期定死,二期只加机器/加开关/加运维。
|
|||
|
|
- **兼容矩阵 7 维度**:Manager 数(1..N 同代码)|Worker 数(**一期就走 RemoteSpawner+agent,哪怕 Worker 在本机**)|存储(一期就写能力探测 + 按目录分层)|DB(**一期必须 PG,不能先用 SQLite 顶** —— 租约依赖 PG 原子 `UPDATE…WHERE`;两期都是 PG ⇒ RDS 迁移只需改 URL + 逻辑复制)|自动接管(开关默认关,但 **lease+fencing+self-fencing 一期全实现**)|备份(一期就用同一套工具/格式,只调频率)|代理路由(零改动)。
|
|||
|
|
- 🔴 **实测到的阶段兼容风险(已写进 §14.2,全是真实存在的)**:
|
|||
|
|
1. **平台状态目录散落 8 处硬编码**(`/opt/dsh/state/{runtime-baseline,capabilities}.json`、模型托管清单、`/opt/dsh/backups`、`/var/run/dsh-storage-report.json`、`/var/log/dsh-crash-breaker.log`、`/opt/dshs/scripts/ensure-biz-plugins.cjs`、`/usr/local/dsh-runtime`×3)⇒ 二期换存储会「一半在 NAS 一半在本地」隐性分裂;
|
|||
|
|
2. 🔴 **模型托管清单是"每机本地文件"**(`server.ts:106` `/opt/dsh/state/model-landing/<userId>.json`)⇒ 多 Manager 后 A 机写过的 ref、B 机不知道 ⇒ **违反档案 87「只碰自己写过的」⇒ 重复写/漏删用户凭据**(这条最危险,必须挪 PG 或共享目录);
|
|||
|
|
3. **`process.cwd()` 定位脚本**(`orchestrator.ts:251` 找 `ensure-workspace-picker.cjs`)⇒ 多机 cwd 由 systemd WorkingDirectory 决定、不稳定,改基于安装根的绝对路径(复用档案 88 的按序探测);
|
|||
|
|
4. 两套 DB 实现(sqlite+repo / pg)⇒ schema 变更必须两份同改;
|
|||
|
|
5. `dsh_hosts`+归属列一期就建(二期零 schema 变更);
|
|||
|
|
6. uid 是否需建号(§10 第 2 项)一期测掉。
|
|||
|
|
- ✅ **好的一面**:`dataRoot` 派生点 **80 处已全走 config** ⇒ "挂载点一改就切存储"成立的前提已具备。
|
|||
|
|
- **新增"状态三分类"表**(用户数据 / 平台状态 / 机器基线)—— **这是"存储可插拔"能否成立的前提**:分类做对二期只改挂载表,不做就要翻代码。⚠️ 机器基线(`/usr/local/dsh-runtime`、镜像、bwrap 白名单)**不能跟着用户迁移**。
|
|||
|
|
- **新增反模式清单 5 条** + **一期 DoD 8 项**(含:单活实测、远端 Worker 走完整协议、advisory lock 单机自动退化为主、PG 全套 smoke + 回滚演练、能力探测可观测、四层备份同一套工具、自动接管开关默认关)。
|
|||
|
|
|
|||
|
|
### 21:0x · 用户要求「还要考虑方便部署和管理」→ 新增 §15(部署与运维)
|
|||
|
|
- **判据一句话**:**凡是"要登录某台机器手工做"的事,都必须能用一条命令替代**(系统级故障除外)。
|
|||
|
|
- 🔴 **部署单元硬结论:Worker 必须整机镜像,不能容器化** —— 实例隔离依赖 `systemd-run --scope` + `bwrap` + 每用户 `setpriv` 改 uid ⇒ 需真实 systemd 与特权;容器化 = 放弃现有隔离语义(换实现)。**这是与 k8s 模式最本质差别,别照搬容器路线**。Manager 无状态无特权 ⇒ 容器化收益大风险低。建议整机镜像(而非手工装)以保证 §14.3 的"机器基线"一致。
|
|||
|
|
- ✅ **重要修正:共享存储不是开通集群的前置 —— 可以后置**(此前我把它写进了必备项)。拆两步:
|
|||
|
|
- **1a**:Manager-01 + Worker-01 **同机**(Manager 组里一台兼任 Worker),`dataRoot` 原地不动 ⇒ **现有用户零迁移** ⇒ 拿到"归属表 + 租约 + 双活门户 + 跨机协议已验证",**不需要共享存储**(本地盘即可);
|
|||
|
|
- **1b**:加 Worker-02 + 挂 NAS,需要时把用户 rsync 搬一次 ⇒ 拿到"可迁移"。
|
|||
|
|
- 好处:① 第一步不动数据 ⇒ 上线风险极低、随时退回单机;② NAS 小文件性能风险推迟到 1b 再评估;③ `RemoteSpawner`+agent 协议在 1a 就被真实流量验证(正好满足 §14「哪怕同机也走远程协议」)。
|
|||
|
|
- ⚠️ 1a 唯一注意:**Manager 默认 `capacity=0`**(只做门户/控制不接实例),避免门户与实例抢内存。
|
|||
|
|
- **一条命令加机器(`join.sh`)自检项**:cgroup v2/swap/nft、bwrap+setpriv 可用(**顺带验 §14.2 #6 是否需建号**)、`/usr/local/dsh-runtime` 版本匹配、存储后端探测 + 小文件延迟采样、出网 443 与内网可达 Manager、**版本自报**;注册幂等。
|
|||
|
|
- **管理面**:门户新增「集群」页(仅 admin)—— 主机列表(含**版本**/水位/心跳)、实例归属视图(可单用户迁移/批量迁移整机)、存储探测结果、备份状态、告警(含**版本漂移**)、审计。红线不变:破坏性动作仍「先清单 + 二次确认」。
|
|||
|
|
- **一键自检与可观测**:`dshs doctor`(把 §10 的 5 项实测固化成命令)、`dshs cluster status`、`/metrics`+Prometheus、集中日志(Loki/SLS,单机 journald 到集群失效)、**统一 trace id**(🔴 跨机排查最容易后悔的地方)、告警规则。
|
|||
|
|
- **灰度升级 = 集群化最大运维红利**:drain → 等回收 → 换版本 → 自检 → 一次只动一台;Manager 先升非控制主那台;**协议须向后兼容(Manager 兼容 N-1 Worker)**;回滚 = 切符号链接。⚠️ **上集群前必须让 `scripts/ci.sh` 恢复绿**(现状 `test/db.test.mjs` 长期红,属别人 lane)—— 否则"发布可信"这个前提不成立。
|
|||
|
|
- **运维任务表**(日/周/月/变更/应急)+ **资产衔接**:`DEPLOY-本部署.md` → 派生 `DEPLOY-集群.md`;`02-运维手册` 加「集群运维」章;`ops/` 加 join.sh 与集群巡检;`backup-platform.sh` 升级为四层编排。⚠️ 这些都在受保护根内 ⇒ **动它们必须先抢全局执行锁**。
|
|||
|
|
|
|||
|
|
### 21:1x · 用户问「所有服务器都要配域名吗 / 不在同一 IP 下能否组网」→ 新增 §16
|
|||
|
|
- ✅ **只有入口需要域名**(`domain` + `*.domain` + 通配证书)。Manager / Worker / PG / NAS **全部不需要公网域名**;Worker 上**不装 nginx、不配证书**。三条易误解点:① **多 Manager 不需要多域名、不需要粘性会话**(登录靠 PG 会话表 + 同一 `cookieDomain`,给 Manager 各配子域**不需要也不建议**)② **Manager 之间不需要网络直连** —— 互斥靠 **PG advisory lock**,只要"都能连同一个 PG"(结构性优点)③ **备案是域名的属性不是服务器的属性**(阿里云公网 SLB 才校验,现状 CF→ECS 直连不受影响)。
|
|||
|
|
- ✅ **不在同一 IP 下能组网,但分三档**:① 同 VPC(延迟 0.1–1 ms,**可任意迁移**,推荐)② 跨可用区同 VPC(0.5–2 ms,仍可迁移,跨 AZ 流量可能计费)③ **跨云/跨机房**(10–30 ms,必须建 overlay:**WireGuard 首选** / Tailscale·ZeroTier / 专线·CEN;SSH 隧道与纯公网直连不推荐)。
|
|||
|
|
- 🔴 **跨云三代价(重要架构推论,写进 §16.3)**:① **PG 必须与 Manager 同侧**(跨云 SQL 10–30 ms 不可接受)② **NAS 不能跨云挂载** ⇒ 每云一个存储域 ⇒ **用户不能跨云迁移**(所以"任意 Worker 可接手"隐含前提 = 同地域 + 同一存储域;跨云会降级成"每云一个独立集群域")③ **代理流量全走 Manager** ⇒ 跨云带宽成本 + 延迟叠加。⇒ **建议先在一个云内横向扩,不过早跨云**。
|
|||
|
|
- **LB/VIP 前提**:`keepalived` VRRP **需同一二层网络**(跨 AZ 通常不成立,别默认选它)⇒ 用云 HAVIP 或 **SLB(后端注册用 IP,不需要域名)**;⚠️ 阿里云公网 SLB 有 ICP 校验。
|
|||
|
|
- **运维要点**:**防火墙白名单写网段(如 `10.99.0.0/24`)而不是单个 IP** ⇒ 加机器/换 IP 零改防火墙,与 §15「一条命令加机器」目标一致;可选配内网 DNS 名(用 IP 完全等价)。
|
|||
|
|
|
|||
|
|
### 21:1x–21:2x · 现网实测(用户给定 manager=47.77.182.89 / worker-01=47.77.182.89 / worker-02=106.54.21.172)→ 方案新增 §17
|
|||
|
|
- ✅ **全都可达**:47 SSH **端口 32022**(别名 `bt-server`)|106 SSH 22(密钥 `id_ed25519_test106`)|**双向 TCP 22/80/443/8888 等全开**。
|
|||
|
|
- 🔴 **ICMP 双向被安全组拒绝** ⇒ **存活检测绝不能用 ping**(方案 §11 心跳用 HTTP `/healthz` 恰好正确,保持)。
|
|||
|
|
- 🔴 **实测 RTT:47→106 connect 150 ms、106→47 183 ms**(本机回环 0.1 ms;HTTP 首页 total 300 ms)⇒ **比方案原估的 10–30 ms 高 5–10 倍**。⇒ **"不要跨云"的结论不变,但理由从"贵"升级为"实测不可接受"**;两者分属**阿里云 vs 腾讯云**(hostname `iZrj99…` vs `VM-0-8-opencloudos`;内网 172.18.16.212 vs 10.0.0.8)。
|
|||
|
|
- **两台基线差异(§14.3"机器基线不能跟着迁移"的实证)**:47 = Alibaba Cloud Linux 3.2104 / 内核 5.10.134 / **cgroup v1(tmpfs)** / **glibc 2.32(需挂 glibc 2.35 运行时,档案 76)** / **2 核 1870 MB(可用仅 773 MB)**;106 = OpenCloudOS 9.6 / 内核 6.6.119 / **cgroup v2** / **glibc 2.38(不需要该 hack)** / **4 核 3655 MB(可用 2655 MB)** / systemd 255。两台 **bwrap/setpriv/systemd-run/nft/node/dsh 全齐**;两台**都有 swap**。
|
|||
|
|
- 🔴 **发现 1(原"待实测"项闭环):uid 必须建号** —— `setpriv --reuid 100001` 成功(`id -u`=100001),但同 uid 下 node **`os.userInfo()` 直接 FAIL `ERR_SYSTEM_ERROR`** ⇒ **每台 Worker 都必须为要跑的 uid 建号/写 passwd 条目**;**加机器必须同步账号**(漏了 = 实例启动即崩)。这也印证书里为何把 `/etc/passwd` 列为 bwrap 白名单必挂项(`orchestrator.ts:640`)。⇒ 对应 §14.2 #6 = **需要建号**。
|
|||
|
|
- 🟠 **发现 2(新隐患,值得单独立项):`systemd-run` 未设 `MemorySwapMax`**(`orchestrator.ts:749-751` 只有 `MemoryMax` + `TasksMax`),而两台机器都有 swap ⇒ 实例超限**先被换出(变慢)而不是被 OOM kill** ⇒ **"OOM 杀→熔断自愈"(档案 20/78)的语义在有 swap 的机器上不成立**,且不同机器 swap 大小不同 ⇒ 同一用户跨 Worker 表现不一致。建议 Worker 统一 `-p MemorySwapMax=0` 或 join 自检告警。⚠️ **现状单机就有此隐患**(47 也有 swap),不是集群引入。
|
|||
|
|
- 🟡 **106 上已有一套独立平台在跑**(不是裸 Worker):`/root/dsh-users-platform/` + systemd `dsh-users-platform.service`(active 16h+,`EnvironmentFile=/etc/dsh-users-platform.env`,User=root,Restart=always,监听 127.0.0.1:3080);数据根**不在** `/var/lib/dshs`;另有宝塔面板 :8888 与 nginx。⇒ 要用它当 Worker-02 需先定:**(a) 保留独立测试环境(推荐) / (b) 停掉改造成纯 Worker / (c) 就地变成 1a 集群**。
|
|||
|
|
- **部署建议(基于实测)**:🔴 **这两台不能组成"一个"集群**(跨云 150–183 ms)⇒ 要么**选一个云内扩容**,要么**各自独立**(= "每云一个集群域");⚠️ **47 不适合做 Manager**(可用内存 773 MB);✅ **若要试 1a,优先放 106**(可用 2655 MB 且隔离组件全就绪)。
|
|||
|
|
- 📌 **跨日续**(09-15 06:3x 的 K8S 对比 / 验证策略 / 访问模式兼容)已记入 `2026-09-15.md`。
|
|||
|
|
|