Skip to content

Node Agent 升级

两条路径:控制面驱动的自更新是设计上的默认路径;pip wheel 手工滚动用于首次引导与自更新不可用时的回退。

现役状态:控制面的发布库当前为空、全局目标版本未设(GET /admin/agent-releasesversionstarget 都是空),因此自更新链路处于「已具备但未启用」的状态,实际升级仍走手工路径。要切回自更新,按下面的发版流程上传一次 wheel 并设目标版本即可——注意目标版本必须 ≥ 当前 fleet 版本,更新器只升不降。


构建 wheel

两条路径共用同一份产物。

bash
bash deploy/tools/build_wheels.sh
  • 构建 5 个一方 wheel 到 deploy/dist/dn42-commondn42-schemasdn42-runtimedn42-templatesdn42-node-agent(模板数据文件随包打入)。
  • 版本 = 1.0.<git commit 数>(如 1.0.312),单调递增,供 pip -U 识别。
  • 版本注入方式是构建时临时改各 pyproject.toml[project].version,构建完即还原(trap 兜底)。不用 hatch-vcs:.dockerignore 排除了 .git,dynamic version 会让控制面的 docker 构建拿不到 git 历史而失败。
  • control-server 不在此列——它走 docker build 从源码全新重建,没有漏文件或残留的问题。

版本号等于 commit 数,所以不要 squash 提交:squash 会让版本号倒退,而更新器只升不降。


路径一:控制面驱动的自更新

wheel 只上传到控制面一处,各节点 agent 自行拉取安装,取代逐节点 scp 的滚动。

源码:apps/node-agent/agent/self_update.py(节点侧)与 apps/control-server/app/services/agent_releases.py(分发侧)。接口见 Agent 发布

发版流程

  1. 构建 wheel(见上)。
  2. 上传发布POST /control/v1/admin/agent-releases(multipart,一次上传全部 wheel)。控制面落持久卷 <releases_dir>/<version>/ 并生成含每个 wheel sha256 的 manifest。上传本身不放量。
  3. 设全局目标版本POST /control/v1/admin/agent-releases/target。此后 agent 经 WS 心跳上报自身版本,低于目标即收到一次 agent_update_available 门铃。
  4. agent 收铃后经已鉴权通道下载 manifest 与 wheel,逐字节核对 sha256,暂存 /opt/dn42-wheels,然后触发独立的 systemd oneshot 完成整组 pip install、重启与健康门判定,失败自动回滚。

四条设计约束

约束理由
整组更新5 个 wheel 一起装,绝不单更某个包——共享包与 agent 之间有隐式版本耦合
只升不降数字版本严格比较,目标大于当前才更新。防回退,也防降级攻击
下载即校验对 manifest 的 sha256 逐字节核对,不符即弃、绝不安装
进程隔离更新器是独立 oneshot,不随被升级的 agent 一起被杀;健康门失败时它还活着,能滚回旧版

前提

节点须先装更新器 unit,每节点一次:

bash
sudo bash deploy/tools/agent-self-update/install-updater.sh

漏装的节点会静默卡在旧版本——它照常收心跳、照常报版本,只是没人执行安装。发版后核对 GET /control/v1/admin/agent-releases 返回的 nodes[].up_to_date,全 true 才算收敛。


路径二:pip wheel 手工滚动

用于首次引导(含自更新代码的那一版本身还得手动滚一次)、更新器未装、或控制面不可达时。

bash
bash deploy/tools/agent_pip_rollout.sh <ssh-target> <key-path> [ssh-port]

脚本做三件事:

  1. deploy/dist/*.whl 传到节点本地 /opt/dn42-wheels(就是 pip 的 --find-links 目录)。

  2. 执行离线安装:

    bash
    pip install -U --no-index --find-links /opt/dn42-wheels/ \
      dn42-common dn42-schemas dn42-runtime dn42-templates dn42-node-agent
    • --no-index 全程离线,不碰 PyPI 或任何镜像源。
    • 必须显式列全 5 个包:pip 默认 only-if-needed,不会主动升无版本约束的依赖。
  3. 重启 dn42-node-agent.service 并回显 pip list | grep dn42

用「节点本地 find-links」而非中央 HTTP index,是因为小 fleet 下它零公网暴露、零常驻服务、天然离线,分发由 rollout 脚本顺带完成。

非 root 节点

agent_pip_rollout.sh 假定 root。非 root 节点(sudo 免密)需手工等价操作:建 /opt/dn42-wheels 并 chown、scp wheel、远端 sudo 跑 pip 与 restart。


回退

节点的 /opt/dn42-wheels 保留历次 wheel。回退就用旧版本号那批重跑离线安装:

bash
pip install -U --no-index --find-links /opt/dn42-wheels/ \
  dn42-common==1.0.<> dn42-schemas==1.0.<> dn42-runtime==1.0.<> \
  dn42-templates==1.0.<> dn42-node-agent==1.0.<>
systemctl restart dn42-node-agent.service

git checkout <旧 commit> 后重新构建并 rollout。


验收

bash
# 控制面存活探针
curl -s https://api.natlan.io/control/v1/healthz

# fleet 上报
curl -s -H "Authorization: Bearer <admin-token>" \
  https://api.natlan.io/control/v1/admin/health

# 各节点版本与目标是否一致
curl -s -H "Authorization: Bearer <admin-token>" \
  https://api.natlan.io/control/v1/admin/agent-releases

last_report_status=succeeded 加上全部节点 up_to_date=true,即升级成功。

节点侧核对:

bash
/opt/dn42-agent/venv/bin/pip list | grep dn42

何时不能用自更新

锁步契约——涉及 agent 与控制面之间连通性契约的变更(API 前缀、鉴权形态),健康门必然超时并逐节点自动回滚,必须走 SSH 手工路径。