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,出向拉取期望状态
场景:隔离网段部署
角色:平台运维 / SRE
耗时:约 10 分钟
- 背景
- 陈工要把 demo-app 部署到三区——一个只能出、不能进的隔离网段,安全策略明文规定"控制平面不得向被管环境建立任何入向连接"。他在目标机放了一个 Spoke Agent,配置 HubBaseURL 指向控制平面(出向可达)、AgentName=prod-zone3-node01,让它自己定时拉期望状态。
- 传统做法对比
- 以前要从跳板机连进去拷包、手动启停、人工对版本,一次发布 1~2 小时;开入向端口又过不了安全评审,两边扯皮。
- 角色
- 陈工(平台运维 / SRE,负责 Agent 部署与配置);安全合规(刘经理)确认了"零入向端口"模型后才放行。
- 操作步骤
-
- 把 Agent 二进制拷到目标机(三区出向可达控制平面即可)
- 配置轮询参数:HubBaseURL=http://hub 控制面地址、AgentName=prod-zone3-node01、Interval=60s
- (可选)配置 Bootstrap Token:Token=envt_xxx 随 ?token= 传递,登记过 token 的环境必须携带
- 启动 Agent,让它开始轮询 GET /api/v1/agent/pull?agent=prod-zone3-node01
- 到控制台 /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 看得到节点
场景:状态上报
角色:应用开发
耗时:约 3 分钟
- 背景
- 阿强在自己负责的 dev 环境启动了 Spoke Agent(AgentName=dev-app-01)。每 60 秒,Agent 把 web / db 两个组件的状态打包成一次上报发回控制平面。他想确认"我的应用是不是按期望状态在跑"——打开 Agent 页就能看到。
- 传统做法对比
- 以前装了什么组件、活没活着全靠猜,得登服务器 ps 一下;几十台机器挨个看一遍,还没看完前面的又变了。
- 角色
- 阿强(应用开发,看自己环境的组件状态);陈工(平台运维,负责 Agent 心跳与节点健康汇总)。
- 操作步骤
-
- 启动 Poller 并观察它的上报日志
- Agent 每轮 POST /api/v1/agent/report 上报组件状态
- 到控制台 /apollo/agents 看节点列表
- (可选)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 指数退避重试、恢复后自动续拉
场景:断网 / 弱网容错
角色:平台运维 / SRE
耗时:约 5 分钟
- 背景
- 晚上 22:40,三区出口防火墙做例行变更,Agent 连续几次拉取失败。陈工盯着日志看它"慌不慌"——结论是不慌:失败按指数退避重试,退避期内只记日志不重试,等出口恢复后自动续拉,期间本地进程照常运行。
- 传统做法对比
- 以前断网即断联,恢复后还得人肉补同步;有些脚本失败就疯狂重试,把出口打得更堵,运维半夜起来处理"重试风暴"。
- 角色
- 陈工(平台运维 / SRE,观察退避与恢复);阿强(应用开发,确认自己应用进程在断网期间不受影响)。
- 操作步骤
-
- 模拟断网:停掉 Hub 或切断 Agent 出向链路
- 观察 Agent 日志中的指数退避序列(5s → 10s → 20s → … → 5min)
- 恢复链路,观察 Agent 自动续拉成功、退避重置为 5s
- 到 /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,离线三区骨架属规划。