使用 Prometheus 统计每个租户的 CPU 秒数用于计费

解读

在国内多租户 SaaS、游戏云、GPU 渲染等场景里,“按秒计费” 是云厂商与甲方合同里的硬指标。Prometheus 只存累计 CPU 时间(container_cpu_usage_seconds_total),本身没有租户维度,也没有“计费周期”概念。因此面试题考察的是:

  1. 如何把租户 ID注入到容器指标里;
  2. 如何在 Prometheus 里做周期采样、差值计算、单位换算
  3. 如何防止容器重启、Pod 重建、HPA 扩缩带来的指标断档;
  4. 如何把结果幂等写入计费库,避免重复扣费。

一句话:不是“会写 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,并开启降采样节省成本。

答案

  1. 镜像阶段
    Dockerfile 里加一行:
    LABEL com.xxx.tenant_id="T12345"
    构建时通过 --build-arg 把租户号打进去,防止运行时被篡改

  2. 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 标签。
  3. 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 过滤掉重启后时间回零的坏点。
  4. 计费周期汇总(以 5 min 为例)
    sum_over_time(
    tenant:cpu_seconds:1m{tenant_id="T12345"}[5m]
    ) / 5
    结果即为该租户近 5 min 平均 CPU 秒数,可直接乘单价。

  5. 写入计费库
    prometheus-postgresql-adapter或自写 sidecar,把上述查询结果按
    (tenant_id, instance_id, period_start, cpu_seconds, prometheus_ts)
    写入 PostgreSQL,主键冲突时丢弃,保证 Exactly-Once

  6. 容器重启补偿
    若 container_cpu_usage_seconds_total 出现counter 重置,increase() 会自动处理;若 Pod 被重建,container_id 变化,需用kube_pod_container_info新旧容器映射,防止重复计费。

  7. 校验与对账
    每天凌晨跑离线 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 秒数,解决短生命周期采样盲区