外观
Node Agent 升级
两条路径:控制面驱动的自更新是设计上的默认路径;pip wheel 手工滚动用于首次引导与自更新不可用时的回退。
现役状态:控制面的发布库当前为空、全局目标版本未设(
GET /admin/agent-releases的versions与target都是空),因此自更新链路处于「已具备但未启用」的状态,实际升级仍走手工路径。要切回自更新,按下面的发版流程上传一次 wheel 并设目标版本即可——注意目标版本必须 ≥ 当前 fleet 版本,更新器只升不降。
构建 wheel
两条路径共用同一份产物。
bash
bash deploy/tools/build_wheels.sh- 构建 5 个一方 wheel 到
deploy/dist/:dn42-common、dn42-schemas、dn42-runtime、dn42-templates、dn42-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 发布。
发版流程
- 构建 wheel(见上)。
- 上传发布:
POST /control/v1/admin/agent-releases(multipart,一次上传全部 wheel)。控制面落持久卷<releases_dir>/<version>/并生成含每个 wheel sha256 的 manifest。上传本身不放量。 - 设全局目标版本:
POST /control/v1/admin/agent-releases/target。此后 agent 经 WS 心跳上报自身版本,低于目标即收到一次agent_update_available门铃。 - 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]脚本做三件事:
把
deploy/dist/*.whl传到节点本地/opt/dn42-wheels(就是 pip 的--find-links目录)。执行离线安装:
bashpip 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,不会主动升无版本约束的依赖。
重启
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-releaseslast_report_status=succeeded 加上全部节点 up_to_date=true,即升级成功。
节点侧核对:
bash
/opt/dn42-agent/venv/bin/pip list | grep dn42何时不能用自更新
见 锁步契约——涉及 agent 与控制面之间连通性契约的变更(API 前缀、鉴权形态),健康门必然超时并逐节点自动回滚,必须走 SSH 手工路径。