Files
MoShiro/docs/fleet/update.md
T
justme 8dd3209a65 feat!: probes link to the panel; signed release updates for container nodes [CI]
Probes connect to the panel directly and get the agent list as desired state.
CI signs the image digest of agent and probe releases, the panel forwards the
manifest, and a host updater verifies it with a local public key, pulls by
digest and rolls back when the new version is unhealthy.
2026-10-06 22:42:12 +08:00

7.4 KiB
Raw Blame History

Fleet:通用更新(Fleet.Update / fleet-updater)

实现状态:容器节点(Agent、Probe)的更新已经实现,见 deployment.md 的「容器节点的更新」:同样的信任模型(离线发布密钥签名、本机配置公钥和仓库、seq 防重放、面板只通知和请求),清单里是镜像 digest,更新器是宿主机上的 monitor-update。本文下面描述的纯二进制方案(Fleet.Update、fleet-updater、fleet-release、健康信标)还没有实现。

1. 原则

  • 与产品无关:fleet-updater 通过本地配置声明它管理哪些应用,一台机器上可以同时管理多个产品。
  • 信任来自签名,而不是来源:只接受由离线发布密钥签名的清单。发布源可以是面板、GitHub Releases、对象存储或任意 HTTP 服务。面板即使被攻破,也推不出恶意二进制。
  • 本地说了算:更新策略(auto、notify、pinned)写在节点本地的配置中,面板只能“建议”目标版本。
  • 权限分离:业务进程不能改写自己的二进制;更新进程读不到业务身份私钥,也不参与业务。
┌─ 节点 ─────────────────────────────────────────────────────────────────────┐
│ myapp(业务进程)          用户 myapp:只读 /opt/myapp,只能写配置目录          │
│   └─ HealthBeacon ──写──> /run/fleet/myapp.beacon                          │
│ fleet-updater              用户 fleet-update:只能写 /opt/myapp/versions,        │
│   ├─ 读取信标                 通过 polkit/sudoers 规则只允许重启 myapp.service      │
│   └─ 不读取 node.key                                                        │
└────────────────────────────────────────────────────────────────────────────┘

2. 本地配置(/etc/fleet/updater.json)

{
  "trust": [
    { "id": "rel-2026a", "alg": "ecdsa-p256", "pubkey": "BASE64..." },
    { "id": "rel-2027a", "alg": "ecdsa-p256", "pubkey": "BASE64..." }
  ],
  "check_interval_s": 3600,
  "apps": [
    {
      "id": "monitor-agent",
      "sources": ["https://panel.example.com/fleet/releases", "https://github.com/org/monitor/releases/download"],
      "channel": "stable",
      "policy": "notify",
      "rid": "linux-x64",
      "install_dir": "/opt/monitor-agent",
      "keep_versions": 2,
      "service": { "kind": "systemd", "name": "monitor-agent" },
      "health": { "kind": "beacon", "path": "/run/fleet/monitor-agent.beacon", "timeout_s": 60 }
    }
  ]
}
  • trust 可以配置多把公钥,用于轮换;清单中的 key_id 用来选择验证哪一把。
  • policy 取值:auto(验签通过即安装)、notify(只下载并上报“有可用更新”,等待面板上的人工批准)、pinned:<version>(锁定版本)。
  • 面板上的批准只对 notify 生效,并且批准的版本同样必须通过签名验证。

3. 发布清单(由 fleet-release 在 CI 或离线环境中生成)

{
  "schema": 1,
  "app": "monitor-agent",
  "channel": "stable",
  "version": "0.4.2",
  "released_at": "2026-10-01T00:00:00Z",
  "min_from": "0.3.0",
  "artifacts": [
    { "rid": "linux-x64",   "url": "monitor-agent-0.4.2-linux-x64.tar.gz",   "sha256": "…", "size": 4120334 },
    { "rid": "linux-arm64", "url": "monitor-agent-0.4.2-linux-arm64.tar.gz", "sha256": "…", "size": 3987112 },
    { "rid": "linux-musl-arm64", "url": "monitor-agent-0.4.2-linux-musl-arm64.tar.gz", "sha256": "…", "size": 3990021 },
    { "rid": "win-x64",     "url": "monitor-agent-0.4.2-win-x64.zip",        "sha256": "…", "size": 4455010 }
  ],
  "notes_url": "https://…/CHANGELOG#0.4.2",
  "key_id": "rel-2026a",
  "signature": "BASE64(ECDSA(规范化 JSON,不含 signature 字段))"
}
  • 签名范围是规范化后的 JSON:键排序、无空白、不含 signature 字段。规范化规则由 Fleet.Update 实现,签名端和验签端使用同一份代码。
  • 发布源的目录约定:<source>/<app>/<channel>/latest.json(指向当前版本)和 <source>/<app>/<version>/manifest.json。
  • 防回滚:updater 在本地记录每个应用已安装过的最高版本;除非本地配置显式允许降级,否则拒绝安装更低的版本,防止旧的签名清单被重放。

4. 更新流程

轮询(间隔加随机抖动)
  └─ 拉取 latest.json → 验签 ✗ → 记录并忽略
     ├─ 版本 ≤ 当前版本,或当前版本 < min_from → 跳过(后者需要人工介入)
     ├─ policy = notify → 下载并校验 → 上报“可以更新” → 等待批准
     └─ policy = auto 或已批准:
          下载 → 校验 sha256 和大小 → 解包到 versions/<v>/ → 检查可执行文件
          → 原子切换 current → versions/<v>(Linux 用符号链接,Windows 见 §5)
          → 重启服务 → 等待健康信标(version == v,并且 ok_at 晚于重启时刻)
              ✓ → 记录成功,按 keep_versions 清理旧版本
              ✗ 超时 → 切回旧版本 → 重启 → 记录失败,该版本标记为坏版本,不再自动尝试

5. 平台适配

平台 切换方式 服务管理
Linux current 符号链接 + rename(2) 原子替换 systemd:systemctl restart <name>,由 polkit 规则限定只能操作被管理的单元
Windows 服务的 ImagePath 指向 current\app.exe;先停服务,再切换目录联结(junction),然后启动 SCM:updater 的服务账户只被授予这几个服务的启动和停止权限
OpenWrt 与 Linux 相同的符号链接切换;默认 keep_versions = 1,下载前检查闪存剩余空间 procd:/etc/init.d/<name> restart(updater 以 root 运行,这是 OpenWrt 的常见做法;仍然不读取节点身份目录)
其他(以后) launchd、OpenRC 通过适配器接口 IServiceAdapter 扩展

6. 面板侧(Fleet.Server.Releases,可选)

  • 托管清单和制品(也可以只做代理或索引,制品放在 GitHub 或对象存储)。
  • 通道与灰度:每个通道可以指定目标版本,并按标签分批发布,例如先更新 1 台节点,观察 N 分钟没有失败后再更新其余节点。
  • 状态汇总:节点在 fleet.hello 中上报 {app, version},updater 上报 {available, installing, failed, rolled_back}。面板据此展示版本分布和更新进度。
  • updater 与面板通信时,不使用业务进程的身份。首版只轮询公开的通道清单,完全不需要身份,状态也由业务进程在 fleet.hello 中代为上报。给 updater 单独一对身份密钥是以后的扩展。

7. 发布密钥管理

  • 私钥只存在于 CI 的密钥服务或离线环境中,永远不上传到面板。
  • 轮换方式:新旧两把公钥同时写进 trust → 用新密钥发布 → 所有节点都更新到信任新密钥的版本后 → 移除旧公钥。
  • 密钥泄露时:发布一个用新密钥签名、并从 trust 中移除旧公钥的版本;已经被恶意更新的节点需要人工重装。