用 Prometheus group_left 关联服务 owner 元数据并按 team 聚合

举报
yd_240403232 发表于 2026/11/14 09:44:12 2026/11/14
【摘要】 介绍如何用 Prometheus 的 group_left 将服务 owner 元数据 info 指标关联到请求指标,按 service 匹配后按 team 聚合,并用 count by 和总量对比验证不会因多对多匹配而放大。

问题场景

服务请求指标 http_requests_total{service,method,code} 通常只带服务名,团队 owner 信息维护在配置或 CMDB 中。直接把 team 写进请求指标会带来两个问题:标签随组织调整而变动,且每条时间序列都重复存储元数据。更合适的做法是建立一条低基数的 info 指标,例如包含 service、team、owner 标签且值为 1 的 service_owner_info。请求指标保持原样,查询时用 group_left 把 team 附加到请求指标上,再按 team 聚合。

最小可运行示例

下面用一个 Python 标准库 exporter 暴露示例指标。计数为静态占位值,仅用于验证 PromQL 结构,不表示真实流量。

from http.server import BaseHTTPRequestHandler, HTTPServer

REQUESTS = [
    ('checkout', 'GET', '200', 120),
    ('checkout', 'POST', '500', 3),
    ('catalog', 'GET', '200', 80),
]
OWNERS = {
    'checkout': ('payments', 'alice'),
    'catalog': ('storefront', 'bob'),
}

def render():
    q = chr(34)
    lines = []
    for service, method, code, value in REQUESTS:
        lines.append(
            'http_requests_total{service=' + q + service +
            ',method=' + q + method + ',code=' + q + code + '} ' + str(value)
        )
    for service, (team, owner) in OWNERS.items():
        lines.append(
            'service_owner_info{service=' + q + service +
            ',team=' + q + team + ',owner=' + q + owner + '} 1'
        )
    return chr(10).join(lines) + chr(10)

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != '/metrics':
            self.send_response(404)
            self.end_headers()
            return
        body = render().encode()
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; version=0.0.4')
        self.send_header('Content-Length', str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, *args):
        pass

if __name__ == '__main__':
    HTTPServer(('127.0.0.1', 9101), Handler).serve_forever()

保存为 exporter.py,运行并检查输出:

python3 exporter.py
curl -s http://127.0.0.1:9101/metrics

再准备 prometheus.yml:

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: demo
    static_configs:
      - targets: ['127.0.0.1:9101']

启动 Prometheus(配置文件与 exporter 在同一目录):

promtool check config prometheus.yml
prometheus --config.file=prometheus.yml

在 Prometheus 查询界面执行:

sum by (team) (
  http_requests_total
  * on(service) group_left(team) service_owner_info
)

示例数据应得到 payments = 123、storefront = 80。生产环境应把左侧换成 rate(http_requests_total[5m]):

sum by (team) (
  rate(http_requests_total[5m])
  * on(service) group_left(team) service_owner_info
)

on(service) 表示只按 service 匹配;group_left(team) 表示左侧可以有多条请求序列,右侧每个 service 只能有一条 info 序列,并把 team 附加到结果。若还要保留 owner,可写成 group_left(team, owner)。

验证是否有放大

先确认 info 指标每个服务唯一:

count by (service) (service_owner_info)

每个 service 应为 1。找重复:

count by (service) (service_owner_info) > 1

结果应为空。再比较 join 前后的序列数:

count(http_requests_total)
count(http_requests_total * on(service) group_left(team) service_owner_info)

无放大时两者应相等。还可以检查没有 owner 的服务:

count by (service) (http_requests_total)
unless on(service) service_owner_info

最后比较全局总量与按 team 汇总的总量:

sum(rate(http_requests_total[5m]))
sum(
  sum by (team) (
    rate(http_requests_total[5m])
    * on(service) group_left(team) service_owner_info
  )
)

若所有请求都能匹配且 info 唯一,两者应一致。

边界与限制

  1. service 标签必须在两侧完全一致,大小写、空格、环境后缀都要统一;缺失 service 的请求序列不会参与 join,按团队汇总会漏掉。
  2. 一个 service 只能对应一条 service_owner_info。如果同一服务有多个团队,不要用多条 info 直接做 group_left,否则会出现重复匹配,查询可能失败或口径被放大。应明确主 owner,或另建多对多关系并单独定义统计口径。
  3. team、owner 属于组织元数据,基数应受控;不要把请求 ID、实例 IP、工单号放进 info 指标。
  4. 元数据变更后要等下一次抓取才生效;变更期间 join 结果可能为空或仍为旧值,需要结合发布流程处理。
  5. 示例中的静态计数只用于验证查询结构。实际使用 counter 时注意 rate 窗口、重启和抓取间隔;窗口过短可能没有足够样本。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。