OTLP Receiver 跨项目契约(TFRM-119 / TFRM-114 vNext)¶
对象:TFRO(tfrs-operator)/ TFRS(TFRobotServer)/ TFROB(TFRobot 内核)项目工程师 / 部署运维
目的:定义计费遥测链路(TFROB 内核 emit + TFRO Operator/Collector 转发 → TFRSManager OTLP Receiver → 中央 TimescaleDB of-record)的协议、字段责任、鉴权、轮换、幂等不变量, 使各项目可并行实施且不互相误导。
事实源:本 vNext 据 TFRM-124 三方对齐 R3 决策定稿(评论 #13187) 重写。任何与 §X 冲突的实现须回 TFRM-124 走升级路径, 不得在子工单内私自改契约。
修订历史¶
| 版本 | 日期 | 变更 | 来源 |
|---|---|---|---|
| v1(TFRM-119) | 初版 | Manager 单方撰写,未经 TFRO/TFRS ack | — |
| vNext | 2026-05-15 | 新增 §X 幂等不变量;字段表按 signal 拆表 A/B;鉴权据实(静态 header + Phase A/B gating);env 名统一 OTLP_BILLING_TOKEN;resource 链改 Alt-A2(logs 非 metrics);grace 改派生值;§8 Prometheus 指标实装 |
TFRM-124 #13187 R3 定稿;TFRM-125 落地 |
| v5 | 2026-07-01 | llm_billing_events 新增 manager_key_id(LLM 归属 correlation id = 主 PG llm_managed_keys.id,nullable,不列入 receiver 必填);网关退回哑计量器,org 归属由下游权威反查 |
TFRM-228 / TFRM-230(子2·串行卡点) |
| v6 | 2026-07-01 | gateway 模式 receiver 必填锚 organization_id → manager_key_id(filterValid fail-closed 锚随计费锚迁移);otlp_dropped_total reason organization_id → manager_key_id;ConsumptionScheduler LLM 计费改按 manager_key_id 权威反查归属 + 合并 upsert + 孤儿隔离,organization_id(denorm)不再进网关路径扣费决策 |
TFRM-228 / TFRM-231(子3) |
vNext 三方 + TFROB ack 矩阵(TFRM-124 #13187 §3)¶
| 决策项 | TFRS | TFRO | TFRM | TFROB |
|---|---|---|---|---|
| ns 共享 Collector(非 per-pod sidecar) | ✅ | ✅ | ✅ | n/a |
单一 /v1/logs egress |
✅ | ✅ | ✅ | n/a |
| 归属 Collector upsert / Server 零持有 | ✅ | ✅(C 改 upsert) | ✅ | n/a |
| §X.2 冻结不变量 — 传输纯透传 | ✅(mock 回归) | n/a | ✅ | n/a |
| §X.2 — LLM event_id/TimeUnixNano 冻结 | ⚠️ scope-out | n/a | ✅(规范) | ⛔ 待 ack(hard gate) |
| §X.2 — resource 量化冻结 (Alt-A2) | n/a | ✅(Go 量化) | ✅ | n/a |
| §X.3 fail-loud(删 receivedAt 兜底) | ✅(支持) | ✅(受益) | ✅(认领·已实装) | n/a |
| ConnM2L ❌ → Alt-A2 | ✅ | ✅(自提) | ✅(接受) | n/a |
| SLA = 队列持久+幂等,不锚投递时延(含 6min eviction 边界) | n/a | ✅(①③书面) | ✅(ack·与 β 捆绑) | n/a |
pod 锚点 = semconv k8s.pod.uid |
✅ | ✅(钉死) | ✅ | n/a |
| token: restart-on-sync + T_propagate≤8min;grace=派生值 | n/a | ✅ | ✅(grace 公式) | n/a |
| fan-out sink + per-cluster ramp + 对账 SQL | n/a | n/a(零影响) | ✅(认领·已实装) | n/a |
⛔ 唯一残留依赖(Epic 验收 hard gate):TFROB 非本三方成员。 TFROB-481 owner 必须在 TFRM-124 显式 ack vNext §X.2(LLM 行:内核 emit 一次性冻结
event_id+TimeUnixNano、重试复用、meter.py命名/单位与 logs attribute 1:1)。未 ack 前 Epic 端到端验收不可达。 升级路径:贴 TFROB-481 链接 + @owner 到 TFRM-124,限期 ack 或留反驳。
0. 授权范围与适用边界¶
- Admin API(token rotate/list/revoke):仅限 Manager 平台级管理员(
PermissionAdminManage), 没有 per-cluster 细粒度 ACL —— 拥有该权限的管理员可管理任意集群的 OTLP token。 当前业务规模下这是合理简化;如未来组织级 admin 分权出现,再扩展。 - OTLP Receiver 端点:Collector 持 Bearer token 直接调用,由 token 关联 cluster_id 强制隔离。
- 本文档不约束 Manager 内部 admin-service 的其他能力,仅约束 OTLP Receiver 暴露面。
1. 端点¶
| 环境 | URL | 后端 |
|---|---|---|
| 生产 | https://metrics.turingfocus.cn/v1/logs |
Manager 127.0.0.1:4318 |
| Staging | https://metrics-staging.turingfocus.cn/v1/logs |
Manager 127.0.0.1:4318 |
| Beta | https://metrics-beta.turingfocus.cn/v1/logs |
Manager 127.0.0.1:4318 |
域名说明:
metrics{,-staging,-beta}.turingfocus.cn沿用 VictoriaMetrics 时代已申请并部署好的证书与域名。TFRM-118 拆除 VM 后,该域名/Nginx conf 复用给 OTLP Receiver(不再新增otlp.*域名),权威清单见docs/deploy/02-network.md §域名规划。
- 协议:OTLP/HTTP,protobuf (
Content-Type: application/x-protobuf) - 仅接受 POST;Body 上限 8 MiB(超过返回 413)
- 单一入站 signal =
/v1/logs(OTel Logs/Events)。/v1/metrics、/v1/traces显式返回 403 (code 40301)。
信号模型(R3 定稿,修订 smell A):两类计费事件都以 OTel Logs signal 经
/v1/logs上行: - LLM 计费(event_type=llm_billing):TFROB 内核logger.emit()直接产 OTLP logs。 - 资源利用率(event_type=resource_utilization):由 TFRO Operator Alt-A2 发射器 周期读 kubelet/stats/summary、Go 量化冻结后直接 emit OTLP logs。Collector
kubeletstatsreceiver 退为仅 Prometheus 观测,不再参与计费。 R1 设想的「Collector 内 metrics→logs connector(ConnM2L)」按字面不可实现 (stockotel/opentelemetry-collector-contrib无 metrics→logs connector,OTTL 不跨信号), 已由 Alt-A2 取代(Operator Go 量化比 OTTL 更硬,见 §X.2 责任界)。 Manager 始终单一入站/v1/logs,零改动。
2. 鉴权¶
每个集群在 Manager admin-service 配发 一对 Bearer token(PRIMARY / SECONDARY), 通过 Infisical → ESO 同步给集群内 Collector。
- 鉴权据实(修订 smell 4):Collector
otlphttp/billingexporter 上是静态 headerauthorization: "Bearer ${env:OTLP_BILLING_TOKEN}",非headers_setter、非 auth pipeline。 - 链路 gating(修订 smell A):整条上行链 gated 于
TFRCluster.Spec.Billing.OTLPEndpoint非空 且Billing.Infisical != nil;代码层默认 Phase A = 不上行(debug-only)。 - Token 颁发:admin
POST /api/v1/admin/clusters/:cluster_id/otlp-tokens/rotate—— 响应只返回一次明文,丢失需重新 rotate。 - Token 在 DB 只存 sha256 哈希;明文存活在 Infisical:
- 失败行为:Nginx 层缺
Authorization→ 401;Manager 层错/撤销/过期 token → 401。
3. 强制 cluster_id 重写(重要)¶
Manager 始终用 token 关联的 cluster_id 覆盖 OTLP payload 中的 cluster_id
(ResourceLogs.Resource.attributes 与 LogRecord.attributes 两处都重写)。
含义:Collector 即使漏写/写错 cluster_id,落库值仍以 token 为准。其余 attribute
全部原样透传,Manager 不做任何重写。
字段契约 — 表 A:LLM-logs(signal=logs · event_type=llm_billing)¶
| 字段 | 产出方 | 注入方 | 决策 |
|---|---|---|---|
event_id(UUIDv4·冻结) |
TFROB 内核 | 内核 emit | ⚠️ blocked-by TFROB-481 §X.2 |
occurred_at=TimeUnixNano(冻结) |
TFROB 内核 | 内核 emit | ⚠️ 同上(β 命门) |
model_name/provider/prompt/completion/cached_input_tokens |
TFROB 内核 | 内核 emit | ⚠️ TFROB-481 确认 meter.py 命名/单位 1:1 |
tenant_name/namespace/organization_id/robot_id/cr_name |
— | Collector processor upsert | ✅ TFRO C 改 upsert |
manager_key_id(归属 correlation id·nullable) |
LLM Key 网关(vkey metadata) | 网关 billing.py emit |
✅ TFRM-230(= 主 PG llm_managed_keys.id;下游反查权威 org;per_cluster 恒 NULL) |
k8s.pod.uid(锚点) |
K8s downward API | Operator→OTEL_RESOURCE_ATTRIBUTES |
✅ 字面 = OTel semconv k8s.pod.uid(杜绝漂移) |
cluster_id |
— | Manager 鉴权层强制重写 | ✅ |
字段契约 — 表 B:resource-logs(signal=logs · event_type=resource_utilization · 经 Alt-A2 Operator 发射器)¶
| 字段 | 产出方 | 注入方 | 决策 |
|---|---|---|---|
event_id(量化哈希·冻结) |
TFRO Operator (Alt-A2) | Operator emit→LogRecord | ✅ Go 量化冻结(比 OTTL 更硬) |
occurred_at(量化 bucket·冻结) |
TFRO Operator | Operator emit | ✅ 同 event_id 量化值 |
resource_type/utilization/above_threshold |
TFRO Operator(读 kubelet/summary) | Operator emit | ✅ |
pod_name/container_name |
TFRO Operator | Operator emit | ✅ |
tenant_name/namespace/organization_id/robot_id/cr_name |
— | Collector processor upsert | ✅ |
cluster_id |
— | Manager 鉴权层强制重写 | ✅ |
列名注:hypertable 列 tenant_name 由 TFRM-121 从 tenant_id 重命名而来 —— 实际值是
K8s namespace name 字符串。organization_id / robot_id / cr_name 全 nullable
(空字符串 → sink 层 nilIfEmpty 落 NULL),兼容 personal 池 / 渐进接入。
manager_key_id(TFRM-230,仅 llm_billing_events):LLM Key 网关计费归属 correlation id,
值 = Manager 主 PG llm_managed_keys.id(十进制字符串,与 organization_id 同选 TEXT,中央 TS 与主 PG
物理隔离不能用 FK)。网关模式由 vkey user_api_key_metadata.manager_key_id emit(网关本次仅把已有
metadata 字段一并 emit,无需新增 metadata)。gateway 模式 receiver 必填校验锚(TFRM-231 起,
取代原 organization_id fail-closed,见 §8):无 correlation id 即不可归属,缺失=网关配置错,在边界
fail-loud 丢弃。per_cluster 内核/sidecar 旧链路无此字段恒 NULL、不校验(用可信 organization_id)。
反查不到 org(present-but-unresolvable,需查主 PG)不在 receiver 判定,由下游 ConsumptionScheduler
(子3)孤儿隔离——与「缺失即丢」是两类失败,分层处理。空属性 → NULL(*string nil),不落空串。
下游据此反查权威 org 归属,organization_id(denorm)降级为旁路对账证据(不再进网关路径扣费决策)。
⚠️ 与 llm_key_events.manager_key_id(= provider_keys.id)是不同实体(术语重载,勿混)。
§X 计费时间戳与幂等键不变量(规范性 · TFRM-124 #13187 R3 定稿)¶
这是整个 Epic「at-least-once + 幂等」的命门(根因 smell β)。三段责任,任何实现不得违背。
§X.1 幂等键(Manager of-record 口径)¶
去重键 = (event_id, occurred_at),无时间窗,落库 ON CONFLICT (event_id, occurred_at)
DO NOTHING。对投递时延 / 重试 / 多副本免疫 —— 当且仅当 §X.2 成立。
§X.2 冻结不变量(产出方硬契约)¶
event_id:逻辑事件创建时点一次性生成(LLM = UUIDv4;resource = 对采样 bucket 量化输入的确定性哈希),冻结;任何重传/重试/多副本复用同一字面值, 禁逐次now()/wall-clock。occurred_at载体 = OTLPLogRecord.TimeUnixNano,值 = 业务发生时刻 (LLM 调用结束 / resource 采样 bucket 量化时刻),与event_id哈希时间分量同一量化值; 写入后冻结,传输层(SDK BatchProcessor / exporter 重试 / Collector / Operator 发射器) 不得重写。- 责任界:LLM 链冻结归 TFROB 内核(TFROB-481 硬验收);resource 链归 TFRO (Alt-A2 Operator Go 量化);传输纯透传归 TFRS(SDK 不挂改时戳 processor + mock-collector 回归)。
§X.3 fail-loud(Manager 侧 · TFRM 认领 · 已实装 TFRM-125)¶
Manager 解析 occurred_at 仅取 TimeUnixNano(首选)/ ObservedTimeUnixNano(次选)。
两者皆空 → 判缺必填字段,进 filterValid 丢弃
(internal/shared/otlp/server/server.go,复用 event_id/tenant_name/namespace
的现有丢弃机制,新增 occurred_at 空值分支);已删除 buildEvent 中
default: ev.OccurredAt = receivedAt 兜底(兜底接收墙钟使重试得到不同 PK →
ON CONFLICT 不触发 → 重复计费)。丢弃同时:(a) 单条 zap.Warn 带 event_type/event_id;
(b) 计数 otlp_dropped_total{reason="occurred_at"},非零即告警(见 §8)。
4. 限流¶
- Manager 默认 per-cluster 100 rps,桶容量 200(
OTLP_RATE_PER_SECOND/OTLP_RATE_BURST可调)。 - 超过返回 429 + JSON body
{"code":42900,"message":"too many requests"}。 - Collector OTLP exporter 必须启用
retry_on_failure,遇 429 退避重试。
5. 集群内 Collector / Operator 配置(参考 · 全量定稿归 TFRO-44/45)¶
R3 拓扑:ns 共享 StatefulSet Collector(非 per-pod sidecar;
kubeletstats物理上须 node/ns 级),file_storage队列 PV-backed(VolumeClaimTemplatesper ordinal·RWO)。 完整 Collector logs pipeline + 归属 upsert +k8s.pod.uiddownward env 注入 = TFRO-44; Alt-A2 resource 发射器 = TFRO-45。本节仅给 Manager 关心的协议锚点,非 TFRO 实现规范。
exporters:
otlphttp/billing:
endpoint: https://metrics.turingfocus.cn # signal=logs → 实际打 {endpoint}/v1/logs(域名沿用 VM 时代,见 §1)
headers:
authorization: "Bearer ${env:OTLP_BILLING_TOKEN}" # 静态 header(非 headers_setter)
compression: gzip
retry_on_failure: { enabled: true, initial_interval: 5s, max_interval: 300s, max_elapsed_time: 6h }
sending_queue: { enabled: true, storage: file_storage/billing, num_consumers: 4, queue_size: 10000 }
extensions:
file_storage/billing:
directory: /var/lib/otelcol/billing-queue # PV-backed(StatefulSet VolumeClaimTemplates·RWO)
service:
extensions: [file_storage/billing]
pipelines:
logs/billing:
receivers: [otlp] # LLM 内核 + Alt-A2 resource 发射器汇流
processors: [batch, <归属 upsert processor>] # tenant_name/namespace/org_id/robot_id/cr_name + k8s.pod.uid
exporters: [otlphttp/billing]
环境变量 OTLP_BILLING_TOKEN(修订 smell ε,原契约误写 TFRS_OTLP_TOKEN)经 K8s Secret 注入:
tfrs-otlp-bearer 由 ESO(External Secrets Operator)从 Infisical
/otlp/{cluster_id}/PRIMARY_TOKEN 同步;Operator watch 该 Secret hash 注解触发
受控滚动重启(restart-on-sync,TFRO-46),令 secretKeyRef env 拿到新 token。
归属注入(修订 smell C):tenant_name/namespace/organization_id/robot_id/
cr_name 由 Collector logs processor 一律 upsert/override(Server 零持有,不以 Server
值为准);pod 锚点字面用 OTel semconv k8s.pod.uid(downward API →
OTEL_RESOURCE_ATTRIBUTES),杜绝命名漂移。
6. Token 轮换流程¶
- 运维操作:
POST /api/v1/admin/clusters/:cluster_id/otlp-tokens/rotate - Manager 内部:写新行(status=active)→ 旧 PRIMARY → Infisical
SECONDARY_TOKEN→ 新 raw → InfisicalPRIMARY_TOKEN→ 写 admin audit log → 响应携带 raw(仅此一次) - 集群侧传播(TFRO-46 restart-on-sync):ESO 同步
PRIMARY_TOKEN写tfrs-otlp-bearerSecret → Operator watch Secret → hash 注解 → Collector 受控滚动重启 拿到新 token。传播上界T_propagate ≤ 8min(默认)/≤4min(收紧),书面保证。 - 过渡期:旧 token(SECONDARY)在 grace 窗内仍 active,Collector 仍可用旧 token 推送。
- 运维收尾:grace 窗到期后 DELETE 撤销 SECONDARY
(
DELETE /api/v1/admin/clusters/:cluster_id/otlp-tokens/:token_id)。
grace = 派生值,非魔法常量(修订):
OtlpTokenRotationGrace() = max(运营底线 72h, T_propagate_max × 安全系数)(internal/admin/service/otlp_token_service.goderiveTokenRotationGrace)。 派生依据:grace 必须 ≥ §6 步骤 3 的T_propagate上界,否则旧 token 失效时 Collector 仍持旧 token → 401 风暴;运营另设 72h 底线(值班响应余量)。 默认8min × 3 = 24min ≪ 72h,故当前 72h 主导;T_propagate上调时 grace 自动跟随, 杜绝「契约 §6 与代码常量两处手动对齐」漂移。
7. 故障模式与责任¶
| 情景 | Manager 行为 | 集群侧期望行为 |
|---|---|---|
| Manager 不可达 | 请求失败 | file_storage PV 队列持久排队,恢复后吐出 |
| Token 被吊销 | 401 | Collector 持续 401 → 必须 pull 新 PRIMARY(§6 步骤 3) |
| 限流 429 | 立即返回 | 退避重试,事件保留队列 |
| Body 超 8 MiB | 413 | 缩小 batch size |
occurred_at 缺失 |
200 ack + 丢弃该条 + otlp_dropped_total 计数告警 |
排查产出方未冻结 TimeUnixNano(§X.2/§X.3) |
| protobuf 解析错 | 400 | 不应发生;属配置错误 |
SLA 口径(R3 ack·与 §X.3 捆绑):计费可靠性锚定 「PV 持久队列 + 幂等」, 不锚定投递时延上界。RWO PVC + StatefulSet
VolumeClaimTemplates语义下每副本独立队列, 「≥2 副本」不构成互为故障转移的 HA;保证走「PV 可重 attach」分支:崩溃副本队列不丢, Pod 按 ordinal 重建并重挂同一 PVC 后 drain。显式边界:节点级(非优雅)故障时 RWO 卷 detach + Pod 重调度默认≈6min(kube-controller-manager--pod-eviction-timeout+ CSI detach),期间该 ordinal 队列滞留但不丢(已落 PV,重挂后投递)。 在 §X.1「无时间窗、(event_id,occurred_at)精确去重」框架下,延迟投递 ≠ 丢单, ManagerON CONFLICT对延迟到达免疫 → ns 共享 Collector 成立。
8. 观测信号(Manager 侧)¶
Phase A(Zap 日志)¶
otlp_billing_event(每条事件一行,logging/fanout archive 侧)otlp_auth_failed+reason字段;otlp_rate_limited+cluster_id字段
Phase B(Prometheus 指标 · 已实装 TFRM-125)¶
cmd/otlp-receiver 暴露 /metrics(loopback only,与 admin-service 同模式,无额外鉴权,
运维侧经内网/sidecar 抓取)。指标(internal/shared/metrics/otlp.go):
| 指标 | 类型 | 含义 |
|---|---|---|
otlp_dropped_total{reason} |
counter | filterValid 丢弃数;reason ∈ event_id/tenant_name/namespace/occurred_at/manager_key_id |
otlp_auth_failed_total{reason} |
counter | Bearer 鉴权失败;reason ∈ missing_header/malformed_header/token_not_found/token_revoked/token_expired |
otlp_events_ingested_total |
counter | 成功落 sink 的事件累计(liveness 探针,非精确计费计数) |
otlp_dropped_total的reason取缺失字段裸名(event_id/tenant_name/namespace/occurred_at/manager_key_id), 与server.filterValid的missing字面量一一对应,不加missing_前缀 —— 告警规则按裸名编写, 杜绝代码/规则两处手动对齐漂移。
reason="manager_key_id"(TFRM-231,取代原 TFRM-213 的organization_id)仅对 gateway 模式 token 产生: 归属权威收回 Manager 后,gateway 事件必须携带 correlation idmanager_key_id(下游反查权威 org), 缺失即不可归属(网关配置错),receiver fail-closed 丢弃。非零即说明 gateway proxy 漏注入计费锚 (整批 fail-closed = 静默掉计费/掉收入),须告警。organization_id已降级不再 gate;per_cluster 路径无此字段、不产此 reason。
otlp_events_ingested_total是断流 liveness 探针,不是计费精确计数:primary 失败 + sidecar 重试时同批会被计两次(中央 TS 靠(event_id,occurred_at)PK 去重,本指标不去重)。 仅用于「rotate 后是否还有事件流入」的存活判断,不得用于对账/计费看板。
otlp_auth_failed_total按reason而非cluster_id打标签:鉴权失败时 token 未解析, cluster_id 不可知(auth 失败 hook 仅有auth.FailureReason)。这是设计约束,非遗漏。
rotate 健康探针告警规则(scrape 侧 PromQL,rotate 后人工/自动观察):
# 新 PRIMARY 未传播到 Collector(ESO/restart-on-sync 链路故障)
rate(otlp_auth_failed_total{reason=~"token_not_found|token_expired|token_revoked"}[5m]) > 0
# 或:计费链路实际断流(即便 auth_failed 未突增)
increase(otlp_events_ingested_total[15m]) == 0
# 或:产出方未冻结 TimeUnixNano(§X.2 被违反)
increase(otlp_dropped_total{reason="occurred_at"}[10m]) > 0
# 或(TFRM-231):gateway proxy 漏注入 manager_key_id 计费锚 → 整批 fail-closed 静默掉计费
increase(otlp_dropped_total{reason="manager_key_id"}[10m]) > 0
9. Phase 边界 / Sink 选型¶
Manager 侧 sink 通过环境变量 OTLP_SINK_TYPE 选择:
| 取值 | 行为 | 适用场景 |
|---|---|---|
logging(默认) |
Zap.Info 落地,200 ack 后丢弃数据 | 本地开发 / 中央 TS 不可用兜底 / Phase A |
fanout |
primary=中央 TS·of-record + archive=Zap 归档双写 | Phase B per-cluster ramp 对账期 |
timescale |
仅写中央 TimescaleDB hypertable(TFRM-115) | 生产 / UAT(ramp 收敛后) |
timescale 与 fanout 的 primary 都是中央 TS,启动阶段 fail-fast 要求
OTLP_TS_HOST/OTLP_TS_PORT/OTLP_TS_USER/OTLP_TS_PASSWORD/OTLP_TS_DBNAME/OTLP_TS_SSLMODE
完整;OTLP_TS_AUTO_MIGRATE=true(默认)启动跑嵌入式 schema migration(DDL 幂等)。
SSL 边界(TFRM-120 收敛):生产形态为 docker on management VM —— 中央 TS 与 otlp-receiver / user-service 同 VM、同 docker network 或 loopback。此场景
OTLP_TS_SSLMODE=disable是可接受默认。仅当部署形态变为跨 host / 跨 VPC 时切verify-full并注入 CA / 客户端证书。Trust boundary 假设(
disable成立前提,运维与安全责任人共同确认): 1. 中央 TS 容器端口必须 loopback 绑定(127.0.0.1:5433:5432);改0.0.0.0即等价公网 plaintext。 2. 管理平面 VM 不与不可信容器共享 docker network。 3. 拥有该 VM shell 访问权 = 拥有 TS 明文访问权(与现有tfrs_postgres同信任模型)。 三条任一不成立,本节disable默认失效,必须回退verify-full。⚠️ cluster_id 契约:hypertable
cluster_id列为TEXT(与 Manager PG 物理隔离避免 FK), Manager 鉴权层强制写strconv.FormatUint(uint64(authClusterID), 10)(十进制字符串)。 下游 Worker(TFRM-117)反向 join 必须用strconv.ParseUint(clusterID, 10, 64), 不存 hex / UUID / 任何非十进制格式。
Phase B per-cluster ramp checklist(切流单元 = per-cluster)¶
切流单元 = per-cluster(与 TFRO TFRCluster.Spec.Billing.OTLPEndpoint per-cluster 对齐)。
逐集群推进,不全局一刀切:
- 目标集群 Manager 实例
OTLP_SINK_TYPE=logging → fanout,开对账窗口(建议 ≥1 个计费周期)。 - fanout 期 primary(TS·of-record)与 archive(Zap 归档)双写;primary 失败原样冒泡触发
Collector 重试(TS 经 §X.1
ON CONFLICT幂等,重试安全),primary 失败时不写 archive。 - 跑对账 SQL(下):比该 cluster_id 在对账窗内 TS 侧去重计数 vs Zap 归档侧去重计数。
- 差额 ≤ 阈值(建议 0;允许
archive ≥ TS—— archive 在 primary 成功后才写,不会反超)→ 该集群OTLP_SINK_TYPE=fanout → timescale。 - 推进下一集群,重复 1–4。
对账 SQL(本工单首次起草;#13187 §2 是决策表非 SQL,无既有 SQL 可引用):
-- TS 侧:某 cluster 对账窗内去重事件数(llm / resource 各跑一次)
SELECT count(*) FROM (
SELECT DISTINCT event_id, occurred_at
FROM llm_billing_events -- 或 resource_utilization_events
WHERE cluster_id = :cluster_id
AND occurred_at >= :window_start
AND occurred_at < :window_end
) t;
归档侧:在同窗对 Zap 归档(otlp_billing_event 行,按 event_type 过滤、按
(event_id, occurred_at) 去重)计数,与上面 TS 计数比对。差额需为 0(或仅 archive ≥ TS
且差值可由「archive 在 primary 成功后写」解释)方可切 timescale。
event_type discriminator(TFRM-115 sum-type)¶
LogRecord attribute event_type 决定落库 hypertable:
| 取值 | 落库表 | 必填(NOT NULL) | 可选维度(nullable) |
|---|---|---|---|
llm_billing(默认) |
llm_billing_events |
event_id、occurred_at、tenant_name、namespace、model_name、provider、prompt_tokens、completion_tokens |
organization_id、robot_id、cr_name、manager_key_id(TFRM-230 归属 correlation id) |
resource_utilization |
resource_utilization_events |
event_id、occurred_at、tenant_name、namespace、resource_type、utilization、above_threshold |
organization_id、robot_id、cr_name、pod_name、container_name |
缺省 event_type 视为 llm_billing(兼容历史);新接入方应显式带该 attribute。
occurred_at 为两表必填且受 §X 不变量约束(缺失 fail-loud 丢弃,非兜底)。
resource_utilization 事件由 §1 所述 Alt-A2 Operator 发射器产出(非 Collector
metrics→logs connector)。personal 池场景 namespace 不足以区分扣费主体,需依赖
robot_id/cr_name 归一(TFRM-122
ConsumptionScheduler 精确聚合)。