使用 Prometheus 统计每个租户的 CPU 秒数用于计费
解读
在国内多租户 SaaS、游戏云、GPU 渲染等场景里,“按秒计费” 是云厂商与甲方合同里的硬指标。Prometheus 只存累计 CPU 时间(container_cpu_usage_seconds_total),本身没有租户维度,也没有“计费周期”概念。因此面试题考察的是:
- 如何把租户 ID注入到容器指标里;
- 如何在 Prometheus 里做周期采样、差值计算、单位换算;
- 如何防止容器重启、Pod 重建、HPA 扩缩带来的指标断档;
- 如何把结果幂等写入计费库,避免重复扣费。
一句话:不是“会写 PromQL”就行,而是给出一条端到端可落地的计费流水线,让财务敢用你的数字出账单。
知识点
- cgroup v1/v2 CPU 统计原理:container_cpu_usage_seconds_total 来自 /sys/fs/cgroup/cpuacct.usage,单位是纳秒,需要除以 1e9 得到“秒”。
- 多维度标签建模:租户 ID 必须落盘到容器,推荐Docker Labels(com.xxx.tenant_id)+ Prometheus relabel,绝不用环境变量(容器重启后 env 可能变)。
- PromQL 周期差值:increase() 只能给 5 m 粒度,计费要 1 m 精度时需用子查询或 recording rule,防止慢速 Pod 被 scrape 漏掉。
- 指标断档处理:container_last_seen 做存活判断,避免把重启后的 counter 重置当成“负值”。
- 计费幂等:用租户+实例+周期做联合主键,写入时带prometheus 采样时间戳,下游账单系统用“ON CONFLICT DO NOTHING”去重。
- 安全合规:国内等保 2.0 要求原始指标保存 ≥ 6 个月,Prometheus 本地盘不够,需远程写到 VictoriaMetrics 或 Thanos Receive,并开启降采样节省成本。
答案
-
镜像阶段
Dockerfile 里加一行:
LABEL com.xxx.tenant_id="T12345"
构建时通过 --build-arg 把租户号打进去,防止运行时被篡改。 -
Prometheus 配置
scrape_configs:- job_name: 'docker'
dockerswarm_sd_configs:- host: unix:///var/run/docker.sock
relabel_configs: - source_labels: [__meta_dockerswarm_service_label_com_xxx_tenant_id]
target_label: tenant_id
regex: '(.+)'
replacement: '${1}'
这样每个 container_cpu_usage_seconds_total 都带 tenant_id 标签。
- host: unix:///var/run/docker.sock
- job_name: 'docker'
-
Recording Rule(1 min 精度)
- record: tenant:cpu_seconds:1m
expr: |
increase(container_cpu_usage_seconds_total{tenant_id!=""}[1m])- on(container_id) group_left()
(1 - increase(container_last_seen[1m]) < bool 60)
labels:
unit: seconds
granularity: 1m
用 bool 过滤掉重启后时间回零的坏点。
- on(container_id) group_left()
- record: tenant:cpu_seconds:1m
-
计费周期汇总(以 5 min 为例)
sum_over_time(
tenant:cpu_seconds:1m{tenant_id="T12345"}[5m]
) / 5
结果即为该租户近 5 min 平均 CPU 秒数,可直接乘单价。 -
写入计费库
用prometheus-postgresql-adapter或自写 sidecar,把上述查询结果按
(tenant_id, instance_id, period_start, cpu_seconds, prometheus_ts)
写入 PostgreSQL,主键冲突时丢弃,保证 Exactly-Once。 -
容器重启补偿
若 container_cpu_usage_seconds_total 出现counter 重置,increase() 会自动处理;若 Pod 被重建,container_id 变化,需用kube_pod_container_info做新旧容器映射,防止重复计费。 -
校验与对账
每天凌晨跑离线 Spark 任务,把cgroup 原始 usage 文件与 Prometheus 结果按 tenant_id 聚合对比,差异>0.5% 自动发飞书告警,财务才敢签字。
拓展思考
- 超卖场景:节点 CPU 被超分,container_cpu_usage_seconds_total 可能大于实际物理时间,需要再乘节点cpu_quota/cpu_period比例做折扣系数,否则租户会投诉“我用了 1.2 核·时却只买了 1 核”。
- 混部与优先级:国内大厂普遍用Koordinator/Caelus做离线混部,同一租户容器可能跑在高优先级在线和低优先级离线两种 QoS,计费时要按实际 QoS 单价分档,Prometheus 里再加 qos 标签即可。
- Serverless 容器:阿里云 ECI、华为 CCI 这类秒级计费产品,容器生命周期可能短于 30 s,scrape 间隔必须调到 10 s 以下,否则漏采导致少收钱;此时推荐pushgateway+sidecar模式,容器退出前主动 push 累计 CPU 秒数,解决短生命周期采样盲区。