Prometheus入门与实战

408 阅读17分钟

简介

Prometheus是由SoundCloud在2012年开源的一个用于监控与报警的组件,主要用于采集、存储和查询指标(Metrics)时间序列(Time Series)。在2016年,Prometheus继Kubernetes之后成为第二个加入CNCF的项目。

Prometheus的主要特性如下:

  • 基于指标名称与标签构建了多维度的时间序列数据模型
  • 提供PromQL,能够灵活地查询多维数据。
  • 不依赖分布式存储;单server自洽。
  • 通过基于Http的拉(Pull)模式进行数据采集
  • 通过间接的网关也能够实现推(Push)模式数据采集
  • 支持静态配置服务发现机制来实现采集
  • 支持多种图标和仪表盘

在进一步了解Prometheus之前,首先需要大致了解下指标(Metrics)和时间序列(Time Series)是什么。简单地说,指标就是一类数据,而时间序列就是指标随着时间产生变化的集合。在现实世界中我们也有指标,假设气象局每隔1个小时测量一次温度并记录,于是我们便有了温度这一个指标,并且由间隔为1小时的温度时间序列。

换到我们的计算机世界,指标可以是cpu的占用率,时间序列是我们每间隔一段时间采集的占用率的集合。可以是以下形式 cpu占用率 ∈ {(t_0,v_0)... (t_i,v_i)},其中t表示时间戳,v表示值。

对于一个监控系统而言,指标和时间序列就是核心,通过这两个值,我们能够对程序/系统的运行状态,根据时间维度进行观测。

核心组件

整个Prometheus生态包含多个组件,除了Prometheus server组件其余都是可选的

  • Prometheus Server:主要的核心组件,用来收集和存储时间序列数据。
  • Client Library: :客户端库,为需要监控的服务生成相应的 metrics 并暴露给 Prometheus server。当 Prometheus server 来 pull 时,直接返回实时状态的 metrics。
  • push gateway:主要用于短期的 jobs。由于这类 jobs 存在时间较短,可能在 Prometheus 来 pull 之前就消失了。为此,这次 jobs 可以直接向 Prometheus server 端推送它们的 metrics。这种方式主要用于服务层面的 metrics,对于机器层面的 metrices,需要使用 node exporter。
  • Exporters: 用于暴露已有的第三方服务的 metrics 给 Prometheus。
  • Alertmanager: 从 Prometheus server 端接收到 alerts 后,会进行去除重复数据,分组,并路由到对收的接受方式,发出报警。常见的接收方式有:电子邮件,pagerduty,OpsGenie, webhook 等。

架构

上图是Prometheus的架构,由以下几个模块构成

  • Prometheus Server:服务端,负责周期性采集、存储指标,也为外部服务提供查询服务。
    • Retrieval: 周期性地去暴露的endpoint上采集指标
    • TSDB:Time Series Database,Prometheus自研的时序数据库
    • Http Server:prometheus提供的http服务端
  • Pushgateway:一个中间网关,负责将指标暂存,后续提供给server端采集。一般配合一些短期的job进行使用,原因在于短期的job的生命周期可能还没等到Retrieval进行采集就结束了。
  • PromQL:Prometheus提供的查询语句服务端,能够配合如prometheus前端、grafana等ui界面查询。
  • Alertmanager:负责将告警信息通知到三方的消息渠道,通过在Prometheus server上配置告警规则,server会将告警时间推送到Altermanager。
  • Service Discovery:是指 Prometheus 可以动态的发现一些服务,拉取数据进行监控,如从DNS,Kubernetes,Consul 中发现, file_sd 是静态配置的文件。
  • UI:Prometheus也提供了前端访问的服务,能够绘制一些简单的图标,当然也可以接入Grafana来获得更强大、美观的图表展示。

实战1-SpringCloudGateway自定义指标

Talk is cheap,show me the code.

下面以如何在SpringCloudGateway为案例,看下在真实项目中如何自定义采集指标

<parent>
  <artifactId>spring-boot-starter-parent</artifactId>
  <groupId>org.springframework.boot</groupId>
  <version>2.7.10</version>
</parent>

<properties>
  <java.version>8</java.version>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <spring-cloud.version>3.1.5</spring-cloud.version>
  <micrometer.version>1.10.4</micrometer.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter</artifactId>
    <version>${spring-cloud.version}</version>
  </dependency>
  <dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
    <version>${micrometer.version}</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    <version>${spring-cloud.version}</version>
    <exclusions>
      <exclusion>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
  </dependency>
</dependencies>

主要引入依赖有

<dependency>
  // 暴露指标的endpoint
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  // 指标采集
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

springboot的application.yaml如下,仅配置了一条路由,前缀是/httpbin。同时暴露了actuator的端口,开放了promethues的endpoint。

server:
  port: 8080

spring:
  jmx:
    enabled: false
  cloud:
    gateway:
      routes:
        - id: httpbin
          uri: http://httpbin.org
          order: 1
          filters:
            - StripPrefix=1
          predicates:
            - Path=/httpbin/**

management:
  server:
    port: 8108
  endpoints:
    enabled-by-default: false
    web:
      exposure:
        include: '*'
  endpoint:
    prometheus:
      cache:
        time-to-live: 1
      enabled: true

记录指标的GlobalFilter

package com.xiaoyu.filter;

import io.micrometer.core.instrument.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.concurrent.TimeUnit;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * @author chy
 * @description
 * @date 2024/8/13
 */
@Component
public class CustomMetricFilter implements GlobalFilter, Ordered {
    @Autowired
    private MeterRegistry meterRegistry;
    private static final String METRICS_PREFIX = "test";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 记录开始时间
        Timer.Sample sample = Timer.start(this.meterRegistry);
        return chain.filter(exchange).doOnSuccess((aVoid) -> {
            this.onSuccess(exchange, sample);
        }).doOnError((throwable) -> {
            this.onError(exchange, sample);
        });
    }


    private void onSuccess(ServerWebExchange exchange, Timer.Sample start) {
        record(exchange, start);
    }

    private void onError(ServerWebExchange exchange, Timer.Sample start) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            record(exchange, start);
        } else {
            response.beforeCommit(() -> {
                record(exchange, start);
                return Mono.empty();
            });
        }
    }

    private void record(ServerWebExchange exchange, Timer.Sample sample) {
        // 组成tag
        Tags tags = getTags(exchange);

        Timer timer = Metrics.timer(METRICS_PREFIX, tags);
        long duration = sample.stop(timer);

        Metrics.counter(METRICS_PREFIX + ".count", tags).increment();

        DistributionSummary ds = DistributionSummary.builder(METRICS_PREFIX + ".summary")
                .tags(tags)
                .publishPercentiles(0.5, 0.90, 0.95, 0.99)
                .serviceLevelObjectives(Duration.ofMillis(50).toNanos(),
                        Duration.ofMillis(100).toNanos(),
                        Duration.ofMillis(200).toNanos(),
                        Duration.ofMillis(400).toNanos(),
                        Duration.ofMillis(800).toNanos(),
                        Duration.ofMillis(1600).toNanos(),
                        Duration.ofMillis(3200).toNanos(),
                        Duration.ofMillis(6400).toNanos()
                )
                .minimumExpectedValue((double) Duration.ofMillis(1).toNanos())
                .maximumExpectedValue((double) Duration.ofSeconds(7).toNanos())
                .register(meterRegistry);
        ds.record(duration);

        Timer timer2 = Timer.builder(METRICS_PREFIX + ".timer.histogram")
                .tags(tags)
                .publishPercentiles(0.5, 0.90, 0.95, 0.99)
                .serviceLevelObjectives(Duration.ofMillis(10),
                        Duration.ofMillis(20),
                        Duration.ofMillis(50),
                        Duration.ofMillis(100),
                        Duration.ofMillis(150),
                        Duration.ofMillis(200),
                        Duration.ofMillis(250),
                        Duration.ofMillis(300),
                        Duration.ofMillis(350),
                        Duration.ofMillis(400),
                        Duration.ofMillis(450),
                        Duration.ofMillis(500),
                        Duration.ofMillis(600),
                        Duration.ofMillis(700),
                        Duration.ofMillis(800),
                        Duration.ofMillis(900),
                        Duration.ofMillis(1000),
                        Duration.ofMillis(1250),
                        Duration.ofMillis(1500),
                        Duration.ofMillis(1750),
                        Duration.ofSeconds(10)
                )
                .minimumExpectedValue(Duration.ofMillis(1))
                .maximumExpectedValue(Duration.ofSeconds(10))
                .register(meterRegistry);
        timer2.record(duration, TimeUnit.NANOSECONDS);
    }

    private Tags getTags(ServerWebExchange exchange) {

        String outcome = "CUSTOM";
        String status = "CUSTOM";
        String httpStatusCodeStr = "NA";
        String httpMethod = exchange.getRequest().getMethod().name();
        Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);

        if (exchange.getResponse() instanceof AbstractServerHttpResponse) {
            Integer statusInt = ((AbstractServerHttpResponse) exchange.getResponse()).getStatusCodeValue();
            status = String.valueOf(statusInt);
            httpStatusCodeStr = status;
            HttpStatus resolved = HttpStatus.resolve(statusInt);
            if (resolved != null) {
                outcome = resolved.series().name();
                status = resolved.name();
            }
        } else {
            HttpStatus statusCode = exchange.getResponse().getStatusCode();
            if (statusCode != null) {
                httpStatusCodeStr = String.valueOf(statusCode.value());
                HttpStatus resolved = HttpStatus.resolve(statusCode.value());
                outcome = resolved.series().name();
                status = resolved.name();
            }
        }

        return Tags.of("outcome", outcome, "status", status, "httpStatusCode", httpStatusCodeStr,
                        "httpMethod", httpMethod, "routeId", route.getId());
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

这里暂且不对指标的记录细节详细描述,简单看下最终的效果。

先本地访问下/httpbin/get接口,然后在浏览器访问localhost:8108/actuator/prometheus

可以看到自己定义的指标(test开头)已经有数据了,恭喜你成功暴露了prometheus标准的指标。

核心概念

指标(Metric)

"Prometheus中一共有四种Metric",为什么这句话打了一个引号呢,因为这四种类型是在Prometheus客户端中划分的。实际上在Prometheus Server看来,所有的时间序列指标都是相同的。客户端进行划分也是为了方便我们使用与理解。

  • Counter
  • Gauge
  • Histogram
  • Summary

先来看下一个Prometheus的指标如何表示

<metric name>{<label name>=<label value>, ...}

更具体的案例如

api_http_requests_total{method="POST", handler="/messages"}
// 指标名称为 api_http_requests_total
// 有两个标签 method、handler

Tips:实际上metric_name也是一个特殊的label,prometheus会默认为指标创建一个__name__=‘metric_name’的label,这和prometheus的存储索引设计有点关系,这里先不细究。

Counter

Counter,英译中为计数器,实际上Counter也是一个累加值,一个counter要被被重置为0,要么一直累加。一般情况下,Counter可以被用来统计比如http请求总数、任务完成数或者错误数量。

虽然Counter的api在累加时,能够加上一个负数,但是官方不推荐你这样使用,如果想要记录一个浮动的数目,可以使用Gauge。

Gauge

Gauge,英译中为仪表盘、尺度,在Prometheus中Gauge被用于表示一个浮动的指标。在实际运用中,Gauge可以用于表示如当前并发的任务数,当前并发的请求数,又或者是当前活跃的实例数量。

Histogram

Histogram,英译中为直方图/柱状图。一般用于记录请求耗时、请求大小等数据。在使用该指标时候,需要指定桶的数据上限。如下代码块中记录了一个Histogram指标,并对duration进行了记录。在serviceLevelObjectives方法也就是第4到12行中,指定了桶的大小和范围。

比如当duration为90ms时,将会落入Duration.ofMillis(100)、Duration.ofMillis(150)、Duration.ofMillis(200)、... 、Duration.ofSeconds(10)桶中。

Timer timer2 = Timer.builder(METRICS_PREFIX + ".timer.histogram")
        .tags(tags)
        .publishPercentiles(0.5, 0.90, 0.95, 0.99)
        .serviceLevelObjectives(Duration.ofMillis(10),
                Duration.ofMillis(20),
                Duration.ofMillis(50),
                Duration.ofMillis(100),
                Duration.ofMillis(150),
                Duration.ofMillis(200),
                // ...
                Duration.ofSeconds(10)
        )
        .minimumExpectedValue(Duration.ofMillis(1))
        .maximumExpectedValue(Duration.ofSeconds(10))
        .register(meterRegistry);
timer2.record(duration, TimeUnit.NANOSECONDS);

Histogram延伸出了三个指标

  • metric_name_bucket{le=""}

小于桶上限大小的记录个数

  • metric_name_sum

累计总和,假设Histogram记录的是请求耗时,那么这个指标的含义就是所有请求耗时的和。

  • metric_name_count

总共的记录数量,假设Histogram记录的是请求耗时,那么这个指标的含义就是请求总数。

使用histogram_quantile()函数能够从直方图聚合中计算分位数。比如P99、P95等

Summary

Summary(摘要)呈现的效果与Histogram类似,同样也有三个延伸的指标

  • metric_name{quantile="<φ>"}

这边表示分位数,具体见quantile的值。

  • metric_name_sum

与Histogram的一样

  • metric_count

与Histogram的一样

这里可能有些疑问,为什么有Histogram了还要有Summary。

在询问ChatGPT后得到了如下的解答

Prometheus中,HistogramSummary都是用于收集和处理度量数据的聚合类型,但它们的用法和特性有所不同。以下是对这两种指标的详细解释:

Histogram
定义:Histogram用于统计和分布数据的一个集合。它将值分成多个桶(buckets),每个桶代表一个值的范围。每个桶都有一个计数器,用于记录落在该范围内的事件数量。

用途:

适合于高维度数据的统计。
可以用于计算数据的分布情况(如响应时间的分布)。
可以计算请求的百分位数,但不如Summary精确。
工作原理:

你可以定义一些桶大小,比如: [0.1, 0.2, 0.5, 1.0, 2.0]。
这会生成统计每个桶内的计数,比如有多少个请求的响应时间在0.1秒以下,有多少在0.2秒以下等等。
提供总的请求计数和累积的直方图值(将所有小于等于当前桶值的计数相加)。
优缺点:

优点:可以根据桶的划分得到较好的数据分布视图。
缺点:在真实应用中只能估算百分位数。
Summary
定义:Summary也用于处理和统计数值数据。它记录观察值的总计(总和)和数量,并且提供了计算特定时间窗口内的分位数(如p50p90p99等)。

用途:

适合需要实时精确计算分位数的情况。
比如需要了解请求响应时间的特定百分位数。
工作原理:

Summary在客户端会维护一个队列来存储样本,以便在上报时计算出分位数。
可以直接获取特定的分位数值(如0.5, 0.9, 0.99等)。
优缺点:

优点:能够精确地计算分位数,适合小样本数据。
缺点:由于在处理时需要维护状态,因此生成的样本会消耗更多的内存和计算资源,而且数据不能用于跨实例的聚合。
总结
选择Histogram:当你希望在各个实例之间聚合数据并能大致了解分布情况时,可以选择Histogram。它更适用于高流量,具有大量实例的服务。

选择Summary:当你需要精确的分位数计算且不需要在多个实例之间聚合时,Summary可能是更好的选择。

根据具体的使用场景和需求,可以选择最适合的指标类型。

以下是Summary与Histogram的详细对比

SummaryHistogram
配置分位数和滑动窗口区间配置(桶区间)
客户端性能需要流式计算代价大只需要增加bucket,代价小
服务端性能直接读取,无需计算需要计算分位数,代价高
精度与分位数设置有关与bucket的数量划分有关
聚合一般无法聚合可以聚合

Job和实例

在Prometheus中对一系列相同服务实例的采集行为称之为Job,一个job的表现形式如下:

JobName:scrap_some_service

instance1: ip1:port1

instance2: ip2:port2

表示配置了一个job名称为scrap_some_service,采集了两个实例,地址分别为 ip1:port1,ip2:port2。

特别的,对于被采集的指标都会包含一个label

metric_name{job=''...}

如何配置一个job,可查看Configuration | Prometheus

查询

基本查询

瞬时值查询

直接输入指标名称即可查询,即可查询所有为名称的指标的瞬时值。

metric_name

比如输入 prometheus_http_requests_total 即可查询全部名称为promehteus_http_requests_total的指标

也可以通过label对指标进行过滤,label的过滤有以下四种方式

  • =:标签值等于指定值
  • !=:标签不等于指定值
  • ~=: 标签值等于某正则表达式
  • !~: 标签值不等于某正则表达式

范围值查询

prometheus支持按照时间范围查询指标,查询语法如下

metric_name{}[time_range]

time range的单位如下

  • ms - milliseconds
  • s - seconds
  • m - minutes
  • h - hours
  • d - days - assuming a day always has 24h
  • w - weeks - assuming a week always has 7d
  • y - years - assuming a year always has 365d1

如以下查询语句,会返回过去1小时30分中apiserver_request_duration_seconds_count指标的所有时间序列。

偏移查询

prometheus也支持单独查询某一刻的历史数据,语法如下

metric_name offset time

这里的time的语法与上一节time range的语法一致。

更多查询

关于更多查询的语法,可见Operators | Prometheus,本文不做赘述。

存储设计

关于这块我自认为不能比

yieldnull.com/blog/d77d5f…

这篇文章讲的更加清楚。

实战2-serviceMonitor配置采集

在k8s中,我们可以通过serviceMonitor这个cr来配置prometheus的采集规则。一个典型的serviceMonitor的定义如下

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: test-service-monitor
spec:
  // 	指定采集pod的端口,接口,以及采集间隔
  endpoints:
    - interval: 30s
      path: /actuator/prometheus
      port: monitor
  // 指定作用明明空间
  namespaceSelector:
    any: true
  // 只有匹配到如下label的pod才会被采集
  selector:
    matchLabels:
      micrometer-prometheus-discovery: 'true'

完成配置后,可在prometheus的前端页面查看已经被prometheus感知的serviceMonitor

当然也可以通过查询语句,查看相关的指标是否被prometheus采集了,来确认serviceMonitor是否生效。

内存占用异常

在k8s中部署了prometheus一段时间后,运维同事反应prometheus的pod内存占用异常,达到了惊人的40G。

使用如下命令可以查看prometheus的内存分布,可以看到实际上程序的内存占用为10GB,另外30GB来自于prometheus的mmap,在查询历史chunk中的数据时,prometheus会将历史数据通过mmap映射到内存。

go tool pprof -symbolize=remote -inuse_space https://monitoring.prod.cloud.coveo.com/debug/pprof/heap

在如下内存分布中label所使用的内存占用了1GB左右,而一个head才1.8GB左右,这提示我们label的差异性可能比较大,可能存在相同key的label,value的值特别多。

File: prometheus
Type: inuse_space
Time: Apr 24, 2019 at 4:20pm (CEST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 8839.83MB, 84.87% of 10415.77MB total
Dropped 398 nodes (cum <= 52.08MB)
Showing top 10 nodes out of 64
      flat  flat%   sum%        cum   cum%
 1628.82MB 15.64% 15.64%  1628.82MB 15.64%  github.com/prometheus/tsdb/index.(*decbuf).uvarintStr /app/vendor/github.com/prometheus/tsdb/index/encoding_helpers.go
 1233.86MB 11.85% 27.48%  1234.86MB 11.86%  github.com/prometheus/prometheus/pkg/textparse.(*PromParser).Metric /app/pkg/textparse/promparse.go
 1199.99MB 11.52% 39.00%  1199.99MB 11.52%  github.com/prometheus/tsdb.seriesHashmap.set /app/vendor/github.com/prometheus/tsdb/head.go
 1186.88MB 11.40% 50.40%  1186.88MB 11.40%  github.com/prometheus/prometheus/pkg/labels.(*Builder).Labels /app/pkg/labels/labels.go
  987.60MB  9.48% 59.88%   987.60MB  9.48%  github.com/prometheus/tsdb/chunkenc.NewXORChunk /app/vendor/github.com/prometheus/tsdb/chunkenc/xor.go
  836.65MB  8.03% 67.91%   836.65MB  8.03%  github.com/prometheus/tsdb.newMemSeries /app/vendor/github.com/prometheus/tsdb/head.go
  650.21MB  6.24% 74.16%  1850.20MB 17.76%  github.com/prometheus/tsdb.(*stripeSeries).getOrSet /app/vendor/github.com/prometheus/tsdb/head.go
  450.52MB  4.33% 78.48%   450.52MB  4.33%  github.com/prometheus/tsdb/index.newReader.func2 /app/vendor/github.com/prometheus/tsdb/index/index.go
  360.78MB  3.46% 81.95%   360.78MB  3.46%  github.com/prometheus/tsdb/index.(*MemPostings).Delete /app/vendor/github.com/prometheus/tsdb/index/postings.go
  304.51MB  2.92% 84.87%   304.51MB  2.92%  github.com/prometheus/tsdb.(*decbuf).uvarintStr /app/vendor/github.com/prometheus/tsdb/encoding_helpers.go

prometheus提供一个tsdb的分析工具,方便我们查看chunk中数据的分布。如下链接可以下载到该分析工具。

https://github.com/prometheus/prometheus/releases/download/v2.13.0/prometheus-2.13.0.linux-amd64.tar.gz

解压后得到目录如下

执行解析命令,其中需要指定下查看的block文件的路径

./tsdb analyze path/to/some/prometheus/datadir | less

可以看到kueblet的指标占了大头,而kubelet是默认采集与暴露的。此外在label中,还能够看到key为id的label数量非常多,而在我们的业务大盘中并没有使用该label。

Block path: /prometheus/prometheus-db/01D9CMTKZAB0R8T4EM95PKXKQ6
Duration: 2h0m0s
Series: 5547383
Label names: 248
Postings (unique label pairs): 159621
Postings entries (total label pairs): 44259261

Label pairs most involved in churning:
4424281 job=kubelet
4424281 service=monitoring-prometheus-oper-kubelet
4417556 endpoint=cadvisor
1154707 __name__=container_network_tcp_usage_total
524866 __name__=container_tasks_state
419893 __name__=container_memory_failures_total
419893 __name__=container_network_udp_usage_total
209946 scope=hierarchy
209946 type=pgfault
209946 scope=container
209946 type=pgmajfault
124554 node=ip-10-1-28-104.ec2.internal
124547 instance=10.1.28.104:4194
123625 node=ip-10-1-31-50.ec2.internal
123619 instance=10.1.31.50:4194
123535 node=ip-10-1-24-105.ec2.internal
123528 instance=10.1.24.105:4194
123455 node=ip-10-1-26-91.ec2.internal
123449 instance=10.1.26.91:4194
122999 node=ip-10-1-30-66.ec2.internal
122992 instance=10.1.30.66:4194

Label names most involved in churning:
4448288 __name__
4448286 service
4448286 job
4448280 instance
4448280 endpoint
4425365 node
4417169 id
1154707 tcp_state
524867 state
420440 type
419951 scope
419893 udp_state
61127 namespace
54746 device
53712 cpu
30180 image
30111 name
29976 pod_name
29976 container_name
23829 pod
11653 interface

Most common label pairs:
5230403 job=kubelet
5230403 service=monitoring-prometheus-oper-kubelet
5181993 endpoint=cadvisor
1332991 __name__=container_network_tcp_usage_total
605905 __name__=container_tasks_state
484724 __name__=container_network_udp_usage_total
484724 __name__=container_memory_failures_total
242362 type=pgfault
242362 scope=hierarchy
242362 scope=container
242362 type=pgmajfault
198766 endpoint=http
178005 namespace=infrastructure
137297 namespace=monitoring
135105 node=ip-10-1-28-104.ec2.internal
134282 instance=10.1.28.104:4194
133879 node=ip-10-1-24-105.ec2.internal
133623 node=ip-10-1-26-91.ec2.internal
133602 node=ip-10-1-31-50.ec2.internal
133540 node=ip-10-1-21-53.ec2.internal
133170 node=ip-10-1-28-252.ec2.internal

Highest cardinality labels:
116525 id
19148 type
4794 queue
3655 mountpoint
2926 __name__
2323 name
1073 container_id
956 exported_pod
949 pod_name
747 pod_ip
697 device
678 interface
657 instance
523 pod
286 replicaset
205 url
176 instance_type
173 le
146 image
133 address
90 container

Highest cardinality metric names:
1332991 container_network_tcp_usage_total
605905 container_tasks_state
484724 container_memory_failures_total
484724 container_network_udp_usage_total
121181 container_memory_swap
121181 container_start_time_seconds
121181 container_memory_rss
121181 container_memory_usage_bytes
121181 container_memory_working_set_bytes
121181 container_memory_failcnt
121181 container_memory_max_usage_bytes
121181 container_memory_cache
121181 container_last_seen
121181 container_cpu_user_seconds_total
121181 container_cpu_system_seconds_total
121181 container_cpu_load_average_10s
121173 container_spec_cpu_period
121173 container_spec_cpu_shares
120963 container_spec_memory_limit_bytes
120963 container_spec_memory_swap_limit_bytes
120963 container_spec_memory_reservation_limit_bytes

在后续的处理中,通过配置 <metric_relabel_confis> 将id drop掉

prometheus.io/docs/promet…

经过如此操作后,prometheus的内存占用从40GB降低到了8GB