Prometheus 性能调优 - 什么是高基数问题以及如何解决?

本文涉及的产品
可观测可视化 Grafana 版,10个用户账号 1个月
可观测监控 Prometheus 版,每月50GB免费额度
简介: Prometheus 性能调优 - 什么是高基数问题以及如何解决?

背景

近期发现自己实验用的 Prometheus 性能出现瓶颈, 经常会出现如下告警:

  • PrometheusMissingRuleEvaluations
  • PrometheusRuleFailures

之后慢慢排查发现是由于 Prometheus 的某些 series 的高基数 (High Cardinality) 导致的. 本文是对 Prometheus 高基数问题的一次全面总结.

什么是基数(Cardinality)?

基数的 基本定义 是指一个给定集合中的元素的数量。

Prometheus 和可观察性的世界里,标签基数 是非常重要的,因为它影响到你的监控系统的性能和资源使用。

下面这张图, 可以清晰地反应基数的重要性:

基数激增: Prometheus 中的基数的基本图示。

简单地说。基数 是指一个标签的总体数值的计数。在上面的例子中,标签 status_code 的基数是 5,(即:1xx 2xx 3xx 4xx 5xx),environment的基数是 2(即 prod dev),而指标server_responses 的总体基数是 10。

多少算高基数?

一般来说:

  • 较低的基数 1:5 的标签值比率,
  • 标准基数 1:80 的标签值比率
  • 高基数 1:10000 的标签值比率。

还是上面的例子, 如果 status_code 是详细的 code, 如 200 404…, 那它的基数就可能高达数百个, environment 的基数再多一些, 指标 server_responses 的总体基数就会迅速膨胀.

高基数的典型案例

这还不够形象, 再举 2 个特别典型的例子:

  1. 有一个指标叫做:http_request_duration_seconds_bucket
  1. 它有 instance label, 对应 100 个实例;
  2. le label, 对应的是不同的 buckets, 有 10 个 buckets, 如(0.002 0.004 0.008=+inf)
  3. 它还有url这个 label, 对应的是不通的 url:
  1. 即使规模很小, url 可能也会有 400 个 url
  2. 这里还有个特别恐怖的隐患, 就是对于大规模系统来说, 这个 url 可能是近乎于 无穷!!!
  1. 它还有 http_method 这个 label, 对应有 5 个 http method
  2. 在这种情况下, 该指标的 label
  1. 小规模也会有: 100*10*400*5=2 000 000 200 万个 series 💀💀💀
  2. 如果大规模, url 近乎无穷的话, 那么这个基数根本无法计算出来💥💥💥
  1. 再有一种情况, 将 user_id 甚至是 session_id 经纬度 这种本来基数就很大, 甚至可能是无穷的参数设为 label, 那么对于 Prometheus 来说就是灾难了.💥💥💥

高基数的负面影响

当 Prometheus 有高基数的时候,就会出现各种问题:

  • 监控系统不稳定甚至崩溃
  • 仪表板加载很慢甚至加载失败
  • 监控查询很慢甚至失败
  • 计算存储资源开销巨大
  • 监控充斥着大量噪音干扰
  • SRE 团队不得不疲于应对海量的告警数据, 反而耽误 root cause 的分析定位

📝Notes:

基数 与指标系列(metrics series) 的数量相对应。所以在这篇博文中,会把 series 的数量与基数交替提及。

如何分析高基数问题?

分析高基数问题有以下方法:

  1. 使用 Prometheus UI 分析
  2. 使用 Prometheus PromQL 分析
  3. 使用 Prometheus API 分析
  4. 使用 Grafana Mimirtool 分析未使用的指标

使用 Prometheus UI 分析

从 Prometheus v2.14.0 以后, 在 UI 上直接有 Head Cardinality Stats 这个菜单. 极大方便了我们进行高基数问题的分析! 👍️👍️👍️

位于: Prometheus UI -> Status -> TSDB Status -> Head Cardinality Stats, 截图如下:

📝Notes:

以下截图的系统规模说明: 这就是个我用来做实验的环境, 只有 4 个 1c2g 的 node

Prometheus UI - Head Cardinality Stats

Prometheus UI - Head Cardinality Stats - 2

从上图可以直观看到:

  1. 值最多的 Label 是 url
  2. 最多的 series 的指标有:
  1. apiserver_request_duration_seconds_bucket 45524
  2. rest_client_rate_limiter_duration_seconds_bucket 36971
  3. rest_client_request_duration_seconds_bucket 10032
  1. 内存使用量最多的 Label: url
  2. 根据 Label 键值对匹配, series 最多的键值对有: (这一项目前对我来说用处不大)
  1. endpoint=metrics 105406
  2. service=pushprox-k3s-server-client 101548
  3. job=k3s-server 101543
  4. namespace=cattle-monitoring-system 101120
  5. metrics_path=/metrics 91761

使用 Prometheus PromQL 分析

如果 Prometheus 版本低于 v2.14.0, 那就需要通过:

  • Prometheus PromQL
  • Prometheus API

来进行分析.

以下提供一些实用的 PromQL:

topk(10, count by (__name__)({__name__=~".+"}))
PROMQL

对应的查询结果就是上文的 series 指标最多的 Top10

知道了 Top10, 接下来可以进一步查询细节, 由于基数巨大, 如果查询 range 可能会一直失败, 所以推荐使用 instant 的方式查询细节.

如果要查询标签的维度, 可以执行如下 PromQL:

count(count by (label_name) (metric_name))
PROMQL

如:

count(count by (url) (apiserver_request_duration_seconds_bucket))
PROMQL

另外还有一些其他的 PromQL, 罗列如下:

  • sum(scrape_series_added) by (job) 通过 job Label 分析 series 增长
  • sum(scrape_samples_scraped) by (job) 通过 job Label 分析 series 总量
  • prometheus_tsdb_symbol_table_size_bytes

使用 Prometheus API 分析

因为高基数问题的特点, 所以通过 Prometheus PromQL 查询可能经常会超时或失败. 那么可以通过 Prometheus API 进行分析:

分析各个指标的 series 数量

# 找到 Prometheus 的 SVC ClusterIP
kubectl get svc -n cattle-monitoring-system
export url=http://10.43.85.24:9090
export now=$(date +%s)
curl -s $url/api/v1/label/__name__/values \
| jq -r ".data[]" \
| while read metric; do
    count=$(curl -s \
        --data-urlencode 'query=count({__name__="'$metric'"})' \
        --data-urlencode "time=$now" \
        $url/api/v1/query \
    | jq -r ".data.result[0].value[1]")
    echo "$count $metric"
done
BASH

我自己的实验集群分析结果 top 如下: (null 可能是当前没有数据, 但历史数据量可能会很大)

活动 series 数量 指标名称
null apiserver_admission_webhook_rejection_count
null apiserver_registered_watchers
null apiserver_request_aborts_total
null apiserver_request_duration_seconds_bucket
null cluster_quantile:scheduler_e2e_scheduling_duration_seconds:histogram_quantile
null cluster_quantile:scheduler_scheduling_algorithm_duration_seconds:histogram_quantile
null kube_pod_container_status_waiting_reason
null prometheus_target_scrape_pool_target_limit
null rest_client_rate_limiter_duration_seconds_bucket
5786 rest_client_request_duration_seconds_bucket
3660 etcd_request_duration_seconds_bucket
2938 rest_client_rate_limiter_duration_seconds_count
2938 rest_client_rate_limiter_duration_seconds_sum
2840 apiserver_response_sizes_bucket
1809 apiserver_watch_events_sizes_bucket

获取指定指标的活动 series

这里以 rest_client_request_duration_seconds_bucket 为例:

export metric=rest_client_request_duration_seconds_bucket
curl -s \
    --data-urlencode "query=$metric" \
    --data-urlencode "time=$now" \
    $url/api/v1/query \
| jq -c ".data.result[].metric"
BASH

结果如下: (主要原因就是 url 的 value 太多)

获取指定指标的活动 series - url 值太多

获取所有指标的列表

curl -s $url/api/v1/label/__name__/values | jq -r ".data[]" | sort
BASH

获取标签及其基数的列表

curl -s $url/api/v1/labels \
| jq -r ".data[]" \
| while read label; do
    count=$(curl -s $url/api/v1/label/$label/values \
    | jq -r ".data|length")
    echo "$count $label"
  done \
| sort -n
BASH

结果如下: (还是因为 label url 的 value 过多! )

基数 标签
2199 url
1706 __name__
854 name
729 id
729 path
657 filename
652 container_id
420 resource
407 le
351 secret
302 type
182 kind

使用 Grafana Mimirtool 分析未使用的指标

📚️Reference:

Grafana Mimirtool | Grafana Mimir documentation

Grafana Mimir 的介绍具体见这里: Intro to Grafana Mimir: The open source time series database that scales to 1 billion metrics & beyond | Grafana Labs

Mimir 有个实用工具叫 mimirtool, 可以通过对比 Prometheus 的指标, 和 AlertManager 以及 Grafana 用到的指标, 来分析哪些指标没有用到. 可以通过如下输入进行分析:

  • Grafana 实例的 Grafana Dashboards
  • Prometheus 实例的 recording rules 和 alerting rules
  • Grafana Dashboard json 文件
  • Prometheus recording 和 alerting rules YAML 文件

这里就不做详细介绍, 完整介绍见这里: Analyzing and reducing metrics usage with Grafana Mimirtool | Grafana Cloud documentation

解决高基数问题

对于高基数问题, 有几种情况:

  1. 某些 label 不合理, 值很多甚至无穷;
  2. 某些 指标 不合理, 值很多;
  3. Prometheus 整体的全部 series 量太大

对于第三个问题, 以下 2 个办法可以解决:

对于高可用 Prometheus 的高基数问题

有一种高基数的情况, 是 Prometheus 以 HA 模式部署, 并且通过 remote_write 方式将数据发送到 VM、Mimir 或 Thanos. 导致数据冗余。

针对这种情况,可以根据 VM、Mimir 或 Thanos 官方文档的指导,添加 external_labels 供这些软件自动处理高基数问题.

示例配置如下:

增加external_labels

  1. cluster
  2. __replicas__

增大采集间隔

增加 Prometheus 的 global scrape_interval(调整全局的该参数, 对于某些确实需要更小采集间隔的, 可以在 job 内详细配置)

一般可能默认是 scrape_interval: 15s

建议将其增大值调整为 scrape_interval: 1m 甚至更大.

过滤和保留 kubernetes-mixin 指标

对于 kubernetes-mixin、Prometheus Operator、kube-prometheus 等项目,都会提供一些开箱即用的:

  • scrape metrics
  • recording rules
  • alerting rules
  • Grafana Dashboards

对于这种情况, 根据对于 Grafana Dashboards 和 alerting rules,可以通过 relabel 保留用到的指标。

📚️Reference:

「译文」通过 Relabel 减少 Prometheus 指标的使用量 - 东风微鸣技术博客 (ewhisper.cn)

示例如下:

remoteWrite:
- url: "<Your Metrics instance remote_write endpoint>"
  basicAuth:
    username:
      name: your_grafanacloud_secret
      key: your_grafanacloud_secret_username_key
    password:
      name: your_grafanacloud_secret
      key: your_grafanacloud_secret_password_key
  writeRelabelConfigs:
  - sourceLabels:
    - "__name__"
    regex: "apiserver_request_total|kubelet_node_config_error|kubelet_runtime_operations_errors_total|kubeproxy_network_programming_duration_seconds_bucket|container_cpu_usage_seconds_total|kube_statefulset_status_replicas|kube_statefulset_status_replicas_ready|node_namespace_pod_container:container_memory_swap|kubelet_runtime_operations_total|kube_statefulset_metadata_generation|node_cpu_seconds_total|kube_pod_container_resource_limits_cpu_cores|node_namespace_pod_container:container_memory_cache|kubelet_pleg_relist_duration_seconds_bucket|scheduler_binding_duration_seconds_bucket|container_network_transmit_bytes_total|kube_pod_container_resource_requests_memory_bytes|namespace_workload_pod:kube_pod_owner:relabel|kube_statefulset_status_observed_generation|process_resident_memory_bytes|container_network_receive_packets_dropped_total|kubelet_running_containers|kubelet_pod_worker_duration_seconds_bucket|scheduler_binding_duration_seconds_count|scheduler_volume_scheduling_duration_seconds_bucket|workqueue_queue_duration_seconds_bucket|container_network_transmit_packets_total|rest_client_request_duration_seconds_bucket|node_namespace_pod_container:container_memory_rss|container_cpu_cfs_throttled_periods_total|kubelet_volume_stats_capacity_bytes|kubelet_volume_stats_inodes_used|cluster_quantile:apiserver_request_duration_seconds:histogram_quantile|kube_node_status_allocatable_memory_bytes|container_memory_cache|go_goroutines|kubelet_runtime_operations_duration_seconds_bucket|kube_statefulset_replicas|kube_pod_owner|rest_client_requests_total|container_memory_swap|node_namespace_pod_container:container_memory_working_set_bytes|storage_operation_errors_total|scheduler_e2e_scheduling_duration_seconds_bucket|container_network_transmit_packets_dropped_total|kube_pod_container_resource_limits_memory_bytes|node_namespace_pod_container:container_cpu_usage_seconds_total:sum_rate|storage_operation_duration_seconds_count|node_netstat_TcpExt_TCPSynRetrans|node_netstat_Tcp_OutSegs|container_cpu_cfs_periods_total|kubelet_pod_start_duration_seconds_count|kubeproxy_network_programming_duration_seconds_count|container_network_receive_bytes_total|node_netstat_Tcp_RetransSegs|up|storage_operation_duration_seconds_bucket|kubelet_cgroup_manager_duration_seconds_count|kubelet_volume_stats_available_bytes|scheduler_scheduling_algorithm_duration_seconds_bucket|kube_statefulset_status_replicas_current|code_resource:apiserver_request_total:rate5m|kube_statefulset_status_replicas_updated|process_cpu_seconds_total|kube_pod_container_resource_requests_cpu_cores|kubelet_pod_worker_duration_seconds_count|kubelet_cgroup_manager_duration_seconds_bucket|kubelet_pleg_relist_duration_seconds_count|kubeproxy_sync_proxy_rules_duration_seconds_bucket|container_memory_usage_bytes|workqueue_adds_total|container_network_receive_packets_total|container_memory_working_set_bytes|kube_resourcequota|kubelet_running_pods|kubelet_volume_stats_inodes|kubeproxy_sync_proxy_rules_duration_seconds_count|scheduler_scheduling_algorithm_duration_seconds_count|apiserver_request:availability30d|container_memory_rss|kubelet_pleg_relist_interval_seconds_bucket|scheduler_e2e_scheduling_duration_seconds_count|scheduler_volume_scheduling_duration_seconds_count|workqueue_depth|:node_memory_MemAvailable_bytes:sum|volume_manager_total_volumes|kube_node_status_allocatable_cpu_cores"
    action: "keep"
YAML

🐾Warning:

以上配置可能根据不同的版本, 会有不同的变化, 请酌情参考使用.

或者根据上文提到的 mimirtool 自行分析生成适合自己的配置.

通过 Relabel 减少 Prometheus 指标的使用量

举一个简单例子如下:

write_relabel_configs:
  - source_labels: [__name__]
    regex: "apiserver_request_duration_seconds_bucket"
    action: drop
YAML

通过 recording rules 聚合指标并和 relabel drop 结合使用

比如对于 apiserver_request_duration_seconds_bucket, 我需要的是一些高纬度的指标 - 如 API Server 的可用率, 那么这些指标可以通过 recording rules 进行记录和存储, 示例如下:

groups:
  - interval: 3m
    name: kube-apiserver-availability.rules
    rules:
      - expr: >-
          avg_over_time(code_verb:apiserver_request_total:increase1h[30d]) *
          24 * 30
        record: code_verb:apiserver_request_total:increase30d
      - expr: >-
          sum by (cluster, code, verb)
          (increase(apiserver_request_total{job="apiserver",verb=~"LIST|GET|POST|PUT|PATCH|DELETE",code=~"2.."}[1h]))
        record: code_verb:apiserver_request_total:increase1h
      - expr: >-
          sum by (cluster, code, verb)
          (increase(apiserver_request_total{job="apiserver",verb=~"LIST|GET|POST|PUT|PATCH|DELETE",code=~"5.."}[1h]))
        record: code_verb:apiserver_request_total:increase1h
YAML

之后可以再在 remote_wirte 等阶段删掉原始指标:

write_relabel_configs:
  - source_labels: [__name__]
    regex: "apiserver_request_duration_seconds_bucket"
    action: drop
YAML

💪💪💪

📚️参考文档

相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
相关文章
|
7月前
|
存储 Prometheus 监控
Prometheus 性能调优 - 水平分片
Prometheus 性能调优 - 水平分片
|
1月前
|
Prometheus 运维 监控
智能运维实战:Prometheus与Grafana的监控与告警体系
【10月更文挑战第26天】Prometheus与Grafana是智能运维中的强大组合,前者是开源的系统监控和警报工具,后者是数据可视化平台。Prometheus具备时间序列数据库、多维数据模型、PromQL查询语言等特性,而Grafana支持多数据源、丰富的可视化选项和告警功能。两者结合可实现实时监控、灵活告警和高度定制化的仪表板,广泛应用于服务器、应用和数据库的监控。
182 3
|
4月前
|
Prometheus 监控 Cloud Native
【监控】prometheus传统环境监控告警常用配置
【监控】prometheus传统环境监控告警常用配置
【监控】prometheus传统环境监控告警常用配置
|
14天前
|
存储 Prometheus 监控
监控堆外第三方监控工具Prometheus
监控堆外第三方监控工具Prometheus
33 3
|
17天前
|
存储 Prometheus 运维
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案
在云原生环境中,阿里云ARMS与Prometheus的集成提供了强大的应用实时监控解决方案。该集成结合了ARMS的基础设施监控能力和Prometheus的灵活配置及社区支持,实现了全面、精准的系统状态、性能和错误监控,提升了应用的稳定性和管理效率。通过统一的数据视图和高级查询功能,帮助企业有效应对云原生挑战,促进业务的持续发展。
25 3
|
28天前
|
Prometheus 监控 Cloud Native
在 HBase 集群中,Prometheus 通常监控哪些类型的性能指标?
在 HBase 集群中,Prometheus 监控关注的核心指标包括 Master 和 RegionServer 的进程存在性、RPC 请求数、JVM 内存使用率、磁盘和网络错误、延迟和吞吐量、资源利用率及 JVM 使用信息。通过 Grafana 可视化和告警规则,帮助管理员实时监控集群性能和健康状况。
|
1月前
|
Prometheus 运维 监控
智能运维实战:Prometheus与Grafana的监控与告警体系
【10月更文挑战第27天】在智能运维中,Prometheus和Grafana的组合已成为监控和告警体系的事实标准。Prometheus负责数据收集和存储,支持灵活的查询语言PromQL;Grafana提供数据的可视化展示和告警功能。本文介绍如何配置Prometheus监控目标、Grafana数据源及告警规则,帮助运维团队实时监控系统状态,确保稳定性和可靠性。
153 0
|
3月前
|
Prometheus 监控 Cloud Native
介绍如何使用Prometheus进行监控
介绍如何使用Prometheus进行监控
254 3
|
3月前
|
Prometheus 监控 Cloud Native
docker安装prometheus+Granfan并监控容器
【9月更文挑战第14天】本文介绍了在Docker中安装Prometheus与Grafana并监控容器的步骤,包括创建配置文件、运行Prometheus与Grafana容器,以及在Grafana中配置数据源和创建监控仪表盘,展示了如何通过Prometheus抓取数据并利用Grafana展示容器的CPU使用率等关键指标。
113 1
|
4月前
|
存储 Prometheus 监控
Grafana 与 Prometheus 集成:打造高效监控系统
【8月更文第29天】在现代软件开发和运维领域,监控系统已成为不可或缺的一部分。Prometheus 和 Grafana 作为两个非常流行且互补的开源工具,可以协同工作来构建强大的实时监控解决方案。Prometheus 负责收集和存储时间序列数据,而 Grafana 则提供直观的数据可视化功能。本文将详细介绍如何集成这两个工具,构建一个高效、灵活的监控系统。
474 1