已开启
oFEP-0006:FluxSandbox 可观测性指标体系设计 #2
rd-tan创建于  20 天前
rd-tan成员
20 天前 创建

oFEP 元信息
ofep-number: 0006 | status: provisional | stage: alpha | authors: - rd-tan | creation-date: 2026-10-12 | disable-supported: true
see-also: - "oFEP-0001(性能目标:<50ms / >3000实例每秒 / 密度>1000,监控要求章节待回填) | 本 oFEP 登记指标 113 个(完整名册见 front matter 原文,位于仓库 docs/design 落地版)
关联底稿:《FluxSandbox 指标采集设计建议》(issue #1)| 更新记录:2026-10-12 架构图 v2(读图与走线说明 + 两端外部边界 + 移除未接线组件);布局经多轮迭代(隐形分隔/真实关系边/无容器着色)后定稿回滚为初版结构:控制面与 agent 用容器、watcher 用独立裸节点(不经容器),保留外部边界虚线框与端口归属标注

oFEP-0006:FluxSandbox 可观测性指标体系设计

发布签核清单

概述

本提案为 FluxSandbox 建立从零到一的 Prometheus 指标体系:以意图账本状态机为黄金信号、以现有 slog 分段计时日志为口径母本、以 oFEP-0001 的性能硬目标(单沙箱调度时延 <50ms、并发创建吞吐 >3000 实例/s、单节点密度 >1000)为 SLO 锚点,定义约 120 个指标(含标签体系、埋点位置、暴露端点、告警语义与实施分期),并使本指标的 SLI/SLO 可直接回填 oFEP-0001~0005 全部空白的「监控要求」章节。

动机

背景

  1. 指标现状为零(已核验):全仓库无 /metrics 端点、无 prometheus 业务代码引用(client_golang v1.23.2 仅为间接依赖)、charts 无抓取端口/注解/ServiceMonitor、gRPC 无拦截器(internal/merged/grpc.go:38-47)、HTTP 仅有一个只记非 2xx 的日志中间件(internal/flux-sandbox-scheduler/server.go:194-215)。
  2. 口径母本已存在:全链路 slog 分段计时日志已定义了完整的时延分段语义,且压测工具 tools/sandbox-benchmark/collector.go:266-295 已在解析这些字段——controller handler_ms/resolve_ms/scheduler_rpc_ms/total_ms(internal/flux-sandbox-controller/controller/controller.go:411-416)、scheduler allocate_ms/agent_rpc_ms/attempts(internal/flux-sandbox-scheduler/sandbox_controller.go:425-429)、agent total_ms/pull_ms/create_ms/start_ms(internal/agent/runtime/containerd.go:358-369,代码注释明确"slog text format so sandbox-benchmark can parse")与 run_pod_ms/ip_status_ms(internal/agent/runtime/e2b.go:344-347)。
  3. 日志承载观测不可持续:30k 并发压测下日志量巨大,代码已出现"有意合并 timing 日志"的取舍(internal/merged/sandbox_provider.go:195-197 注释),日志聚合不宜作为常驻观测手段。
  4. 监控要求章节为空模板:oFEP-0001/0002/0004/0005 的「监控要求」章节均未填写;仅 0003 填写过唯一一条 SLO(拓扑域沙箱数偏差 ≤ 均值 20%)。
  5. 实测基线(test/性能验证-v0.1.md):30000 并发下 QPS 630-1075、p99 26.5-46.9s——时延直方图桶设计需同时覆盖 1ms(目标下限)与 60s(实测 p99 上限)。

目标

  • 建立统一指标命名/标签规范与三个二进制的 Prometheus 暴露端点,全部入口(FastPath HTTP :9090、SandboxProvider gRPC :9091、心跳 intake :9092)的请求量/时延/结果可抓取
  • 意图账本六态状态机(bound/host_lost/restoring/paused/terminating/orphaned)的状态分布、每次 CAS 迁移、冲突与停留时间全量可见
  • 创建链路三层分段时延指标化,分段口径与现有 slog 日志字段一一对应,压测工具可平滑迁移
  • 拒绝/失败根因标签化(503 的 reason 逐分支计数),容量仲裁 Lua 的接受/拒绝可观测
  • 定义与 oFEP-0001 目标对齐的 SLI/SLO,可直接回填各 oFEP 监控要求章节
  • 观测开关可配置、可回滚,热路径开销可度量(目标 ❤️%)

非目标

  • 不建设日志采集与链路追踪体系(OpenTelemetry 引入见替代方案;本提案仅指标)
  • 不替代心跳通道:心跳(agent→scheduler 5s)继续承载容量/活性类数据,指标端点仅补充 Prometheus 抓取面
  • 不新建告警平台:仅定义告警语义建议,规则资产化归运维侧
  • 不改变业务逻辑:所有埋点为旁路观测,CAS/仲裁/调度路径零行为变更
  • 首期不覆盖沙箱网络流量字节级采集(tap/veth 路径需从零设计,列为后续演进)

提案

用户故事

Story 1:SRE 排查创建 503 根因

现状:503 只能翻各副本日志定位(ssg_not_synced?no_available_agent?capacity_exceeded?)。本提案后:flux_scheduler_create_rejected_total{reason=...} 一条 PromQL 给出根因分布;flux_viewlayer_reserve_total{result} 区分"Lua 仲裁拒绝"与"本地预留失败"。

Story 2:容量运营与扩容决策

现状:节点容量/水位只能靠心跳日志与 Redis 人工查询。本提案后:flux_registry_capacity{kind=capacity|allocated|idle}、flux_sandbox_ledger_rows{state}(含 paused 驻留时长)构成容量看板,SADD/SREM 对账动作(flux_capacity_reconcile_actions_total)暴露视图漂移。

Story 3:性能回归看门

现状:性能验证依赖压测工具离线解析日志。本提案后:CI 压测直接对比 flux_controller_create_phase_seconds / flux_scheduler_allocate_duration_seconds / fluxsandbox_agent_create_total_seconds 的分位数与基线,分段回归一眼定位(调度退化 vs agent 退化 vs 镜像拉取退化)。

Story 4:SLO 治理与 oFEP 回填

本提案的 SLI/SLO 定义按 oFEP 监控要求模板作答,直接回填 0001(调度引擎)、0002(E2B 运行时)、0005(增量快照)的空白章节,完成监控治理闭环。

风险与缓解措施

风险 影响 缓解
标签基数爆炸(沙箱维度标签) Prometheus 序列膨胀 沙箱 ID 仅用于资源用量类指标(密度上限 1000/节点,序列预算见可扩展性节);其余指标标签全部为低基数枚举
热路径埋点开销(CAS 中心点、Lua 仲裁、Op 层直方图) 创建 P99 退化 Counter/直方图记录为 O(1) 预分配 children 操作;埋点开销基准纳入测试计划(❤️%);必要时对分段直方图降采样(参考 AgentENV 1/1024 先例)
多副本重复计数 全局聚合失真 各副本序列天然带 instance/Pod 标签;leader 任务指标用 flux_leaderrun_leading==1 过滤;容量类 gauge 取 max by (ssg) 而非求和
双轨期口径漂移(日志 vs 指标) 压测基线断裂 分段命名一一继承日志字段名;字段名纳入单元测试契约;日志侧不删(降 Debug 级)保留双读
/metrics 暴露面安全 信息泄露 端点仅暴露枚举型标签与聚合值,无沙箱明细;charts 按 loopback/管理网络 + ServiceMonitor 暴露(与部署文档既有建议一致)
ListRows 全表扫描规模风险被指标放大 抓取变慢拖垮账本 行数/停留时间改为周期导出器(leader 任务内聚合)而非抓取时实时扫描;flux_grpc_provider_list_rows_projected 观测规模上限

设计细节

整体架构

图例 含义
实线箭头 现有请求/数据路径,线上组件即埋点位置
虚线箭头 Phase-4 规划采集(尚未实现,边标签自带「Phase-4 规划」前缀)
虚线边框节点 仓库外组件——上游调用方 OpenSandbox Server(无指标)、下游监控端 Prometheus(指标消费方)
圆柱 存储(MySQL 意图账本 / Redis 视图层)
%%{init: {'flowchart': {'nodeSpacing': 60, 'rankSpacing': 70}}}%%
flowchart TB
    classDef ext stroke-dasharray: 5 5

    OS["OpenSandbox Server(仓库外 · 无指标)"]
    OS -->|"gRPC 客户端,拨号 :9091"| GRPC

    subgraph CP["合并控制面 flux-sandbox-scheduler-evolution(多副本,监听三个端口)"]
        GRPC["SandboxProvider gRPC(监听 :9091)"]
        FP["FastPath HTTP(监听 :9090)"] --> MW["HTTP 中间件(升级 loggingMiddleware)"]
        GRPC --> IC["gRPC UnaryInterceptor(新增)"]
        HB["agent 心跳 intake(监听 :9092)"] --> HH["HeartbeatHandler 埋点"]
        MW --> OPS["Op 层 ×5(HTTP 与 inline 双路汇合)"]
        IC --> OPS
        OPS --> SC["SandboxController(fork-join 双腿)"]
        SC --> CAS["casRowPhase 中心埋点(账本 CAS)"]
        CAS --> DB[("MySQL 意图账本")]
        SC --> REG["registry 调度"]
        REG --> VL["viewlayer 接口(Lua 容量仲裁)"]
        VL --> RD[("Redis 视图层")]
        SSG["SSG 编排 / leaderrun 周期任务 ×5"] --> K8S["K8s API"]
        SC --> PR["Registry · 控制面"]
        CAS --> PR
        REG --> PR
        VL --> PR
        SSG --> PR
        HH --> PR
    end
    subgraph AG["数据面 flux-sandbox-agent"]
        MGR["runtime manager"] --> RT["containerd / E2B runtime"]
        MGR --> RPT["reporter 心跳(5s 版本化向量)"]
        RT -. Phase-4 规划(新采集) .-> ST["Task.Stats / 事件订阅"]
        MGR --> PA["Registry · agent"]
        RT --> PA
        RPT --> PA
    end
    SW["sandbox-watcher(DaemonSet · 节点级孤儿清理)"] --> PW["Registry · watcher"]
    PR --> P[("Prometheus(Pull 抓取 · 运维侧)")]
    PA --> P
    PW --> P

    class OS ext
    class P ext

读图与走线说明

外部边界(两端):本指标体系的边界是「三个仓库内进程」构成的闭环,图中的虚线框节点均在边界之外——

  • 上游调用方 OpenSandbox Server:仓库之外的上级系统(openFuyao/opensandbox),产品链路为「用户 OpenSandbox SDK → OpenSandbox Server →(gRPC)→ FluxSandbox」。它作为 gRPC 客户端拨号 FluxSandbox 监听的 :9091,不产生本体系的任何指标——指标从 FluxSandbox 的三个监听端口开始计数,SLI(如 flux_grpc_provider_duration_seconds{method="CreateSandbox"})测的是 FluxSandbox 对它承诺的服务时延,不含 Server 自身与用户网络段。
  • 下游监控端 Prometheus:运维侧提供的监控基础设施(详见「依赖项」),仅从三个端点消费指标(Pull 抓取),不向体系注入任何数据。

端口归属:三个端口由同一个进程(合并控制面)监听——

端口 服务 谁来连它
:9090 FastPath HTTP 内部/测试快路径(HTTP 形态的同一套能力)
:9091 SandboxProvider gRPC(10 个 RPC) OpenSandbox Server(外部)
:9092 agent 心跳 intake 数据面 agent(5s 一次)

一次 create 请求的指标触发走线(对照上图从上到下):

步 组件 动作 触发指标
1 OpenSandbox Server 发起 CreateSandbox (外部系统,无指标)
2 SandboxProvider :9091 gRPC 进入 flux_grpc_provider_requests_total / _duration_seconds{method=CreateSandbox}(UnaryInterceptor 一处覆盖 10 个 RPC)
3 controller 路由解析 + 重试 flux_controller_create_phase_seconds{phase=handler/resolve/rpc/total}
4 Op 层 HTTP 与 inline 双路汇合 flux_scheduler_op_total / _op_duration_seconds{op=create}——埋这一层即可同时覆盖 FastPath HTTP 与 gRPC inline 两种入口
5 腿A:调度 registry 分配 agent flux_scheduler_allocate_duration_seconds{mode=local/viewed}、flux_scheduler_allocate_attempts
6 腿A:仲裁 viewlayer Lua 容量裁决 flux_viewlayer_reserve_total{result=accepted/rejected}(多活唯一裁决点)
7 腿A:派发 agent HTTP 创建 flux_scheduler_agent_rpc_duration_seconds{op=create}
8 腿B:账本 casRowPhase CAS 写入 flux_sandbox_ledger_state_transitions_total{from=∅,to=bound,reason=…}
9 agent runtime 执行创建 fluxsandbox_agent_create_total_seconds(containerd:pull/create/start 分段;E2B:run_pod/ip_status 分段)
10 收尾 任一环节失败 flux_scheduler_create_rejected_total{reason=…}(503 根因分布)

三个收敛埋点:全部埋点只写三处——①入口层(HTTP 中间件 + gRPC 拦截器,两处代码覆盖三个端口的所有请求);②账本中心(casRowPhase 一个函数覆盖约 19 个 CAS 迁移调用点);③依赖接口层(intentstore.Store / viewlayer.ViewLayer 两个 Go interface 包装覆盖全部 MySQL/Redis 访问)。

后台任务线(无外部请求的自驱循环):SSG 编排(attach/restore/scale/status 四阶段)与 leaderrun 的 5 个周期任务(10s/30s 节奏)没有请求进来,其指标形态是阶段耗时直方图 + flux_leaderrun_task_last_success_timestamp 停摆检测(时间戳不更新 = 任务卡死)。注意:账本六态行分布 gauge(flux_sandbox_ledger_rows{state})挂在 view-sweep 任务里周期导出,不在抓取路径上实时扫表——这是图上看不出来但关键的设计。

三个 Registry 与 Pull/Push 分工:三个 Registry 不是三个服务,而是三个进程各自内存中的指标集合——按二进制边界隔离(watcher 单独重启不影响控制面;agent 侧数千序列不污染控制面基数)。Prometheus(仓库外监控基础设施,见「依赖项」)以 Pull 定时抓取三个端点(进程挂掉序列自然变 stale,无僵尸数据);心跳通道(agent → :9092)是 Push 语义、承载调度器要实时消费的容量/向量数据——观测数据走 Pull 端点、调度数据走心跳,两条通道语义不混用。

设计原则小结:

  1. 三套 Registry、三个抓取端点:合并控制面与 agent/watcher 各自独立 Registry,互不污染基数与生命周期
  2. 三个收敛埋点层:入口面(middleware + interceptor 两处覆盖全部入口)、账本中心(casRowPhase 一处覆盖全部 CAS 迁移)、依赖接口层(intentstore.Store / viewlayer.ViewLayer 两个接口包装覆盖全部 DB 访问)
  3. 心跳与指标分工:心跳继续承载"需要调度器实时消费"的容量/向量数据(Push 语义);指标端点承载"监控系统周期抓取"的观测数据(Pull 语义),不混用

指标命名与标签规范

  • 命名:<组件前缀>_<对象>_<动词复数/单位> snake_case,组件前缀即二进制/子系统边界:flux_scheduler_*(合并控制面)、fluxsandbox_*(agent)、fluxwatcher_*、flux_intentstore_* / flux_tm_*(依赖层)。Counter 一律 _total 后缀,时延一律 _seconds
  • 标签白名单(低基数枚举):state(账本六态)、op(create/delete/pause/resume/snapshot/status…)、reason(拒绝/迁移根因枚举)、result(ok/error/conflict/not_found/rejected…)、runtime_type(containerd/e2b)、stage(链路分段,继承日志字段名)、mode(local/viewed)、strategy(round_robin/random)、rpc(9 方法白名单)、method(gRPC 10 方法)、gate、task(leader 任务名)
  • 禁止项:沙箱 ID 不进入控制面指标标签;时间戳不做标签;未枚举的动态值折叠为 unknown/unmatched
  • 语义分列:error(故障)与 conflict(CAS 乐观锁竞争)分列——前者反映故障,后者反映多活并发强度;not_found(幂等正常路径)与 error 分列;水位型 gauge 与速率型 counter 在告警语义上分列(见告警建议)
  • 桶配置:时延直方图统一桶 [0.001, 0.002, 0.005, 0.010, 0.025, 0.050, 0.100, 0.250, 0.500, 1, 2.5, 5, 10, 30, 60] 秒——下端覆盖 <50ms SLO 精度,上端覆盖实测 p99 46.9s;DB 快路径如需亚毫秒精度,逐指标用 Matcher::Full 精确覆盖(参考 AgentENV 的全局桶+白名单覆盖模式)

埋点架构设计

收敛点 覆盖范围 实现位置
HTTP 中间件(升级现有) FastPath 全部路由的 request/duration,及 readyz 判定 gate internal/flux-sandbox-scheduler/server.go:194-215(statusRecorder 已有,补 counter+histogram)
gRPC UnaryInterceptor(新增) SandboxProvider 全部 10 个 RPC 的 method/duration/grpc_code internal/merged/grpc.go:38-47 挂载;rpc 白名单对齐 proto 方法集
Op 层计时(既有共用体收口) create/delete/pause/resume/snapshot 五操作,天然覆盖 HTTP 与 inline 双路径 CreateSandboxOp 等 5 个 Op(server.go:353-644)
账本中心埋点 全部 CAS 状态迁移(约 19 个调用点收敛为 1 处 + 少量直接 CAS) casRowPhase(internal/flux-sandbox-scheduler/intent_ledger.go:85-101)+ PutIntent/MarkOrphaned/Delete 直调点
依赖接口包装 MySQL 16 个 op、Redis 13 个命令面、TM 8 个 op、Agent HTTP intentstore.Store(store.go:113-162)、viewlayer.ViewLayer(viewlayer.go:128-183)、templatemanager/client.go 包装层
状态机周期导出器 账本行分布/停留时间/TTL(不侵入抓取路径) 挂入现有 leader 任务 view-sweep(30s,internal/merged/leader_tasks.go)
Agent 埋点 创建分段(改日志点为打点)、删除/暂停/恢复/快照计时、镜像、心跳 manager.go/containerd.go/e2b.go 各 timing 日志处与分支点
Go 内建指标 runtime/process + client-go rest_client_*、workqueue_*、leader_election_* 注册即得(controller-runtime 已在依赖树)

指标体系定义

按观测对象分八层定义。就绪度标注:A=数据现成只需导出、B=分支已存在只需计数/直方图、C=需全新采集逻辑。

第 1 层:入口 SLI(对齐 SLO 的直接指标)

指标 类型 标签 语义 埋点 就绪度
flux_scheduler_http_requests_total Counter route,method,code FastPath 请求计数(route 白名单化,未匹配折叠 unmatched) 升级 loggingMiddleware B
flux_scheduler_http_request_duration_seconds Histogram route 同上时延 同上 B
flux_scheduler_op_total / _op_duration_seconds Counter/Histogram op,ssg,result 五操作 Op 层计数/时延(覆盖 HTTP+inline 双路径;CreateSandboxOp 现有 total_ms 日志直改) server.go:353-644 B
flux_scheduler_create_rejected_total Counter reason=ssg_not_found/ssg_not_synced/no_available_agent/capacity_exceeded/invalid_request/timeout 503 根因分布 server.go:359,363,405-408 B
flux_scheduler_delete_misroute_total Counter reason 删除跨副本误路由(幂等分支单列) server.go:464-477 B
flux_scheduler_operation_timeout_expired_total Counter op 操作超时(独立 ctx) 5 处 context.WithTimeout B
flux_controller_create_phase_seconds Histogram phase=handler/resolve/rpc/total,ssg controller 四分段(现有 handler_ms/resolve_ms/scheduler_rpc_ms/total_ms 直改) controller.go:411-416 B
flux_controller_create_route_retry_total Counter sandbox_group,failed_ssg 503 换路重试 controller.go:345-384 B
flux_controller_not_ready_rejected_total Counter op controller 未就绪拒绝 ErrControllerNotReady 5 处 B
flux_grpc_provider_requests_total / _duration_seconds Counter/Histogram method,grpc_code SandboxProvider 10 RPC UnaryInterceptor B
flux_grpc_provider_list_rows_projected Histogram — ListSandboxes 全表投影行数(规模风险观测) sandbox_index.go:90-112 B
flux_grpc_inflight_streams Gauge — gRPC 并发流(对照 MaxConcurrentStreams=1000) grpc.go B
flux_scheduler_heartbeat_requests_total Counter result=ok/unauthorized/bad_request/view_unconfigured/view_error/vector_error 心跳 intake 六类结果 heartbeat.go:66-115 B
flux_scheduler_heartbeat_apply_total Counter applied,need_images Lua 心跳门结果(need_images=1 即镜像向量失配信号) viewlayer.go:397-400 B
flux_scheduler_heartbeat_replay_rejected_total Counter — epoch/seq 回放拒绝 viewlayer.go:336-340 B

第 2 层:意图账本状态机(黄金信号)

指标 类型 标签 语义 埋点 就绪度
flux_sandbox_ledger_rows Gauge state 六态行分布(周期导出器,随 leader view-sweep) ListRows 聚合 A
flux_sandbox_ledger_row_state_dwell_seconds Gauge/Histogram state 状态停留时长 now-last_seen;paused 行无刷新,停留=精确暂停时长 同上 A
flux_sandbox_ledger_row_age_seconds Gauge — 意图年龄 now-created_at 同上 A
flux_sandbox_ledger_ttl_remaining_seconds Gauge — expires_at-now(TTL 预算 vs 实际生存) 同上 A
flux_sandbox_ledger_state_transitions_total Counter from,to,reason,result 全部 CAS 迁移(reason:ttl_expiry/agent_dead/runtime_gone/autorecreate_ok/autorecreate_failed/pause/resume/ghost/orphan/foreign_gc) casRowPhase 中心 + 直调点(intent_ledger.go:87-97,185,206,222,245,256-313、sandbox_controller.go:391,412,812,860,952,1176、crossreplica.go:154,247,336、loop.go:987、leader_tasks.go:144,192) B
flux_sandbox_ledger_cas_conflicts_total Counter from,to CAS ok=false 竞争(与 error 分列,反映多活并发强度) 各 ok==false 分支 B
flux_sandbox_ledger_recreate_attempt_total Counter/Histogram ssg,result AutoRecreate 进入/成功/六类失败回退(intent_ledger.go:245,256-304,313) 中心埋点 B
flux_sandbox_ledger_gc_deleted_total Counter kind=orphan/expiry/foreign_orphan/foreign_expiry/teardown GC 收敛删除 intent_ledger.go:331,356 等 B

第 3 层:调度与容量

指标 类型 标签 语义 埋点 就绪度
flux_scheduler_allocate_total / _allocate_duration_seconds Counter/Histogram ssg,result / mode=local,viewed 调度决策结果与时延(现有 allocate_ms 直改) registry.go:469-496,564,820 B
flux_scheduler_allocate_attempts Histogram ssg 跨 agent 重试次数分布(现有 attempts 字段) sandbox_controller.go:429 B
flux_scheduler_allocate_candidates Histogram ssg 候选 agent 数 registry.go:773,782 B
flux_scheduler_allocate_reserve_race_lost_total Counter ssg,agent Lua 预留竞争失败 registry.go:798-800 B
flux_scheduler_allocate_queue_wait_seconds Histogram ssg agent 忙排队等待(200ms×3) sandbox_controller.go:150-183 B
flux_scheduler_try_reserve_rejected_total Counter reason=not_ready/draining/no_capacity/port_conflict/resource_conflict 本地预留拒绝 registry.go:324-338,537-551,730-767 B
flux_scheduler_agent_rpc_duration_seconds Histogram op,caller=loop/op/crossreplica/restore agent HTTP 调用(三种超时 5s/30s/60s 可从 caller 区分) agent_client.go B
flux_registry_capacity Gauge ssg,agent,kind=capacity/allocated/idle 节点容量水位(UpdateSummary 聚合) registry.go:1283-1337 A
flux_viewlayer_reserve_total / _release_total Counter ssg,result Lua 容量仲裁接受/拒绝(多活唯一裁决点) viewlayer.go:490-516 B
flux_viewlayer_live_agents / _agent_liveness / _liveness_degraded_total Gauge/Counter ssg / ssg,agent 视图活体数(Redis 降级分支单列) viewlayer.go:450-460、registry.go:442-454 A/B
flux_capacity_reconcile_actions_total Counter ssg,action=srem_orphan/sadd_backfill,result 容量视图双向对账(视图漂移信号) capacity_reconcile.go:99-120 B
flux_snapshotcache_lookups_total Counter kind=snapshot/usage/live,result=hit/miss/singleflight 三级快照缓存 viewlayer/snapshotcache/cache.go:134-255 B

第 4 层:编排与选主

指标 类型 标签 语义 埋点 就绪度
flux_ssg_reconcile_duration_seconds Histogram ssg,phase=attach/restore/register/scale/status Reconcile 各阶段(ssg_controller.go:126-342) 各段首尾 B
flux_ssg_restore_duration_seconds / _restore_reservations Histogram/Gauge ssg 恢复耗时(5min 上限)与账本预订恢复规模 restoreSSG:395-499 B
flux_ssg_scale_up_pods_total / _scale_down_pods_total / _scale_duration_seconds Counter/Histogram ssg,result / direction 扩缩容(draining/二确认分支单列) scaleUp:656-702、scaleDown:764-820 B
flux_ssg_draining_agents / _synced Gauge ssg 排空数/恢复完成态 registry.go:1217-1226 A
flux_ssg_owner_reassigned_total Counter ssg,old_owner 死主 Pod 收编 ssg_controller.go:165-183 B
flux_ssg_status_write_errors_total / _reconcile_errors_total Counter ssg / ssg,stage CR patch 失败与 Reconcile 错误(含 loop 静默冲突 loop.go:255-258) 各 err 分支 B
flux_router_route_table_size Gauge — 跨副本路由表规模 route_sync.go:89-148 A
flux_leaderrun_leading Gauge lease,identity 选主态(聚合时过滤用) leaderrun.go:160-164 A
flux_leaderrun_leadership_changes_total Counter event=acquired/lost 主切换 leaderrun.go:140-150 B
flux_leaderrun_task_runs_total / _task_skipped_ticks_total / _task_last_success_timestamp Counter/Gauge task 5 个 leader 任务(10s/30s 周期)的运行/跳过/停摆检测 leaderrun.go:200-223 B
flux_scheduler_ready / _informer_synced / _startup_duration_seconds Gauge/Histogram gate=ssg_informer/ssg_restored/controller_router / informer / stage 就绪门与启动分段(补齐 readyz 与 controller Ready 双链路可观测) server.go:221-242、main.go:234-446 B
client-go 内建 rest_client_* / workqueue_* / leader_election_* — — K8s 访问与队列(QPS=100/Burst=200 节流观测) 注册即得 A

第 5 层:数据面(agent)

创建链路分段(口径照抄现有日志字段,B 就绪):

指标 分段 埋点(现有日志处)
fluxsandbox_agent_create_duration_seconds{runtime_type,outcome} 总口径 server.go:234-241
fluxsandbox_agent_manager_create_duration_seconds manager 段(锁 convoy 观测) manager.go:171-177
fluxsandbox_agent_runtime_create_duration_seconds{runtime_type} runtime 段 manager.go:172-177
fluxsandbox_agent_create_pull_seconds / _create_container_seconds / _create_task_start_seconds / _create_total_seconds{image} containerd 四段(pull_ms/create_ms/start_ms/total_ms) containerd.go:358-369
fluxsandbox_agent_create_runpod_seconds / _create_ipwait_seconds{template_id} E2B 两段(run_pod_ms/ip_status_ms) e2b.go:344-347

其余操作与事件(B 就绪):

指标 语义 埋点
fluxsandbox_agent_image_pull_total{image,result=cache_hit/pulled/error} 镜像命中/拉取/失败 containerd.go:387-397、e2b.go:555-566
fluxsandbox_agent_create_retries_total{stage=snapshot_exists/task_exists/task_exists_backoff} 创建重试 containerd.go:219-238,285-339
fluxsandbox_agent_create_rejections_total{reason} 校验/容量/幂等/terminating 拒绝 manager.go:98-159
fluxsandbox_agent_sandboxes{runtime_type,phase} / _capacity / _delete_failures 活跃数/容量/delete_failed 深度(内存真值) manager.go:147-159,344-348,403-415
fluxsandbox_agent_delete_duration_seconds{path=sync/async,mode=hints/label_lookup} / _delete_total{result} / _delete_sigkill_total / _delete_retry_total 删除链路(当前零计时):同步 15s/异步 10s+5s、SIGTERM→SIGKILL 升级(10s)、30s 重试循环 manager.go:273-385、containerd.go:597-669
fluxsandbox_agent_pause_duration_seconds / _resume_duration_seconds{source} / _snapshot_duration_seconds{op=checkpoint/pause} / _unsupported_op_total{op} 仅 E2B 支持(containerd 返回 ErrNotSupported,runtime.go:32) e2b.go:587-682、containerd.go:839-851
fluxsandbox_e2b_cri_calls_total{method,result} / _admin_calls_total{method,result} CRI 7 方法 + admin 2 方法 e2b.go 调用点
fluxsandbox_e2b_label_lookup_fallback_total{op} / _runpod_failures_total agent 重启后 hints 退化 / 取 IP 失败回滚 e2b.go:330-335,375-385
fluxsandbox_heartbeat_send_total{result} / _packet_total{type=full/delta,anchor} / _repush_total 心跳健康(退避 1s→60s 现成;digest 变化或每 12 包 anchor) reporter.go:228-319

第 6 层:依赖访问层(接口收敛,一处包装全覆盖)

依赖 指标 覆盖 op
MySQL intentstore flux_intentstore_queries_total{op,result} / _query_duration_seconds{op,driver} / _tx_stage_total{op,stage} / _rows_scanned{op} + 连接池 db.Stats() 16 个 op(store.go:330-672);not_found 单列;sqlite 池=1、mysql 默认池可观测
Redis viewlayer flux_viewlayer_ops_total{op,result} / _op_duration_seconds{op} + PoolStats() 13 个命令面(viewlayer.go:305-616,含两条 Lua)
Template Manager flux_tm_requests_total{op,code} / _request_duration_seconds{op} 8 个 op(client.go:107-322;disabled 分支单列)

第 7 层:孤儿清理(sandbox-watcher)

指标 语义 埋点 就绪度
fluxwatcher_scan_duration_seconds{path} 孤儿扫描(周期 2min 现成) scanner.go:31-50、controller.go:47 B
fluxwatcher_orphans_detected_total{reason=AgentPodDisappeared/ManifestMissing/SandboxNotReady,runtime,shape} 三类孤儿判定(判定函数现成) scanner.go:439-470 B
fluxwatcher_cleanup_duration_seconds{runtime,result} / _cleanup_gate_abort_total{gate} 清理链(SIGTERM→10s→SIGKILL→5s→Delete)与四个安全门中止 cleanup.go:52-258 B
fluxwatcher_queue_depth / _cleanup_requeues_total / _cleanup_dropped_total / _grace_tracking / _manifest_probe_total{result} 队列/宽限跟踪/manifest 探测 controller.go:140-168、types.go:99-101 A/B

第 8 层:资源用量(Phase-4,全新采集)

指标 方案
fluxsandbox_sandbox_cpu_seconds_total{sandbox,image} / fluxsandbox_sandbox_memory_working_set_bytes{sandbox,image} containerd client 已建立(containerd.go:80):task.Stats(ctx) 直采;或按 task PID(metadata 已存 :379)反推 cgroup v2 读 cpu.stat/memory.current(e2e 已验证路径可得,test/e2e/dataplane/sandbox_lifecycle_test.go:269-301);依赖 containerd/cgroups/v3 已在 go.mod。采集器设计:manager 侧周期任务遍历活跃沙箱向量(manager.go:403-411),并发度按密度自适应
fluxsandbox_sandbox_spec_limits{cpu_quota,mem_bytes} 声明值(WithCPUCFS/WithMemoryLimit 现成,containerd.go:544-558;E2B 模板 vcpu/ram templatemanager/types.go:34-36)——用量/限额比即饱和度
fluxsandbox_containerd_task_exit_total{exit_code} / fluxsandbox_containerd_oom_total 无事件订阅现状:client.Subscribe(events 服务)或 per-task task.Wait;OOM 亦可读 cgroup memory.events
fluxsandbox_containerd_task_status{state} 轮询聚合现成(containerd.go:732-750,771-803,随 5s 心跳带出,A 就绪)

SLO 定义

对齐 oFEP-0001 性能目标,测量点显式声明(边界以 0001 定义为准):

# SLO SLI(指标 / 聚合 / 暴露组件) 说明
S1 单沙箱调度时延 P99 < 50ms histogram_quantile(0.99, rate(flux_scheduler_allocate_duration_seconds_bucket[5m])),暴露组件 scheduler 纯调度决策段(mode=local/viewed 合并);创建 E2E 分段由 flux_controller_create_phase_seconds{phase="total"} 与 fluxsandbox_agent_create_total_seconds 分别归因
S2 并发创建吞吐 > 3000 实例/s sum(rate(flux_scheduler_op_total{op="create",result="ok"}[1m])),暴露组件 scheduler 压测场景验收口径;持续型流量下以 flux_grpc_provider_requests_total{method="CreateSandbox"} 对照
S3 入口可用性:5xx 比例 ≤ 1% sum(rate(flux_scheduler_http_requests_total{code="5xx"}[5m])) / sum(rate(flux_scheduler_http_requests_total[5m])),暴露组件 scheduler/gateway 双面 client_closed(客户端断连)不计入 5xx(网关侧语义),调度器侧以 code 原值计
S4 账本健康:host_lost 收敛 flux_sandbox_ledger_rows{state="host_lost"} 水位 + flux_sandbox_ledger_recreate_attempt_total{result="failed"} 速率,暴露组件 scheduler 异常检测型 SLO:持续增长 = agent 集群健康恶化或自愈失效
S5 心跳管道健康:视图失配率 flux_scheduler_heartbeat_apply_total{need_images="1"} / 总量,暴露组件 scheduler 高位 = agent 镜像缓存漂移

资源用量采集设计(Phase-4 细化)

  • 采集器形态:agent 内周期任务(与心跳同节奏 5s,错峰抖动),遍历活跃沙箱;containerd 路径优先 task.Stats()(单调用返回 CPU/内存/pids),失败降级 cgroup v2 直读
  • 序列预算:每沙箱 2 指标 ×2 标签组 ≈ 4 序列,密度上限 1000/节点 → 单 agent 峰值 ~4000 序列,加控制面全局 < 1000 序列——Prometheus 单实例预算内
  • 暂停态语义:paused 沙箱无 runtime,序列消失(而非归零)——与账本 paused 行驻留时长指标互补表达
  • 退出/OOM 事件:Subscribe 事件流落 counter;OOM 交叉验证 cgroup memory.events,与创建规格声明值联动产出"限额饱和度"

日志到指标的迁移设计

  1. 口径继承:分段名 = 现有 slog 字段名(handler/resolve/rpc/total/allocate/agent_rpc/runtime/pull/create/start/run_pod/ip_status),压测工具 sandbox-benchmark 的 collector 字段映射表保持有效
  2. 双读过渡:日志侧不删除,级别降至 Debug(server.go:411-413 等处);collector 增加 Prometheus 抓取模式,压测报告字段来源切换可配置
  3. 契约锁定:分段 label 值与指标名纳入单元测试断言(参考 AgentENV overlaybd 的"指标行为进入单元测试"实践),防止重构漂移

测试计划

[ ] 我/我们理解,相关组件的所有者可能会要求更新已有的测试,以便在提交实现该增强功能所需的更改之前,使代码达到足够稳固的质量标准。

单元测试
  • 各埋点函数的指标注册、标签基数断言(白名单外值折叠 unknown)、空序列抑制语义
  • CAS 中心埋点:六态全迁移矩阵的 reason 标签正确性(表驱动用例)
  • 中间件/拦截器:code 分桶(1xx-5xx/other)、not_found 与 error 分列
集成测试
  • e2e 创建/删除/暂停/恢复全流程后,/metrics 抓取断言对应序列出现且 label 正确(含 ledger_state_transitions_total 的 from/to 链)
  • leader 任务停摆模拟:kill leader 后 flux_leaderrun_leadership_changes_total{event="lost"} 与接管方 acquired 均可见
  • watcher 孤儿注入:orphans_detected_total{reason=...} 与清理结果计数
性能测试
  • 开销基准:同一压测脚本(3000 并发)对比 metrics 开关前后 QPS/p99,回归阈值 ❤️%
  • 30k 并发全量压测复跑:直方图桶高水位下 scrape 耗时(/metrics P99 < 200ms)与序列数确认在预算内

毕业标准

Alpha 阶段

  • 三个二进制 /metrics 端点就绪 + Go 内建指标 + 入口三面(HTTP/gRPC/心跳)request/duration + 账本六态 gauge 与迁移 Counter(第 1、2 层 + P0 基建)
  • 本 oFEP 合入 docs/design/

Beta 阶段

  • 第 3-7 层全量落地;创建链路分段直方图替换日志口径;charts 增加 metrics 端口/ServiceMonitor;SLO 看板与告警规则资产化;回填 oFEP-0001/0002/0004/0005 监控要求章节

GA 阶段

  • 第 8 层资源用量与事件订阅上线;开销基准达标(❤️%)并复测 30k 压测;压测工具完成指标口径迁移;监控要求章节随 GA 评审关闭

生产可用性审查

功能启用和回滚

  • 配置开关:[observability.metrics] enabled(默认 true,Alpha 起生效)+ listen_addr(默认挂 :9090 mux,可独立端口);disable-supported: true——关闭即回滚,业务路径零残留(埋点为旁路注册)
  • agent/watcher 各自独立开关,支持按组件灰度

推出、升级和回滚规划

  • Alpha 随下一版本发布:先合入 P0/P1(端点+内建+入口+账本),无配置变更即默认生效
  • Beta 起逐组件灰度(feature-gate 预留);指标名/标签在 Beta 后冻结(对外契约),变更需 oFEP 修订

监控要求

操作员如何确定该功能是否正在被工作负载使用?
  • 抓取 http://<scheduler>:9090/metrics(或独立 listen_addr),确认 flux_scheduler_http_requests_total 序列存在
  • flux_scheduler_ready == 1 且 flux_scheduler_informer_synced{informer="ssg_watcher"} == 1
使用此功能的人如何知道它适用于他们的实例?
    • 指标名称:flux_scheduler_op_total{op=...} / flux_sandbox_ledger_rows{state=...}
    • 聚合方法:sum by (op) / sum by (state)
    • 暴露指标的组件:flux-sandbox-scheduler-evolution(合并控制面)、flux-sandbox-agent、sandbox-watcher
    • 条件名称:SandboxGroup/SubSandboxGroup status 的 allocated/idle 聚合(现有 loop.go:244-259 merge-patch 链路)与本指标体系互为印证
增强的合理 SLO(服务级别目标)是什么?
  • 单沙箱调度时延 P99 < 50ms(对齐 oFEP-0001)
  • 并发创建吞吐 > 3000 实例/s(对齐 oFEP-0001)
  • 每天 5xx 响应比例 ≤ 1%
  • 账本 host_lost 行在 AutoRecreate 开启时 10 分钟内收敛(或告警人工介入)
  • 每天 99.9% 的 /healthz 请求返回 200
运维人员可以使用哪些服务级别指标(SLIs)来判断服务的健康状况?
    • 指标名称:flux_scheduler_allocate_duration_seconds(聚合:histogram_quantile(0.99, rate(…[5m])))、flux_scheduler_op_total(聚合:rate(…[1m]))、flux_sandbox_ledger_rows(聚合:sum by (state))、flux_sandbox_ledger_state_transitions_total{result="error"}(聚合:rate(…[5m]))
    • 暴露指标的组件:flux-sandbox-scheduler-evolution
是否存在任何尚未覆盖的指标(metrics),可以用来进一步提升该功能的可观测性?
  • 沙箱网络流量字节级指标(tap/veth 路径无采集代码,需从零设计,列入非目标)
  • MySQL/Redis 服务端自身指标(由 DB 运维体系承载,本体系仅覆盖客户端访问面)
  • guest 内进程级指标(需运行时增强,超出调度引擎边界)

依赖项

此功能是否依赖于集群中运行的任何特定服务?
  • Prometheus server(Pull 采集端,运维侧提供;K8s 部署建议 ServiceMonitor + prometheus.io/* 注解模式,对齐现有 ublk 类部署惯例)
  • github.com/prometheus/client_golang(已在 go.mod 间接依赖,本提案转直接依赖;其余零新增)
  • 可选:Grafana(看板)、Alertmanager(告警路由)——非强依赖

可扩展性

新的 API 调用 / 新 API 类型 / 云厂商 API / 对象大小数量
  • 无新 API/CRD;序列数预算:控制面全局 < 1000 序列(标签全枚举);单 agent 峰值 < 5000 序列(密度上限 1000 × 资源类 4 序列 + 静态项);账本行分布 = 6 态 × SSG 数
  • 抓取端点 P99 < 200ms(30k 并发场景实测校验,见测试计划)

故障排除

现象 排查路径
/metrics 返回 503 recorder 未初始化(启动时序),检查 enabled 配置与启动日志
端点 404 端口未暴露(charts 未加 metrics 端口)或独立 listen_addr 未配置
指标序列缺失 按层检查 export 开关与组件就绪度(flux_scheduler_ready);Agent 侧确认 runtime_type 标签
cas_conflicts_total 突增 多活副本数量/时钟检查;结合 flux_leaderrun_leadership_changes_total 判断是否选主风暴
heartbeat_apply_total{need_images="1"} 高位 agent 镜像缓存漂移:对照 fluxsandbox_heartbeat_packet_total{type="full"} 频率与节点镜像水位
抓取耗时高 检查 flux_intentstore_rows_scanned 是否账本规模异常;确认周期导出器(view-sweep)承担了聚合而非抓取时扫描

实施历史

日期 事件
2026-10-12 oFEP-0006 创建(provisional),基于《FluxSandbox 指标采集设计建议》分析底稿(GitCode rd-tan/SandboxSched issue #1)整理成设计文档

缺点

  • 指标名册约 120 个,注册与维护成本可观——以集中注册文件 + 单元测试契约缓解
  • 双轨过渡期(日志与指标并存)存在口径漂移风险——以"字段名继承 + 测试锁定"缓解,GA 后日志口径降 Debug
  • 第 8 层(资源用量/事件订阅)为全新采集逻辑,是本提案中唯一有实质实现工作量的部分,故单列 Phase-4

替代方案

方案 结论
OpenTelemetry 统一指标+追踪 现状无追踪栈,SDK/Collector 引入与改造成本高、收益主要是跨组件归因;保留为 GA 后演进路径(指标 API 层可平滑迁移 OTel SDK)
go-grpc-prometheus 库 引入统一 gRPC 指标,但其 rpc 标签粒度与本项目 9 方法白名单、grpc_code 分桶语义不完全一致;自写拦截器约 40 行且零依赖,胜出
Pushgateway / 心跳内嵌指标 Push 模式与实例生命周期解耦易产生陈旧序列;心跳通道已承载容量数据,再内嵌指标会混淆"调度数据"与"观测数据"两条语义——维持 Pull 端点 + 心跳分工
仅日志聚合(现状延续) 30k 并发日志量不可常驻,代码已出现合并 timing 日志的取舍;放弃

本文由 AI 代理基于 flux-sandbox-dev 源码逐点核验后整理(埋点位置 file:line 均经人工比对,oFEP 格式对齐 docs/design 现有 0001-0005 house style)。

Assisted-by: opencode:glm-5.3-flash

likedislike
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述
Rrd-tan成员
20 天前 修改了issue 的描述