业务故事站
P3 Apollo

Spoke Agent:拉取式部署的触角

Apollo 不往你的服务器里塞东西、也不反向连你的网络——被管环境里只放一个轻量 Spoke Agent,它自己定时向控制平面"拉"期望状态、"报"实际状态。隔离网段能部署、断网不慌、状态可查。看完这 3 个故事,你就明白为什么"控制平面永不主动连入被管环境"是信创离线与安全合规的根基。

平台运维 / SRE 应用开发 拉取模式 轮询上报 指数退避 离线容错 共 3 个故事

能 / 不能速览

✅ 这个主题能做
  • 在隔离网段 / 无入向端口的环境部署 Spoke Agent,出向 GET 拉期望状态
  • Agent 按周期(默认 60s)轮询 /api/v1/agent/pull,可配置间隔与鉴权 Token
  • Agent 出向 POST /api/v1/agent/report 上报组件状态与资源,Hub 汇总可见
  • 拉取失败指数退避(5s → ×2 → 封顶 5min),成功自动重置,断网不丢状态
  • 上报端点登记必验 Token(F1):登记过 Bootstrap Token 的环境,伪造上报直接 401,不写库不推进
  • 部署失败/漂移可用手动 sync 指令(F2)清退避强制重调和;Agent 长期离线超 5 分钟部署自动置 failed(F7)
  • 控制台 /apollo/agents 与 GET /agents 查看节点在线状态、reconcile_state(真实聚合)与最近心跳
⛔ 这个主题做不了
  • Agent 仅 ProcManager(process)运行时:exec.Cmd 管理子进程,systemd 未实现
  • 演示 Poller 只拉取 / 上报,不真实部署进程(未配置 ProcManager 时仅巡检)
  • gRPC / mTLS 属设计文档,当前实现为 HTTP / 轮询,无双向 TLS
  • 非 K8s 环境的 VM / 裸金属 Agent、边缘 / 断网增量同步、指标门控属 V2/V3

适用角色

本主题面向两个角色:

  • 平台运维 / SRE(陈工):负责把 Spoke Agent 部署到各环境、看在线状态、处理断网恢复——是 Agent 生命周期的主人。
  • 应用开发(阿强):在自己负责的环境里跑 Agent、看组件状态、确认自己的应用按期望状态在跑。

安全合规(刘经理)关注"控制平面不反向连入"的模型如何满足隔离网段合规,不直接操作。

能力速览(能做什么)

Pull 模式轮询

Agent 按周期(默认 60s,可配置)出向 GET /api/v1/agent/pull 拉取期望状态声明与 bundle digest;控制面永不主动连入被管环境。

状态上报

Agent 出向 POST /api/v1/agent/report,上报组件状态、实际状态快照与资源占用;Hub 幂等 upsert 到 spoke_agents 表。

指数退避重试

拉取失败按 5s → ×2 → 封顶 5min 退避,成功即重置;404 视为"无更新",幂等巡检不误报。

在线视图

控制台 /apollo/agents 与 GET /agents 列出所有 Agent:online / offline / updating,reconcile_state 与最近心跳一目了然。

ProcManager 进程管理

组件级 start / stop / restart 落到进程:崩溃指数退避自愈(初始 5s ×2 封顶 5min),达到上限进 degraded 等待人工。

出向鉴权与指令回传

report 登记必验 Token(F1,伪造 401 不写库不推进);手动 sync 清退避强制重调和(F2);Agent 离线超 5 分钟部署自动 failed(F7)。

调整指南(怎么调整)

  • 改轮询周期:在 PollerConfig.Interval 里调(默认 60s),想即时收敛可配合手动拉取 / 触发式调和。
  • 改退避参数:BackoffBase / MaxBackoff(默认 5s / 5min),弱网环境可加大封顶,避免抖动风暴。
  • 加鉴权:PollerConfig.Token 填 Bearer Token,Agent 拉取 / 上报都带上,控制面只暴露"可拉取的元数据 + 制品"。
  • 改 Agent 名:AgentName 是节点唯一标识(pull / report 都携带),建议按"环境-角色-序号"命名便于 /agents 视图区分。
  • 改进程管理:组件声明里配 restart_policy / restart_backoff,控制崩溃自愈强度;无进程组件(config / llm_route 等)自动跳过进程管理。

做得好的场景

"出向拉取 + 出向上报"把安全与可用性都收进来,特别适合以下场景:
  • 隔离网段 / 信创离线:生产环境零入向端口,Agent 自己出向拉包,物理隔离也能按期望状态推进。
  • 大规模多环境:控制面不用知道每台机器的地址,Agent 自报家门(AgentName + 心跳),节点扩容无需改 Hub 配置。
  • 弱网 / 断网容忍:指数退避 + 幂等(digest 未变跳过应用),断网恢复后自动续拉,不会风暴式重试。
  • 安全合规:控制平面不持有生产环境访问凭证、不建立入向连接,"谁拉了什么、报了什么"在审计中留痕。

限制与不足

以下是明确的边界,使用前先知道:
  • 运行时受限:Agent 仅 ProcManager(process)目标运行时;windows_service 只是包装 process,systemd 返回 NOT_SUPPORTED。
  • 演示不真实部署:演示 Poller 未配置 ProcManager 时只拉取 / 上报、不实际拉起进程,属于"巡检 + 汇报"。
  • 协议现状:gRPC / mTLS 是设计方向,当前是 HTTP / 轮询,生产强安全场景需自行加网络层保护。
  • 能力边界:边缘 / 断网增量同步(delta / 断点续传)、非 K8s 裸金属 Agent 托管、指标门控渐进发布均属 V2/V3。
  • degraded 态落地有限:设计里的"连续失败进 degraded + 告警"在演示 Agent 中只覆盖拉取重试,完整监控告警自愈属 V2。

场景故事

故事 1 三区隔离网段部署 Spoke Agent,出向拉取期望状态
背景
陈工要把 demo-app 部署到三区——一个只能出、不能进的隔离网段,安全策略明文规定"控制平面不得向被管环境建立任何入向连接"。他在目标机放了一个 Spoke Agent,配置 HubBaseURL 指向控制平面(出向可达)、AgentName=prod-zone3-node01,让它自己定时拉期望状态。
传统做法对比
以前要从跳板机连进去拷包、手动启停、人工对版本,一次发布 1~2 小时;开入向端口又过不了安全评审,两边扯皮。
角色
陈工(平台运维 / SRE,负责 Agent 部署与配置);安全合规(刘经理)确认了"零入向端口"模型后才放行。
操作步骤
  1. 把 Agent 二进制拷到目标机(三区出向可达控制平面即可)
  2. 配置轮询参数:HubBaseURL=http://hub 控制面地址、AgentName=prod-zone3-node01、Interval=60s
  3. (可选)配置 Bootstrap Token:Token=envt_xxx 随 ?token= 传递,登记过 token 的环境必须携带
  4. 启动 Agent,让它开始轮询 GET /api/v1/agent/pull?agent=prod-zone3-node01
  5. 到控制台 /apollo/agents 或 GET /agents 确认节点已登记在线
系统响应
pull 端点返回拉取目标(优先 Agent 最近部署对应的 active 期望状态,其次 stable 通道指向):
{
  "desired_state": { "app": "demo-app", "version": "1.0.0",
    "name": "demo-app", "channel": "stable",
    "components": [ {"name":"web","depends_on":["db"]}, {"name":"db"} ] },
  "bundle_digest": "sm3:...",
  "has_update": true
}
无 active 期望状态时返回 404,Agent 视为"无更新"继续巡检。
结果洞察
三区一台入向端口都不开,Agent 自己出向拉取、自行校验后应用——这是 air-gap 离线能力的根本来源。Agent 多装一台,控制面不用改任何配置,节点"自报家门"(AgentName + 心跳)就完成了登记。
调整建议
轮询周期按业务紧迫度调(演示 60s,生产可加大);Agent 名按"环境-角色-序号"命名,/agents 视图一眼区分;弱网环境加大 MaxBackoff 封顶。
动手试一试
登录:admin / admin1。页面路径:Agent 页 /apollo/agents。输入内容:启动一个 Poller(AgentName=prod-zone3-node01,HubBaseURL=控制面地址),观察轮询日志。预期结果:GET /agents 出现该节点,status=online、reconcile_state=synced、last_seen 持续刷新。
限制提示
演示 Poller 未配置 ProcManager 时只拉取 / 上报、不真实部署进程;gRPC / mTLS 属设计,当前为 HTTP / 轮询;Agent 运行时仅 process(systemd 未实现);pull 的 token 校验是引导式(token 为空跳过),report 则登记必验(F1)。
故事 2 Agent 上报组件 ready / failed,控制台 /agents 看得到节点
背景
阿强在自己负责的 dev 环境启动了 Spoke Agent(AgentName=dev-app-01)。每 60 秒,Agent 把 web / db 两个组件的状态打包成一次上报发回控制平面。他想确认"我的应用是不是按期望状态在跑"——打开 Agent 页就能看到。
传统做法对比
以前装了什么组件、活没活着全靠猜,得登服务器 ps 一下;几十台机器挨个看一遍,还没看完前面的又变了。
角色
阿强(应用开发,看自己环境的组件状态);陈工(平台运维,负责 Agent 心跳与节点健康汇总)。
操作步骤
  1. 启动 Poller 并观察它的上报日志
  2. Agent 每轮 POST /api/v1/agent/report 上报组件状态
  3. 到控制台 /apollo/agents 看节点列表
  4. (可选)GET /agents 拿接口返回核对字段
系统响应
report 请求体为组件级状态快照,上报成功返回:
{"agent_name":"dev-app-01","accepted":true}
组件状态 running/stopped/restarting/degraded/error/unknown;Hub 幂等 upsert 到 spoke_agents 表,GET /agents 返回 status=online、reconcile_state、mem_mb / cpu、last_seen。reconcile_state 由最近部署状态 + 组件状态真实聚合(全 running → synced、有 degraded/error → degraded、进行中 → pending),不再恒为 synced。
结果洞察
组件级状态通过 advanceStatusOf 映射成编排语义(running → ready、error/degraded → failed、stopped/restarting/unknown → deploying),驱动就绪门控与自动回滚;上报是幂等 upsert,重复上报不会产生脏数据。控制台"部署总览"把 Agent 在线与否和部署推进串在一起看。
调整建议
上报周期跟轮询周期保持一致,避免状态失真;Agent 长时间 offline 说明出向链路断了,优先查网络;组件 readiness 快照可在部署推进时回填,帮助定位是哪一步没就绪。
动手试一试
登录:admin / admin1。页面路径:Agent 页 /apollo/agents。输入内容:启动 Poller(AgentName=dev-app-01),观察其每轮 report 日志。预期结果:/agents 显示 dev-app-01 在线,组件状态与 last_seen 持续刷新。
限制提示
CPU 占用为占位 0,指标采集由后续子任务接入;上报失败只做退避重试,Agent 离线时 Hub 不会主动拉起;组件"真实就绪"依赖 ProcManager,演示 Poller 仅汇报探针快照;登记过 token_hash 的环境上报必须带正确 Token(?token= 或 Bearer 头),缺失/错误返回 401 且不写库、不推进编排(F1)。
故事 3 三区出口抖动,Agent 指数退避重试、恢复后自动续拉
背景
晚上 22:40,三区出口防火墙做例行变更,Agent 连续几次拉取失败。陈工盯着日志看它"慌不慌"——结论是不慌:失败按指数退避重试,退避期内只记日志不重试,等出口恢复后自动续拉,期间本地进程照常运行。
传统做法对比
以前断网即断联,恢复后还得人肉补同步;有些脚本失败就疯狂重试,把出口打得更堵,运维半夜起来处理"重试风暴"。
角色
陈工(平台运维 / SRE,观察退避与恢复);阿强(应用开发,确认自己应用进程在断网期间不受影响)。
操作步骤
  1. 模拟断网:停掉 Hub 或切断 Agent 出向链路
  2. 观察 Agent 日志中的指数退避序列(5s → 10s → 20s → … → 5min)
  3. 恢复链路,观察 Agent 自动续拉成功、退避重置为 5s
  4. 到 /agents 确认节点回到 online
系统响应
拉取失败日志:
WARN 拉取期望状态失败,指数退避
  agent=prod-zone3-node01 error=... backoff=10s
404 视为无更新(幂等巡检);恢复后 backoff 重置为初始 5s;digest 未变的期望状态不会重复应用,只做探测巡检。
结果洞察
退避 5s → ×2 → 封顶 5min 防止抖动风暴;幂等约定(同一期望状态按 digest 判定)保证断网恢复后重试结果与之前一致。所有连入方向都由 Spoke 主动发起(拉取=GET、上报=POST),控制面无入向端口,天然抗断网、天然合规。
调整建议
弱网环境把 MaxBackoff 调大(如 10min),避免频繁打出口;重要发布前先手动拉一次确认链路;断网超过窗口想强制收敛,可配置触发式拉取在维护窗口内即时拉。
动手试一试
登录:admin / admin1。页面路径:Agent 日志 + /apollo/agents。输入内容:启动 Poller 后临时停掉控制面,观察退避日志;恢复后看退避重置。预期结果:退避序列 5s→10s→…→5min,恢复后自动续拉,/agents 回到 online。
限制提示
"连续失败达阈值进 degraded + 告警"在演示 Agent 中落地有限,完整监控告警自愈属 V2;断点续传 / delta 增量更新属 V2(为 Swift 星上铺垫);Agent 离线期间 Hub 不会主动补连,但部署无进展超 5 分钟且 Agent 失联时,部署会自动置 failed(审计 DEPLOYMENT_TIMEOUT,F7),回连后按最新 digest 幂等收敛。

常见问题

为什么说"控制平面永不主动连入被管环境"?

所有通信都由 Spoke Agent 主动发起:拉取是出向 GET /api/v1/agent/pull,上报是出向 POST /api/v1/agent/report,控制面只暴露"可拉取的元数据 + 制品"端点,不向目标机建立任何连接、不持有生产环境访问凭证。因此被管环境不需要任何入向端口,隔离网段 / 信创离线场景才可能成立。

Agent 断网了会怎样?

拉取失败按指数退避重试(5s → ×2 → 封顶 5min),成功自动重置;退避期内只记日志不重试,避免重试风暴。断网期间本地进程照常运行,恢复后 Agent 自动续拉;digest 未变的期望状态不会重复应用,幂等安全。

演示 Agent 真的会部署进程吗?

取决于是否配置 ProcManager。演示 Poller 未配置 ProcManager 时只做"拉取 + 上报",不实际拉起进程;配置了进程管理(仅 process 运行时)后,Reconciler 才会按 start / stop / restart 动作真实管理子进程。systemd / windows_service 完整实现尚在后续。

Agent 之间会互相干扰吗?

不会。每个 Agent 用 AgentName 唯一标识,pull / report 都携带,spoke_agents 按 AgentName 幂等 upsert;部署编排按 Agent 串行锁(同一 Agent 已有进行中部署会返回 409 DEPLOYMENT_CONFLICT),不同 Agent 之间互不阻塞。

上报失败会重试多少次?

上报走同样的退避逻辑:失败进入退避,成功重置;没有"固定重试次数上限",但退避封顶 5min(可配置),不会无限高频重试。节点长期 offline 时,/agents 里仍能看到该 Agent 的最后心跳时间用于判断。

Agent 上报会被伪造吗?

报告端点(/agent/report)对登记过 Bootstrap Token(token_hash)的环境强制校验:必须携带正确 Token(?token= 或 Authorization: Bearer),缺失/错误返回 401,且不写 spoke_agents、不推进编排、不更新心跳(F1)。未登记 token 的环境(旧 demo Agent)为兼容仍放行;拉取端点(pull)的 token 校验是引导式——token 为空即跳过。

主题小结

一句话:Spoke Agent 是 Apollo 伸进被管环境的"手"——但它从不伸手,只出向拉取(GET)与上报(POST),控制平面永不主动连入。隔离网段能部署、断网指数退避、上报登记必验(F1)、状态可查可审,这是 air-gap 离线与安全合规的根基。记住边界:Agent 仅 process 运行时、演示 Poller 不真实部署、断点续传与 gRPC/mTLS 在 V2/V3,离线三区骨架属规划。