背景
近期发现自己试验用的 Prometheus 性能呈现瓶颈, 常常会呈现如下告警:
PrometheusMissingRuleEvaluations
PrometheusRuleFailures
之后缓缓排查发现是因为 Prometheus 的某些 series 的高基数(High Cardinality)导致的. 本文是对 Prometheus 高基数问题的一次全面总结.
什么是基数(Cardinality)?
基数的根本定义是指一个给定汇合中的元素的数量。
在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 个特地典型的例子:
有一个指标叫做:
http_request_duration_seconds_bucket
- 它有
instance
label, 对应 100 个实例; - 有
le
label, 对应的是不同的 buckets, 有 10 个 buckets, 如(0.002
0.004
0.008
...=+inf
) 它还有
url
这个 label, 对应的是不通的 url:- 即便规模很小, url 可能也会有 400 个 url
- 这里还有个特地恐怖的隐患, 就是对于大规模零碎来说, 这个 url 可能是近乎于无穷!!!
- 它还有
http_method
这个label, 对应有 5 个 http method 在这种状况下, 该指标的 label
- 小规模也会有:
100*10*400*5=2 000 000
200万个 series - 如果大规模, url 近乎无穷的话, 那么这个基数根本无法计算出来
- 小规模也会有:
- 它有
- 再有一种状况, 将
user_id
甚至是session_id
经纬度
这种原本基数就很大, 甚至可能是无穷的参数设为 label, 那么对于 Prometheus 来说就是劫难了.
高基数的负面影响
当 Prometheus 有高基数的时候,就会呈现各种问题:
监控零碎不稳固甚至解体
- 仪表板加载很慢甚至加载失败
- 监控查问很慢甚至失败
- 计算存储资源开销微小
监控充斥着大量乐音烦扰
- SRE 团队不得不疲于应答海量的告警数据, 反而耽搁 root cause 的剖析定位
Notes:
基数 与指标系列(metrics series) 的数量绝对应。所以在这篇博文中,会把 series 的数量与基数交替提及。
如何剖析高基数问题?
剖析高基数问题有以下办法:
- 应用 Prometheus UI 剖析
- 应用 Prometheus PromQL 剖析
- 应用 Prometheus API 剖析
- 应用 Grafana Mimirtool 剖析未应用的指标
应用 Prometheus UI 剖析
从 Prometheus v2.14.0 当前, 在 UI 上间接有 Head Cardinality Stats 这个菜单. 极大不便了咱们进行高基数问题的剖析! ️️️
位于: Prometheus UI -> Status -> TSDB Status -> Head Cardinality Stats, 截图如下:
Notes:
以下截图的零碎规模阐明: 这就是个我用来做试验的环境, 只有 4 个 1c2g 的 node
从上图能够直观看到:
- 值最多的 Label 是
url
最多的 series 的指标有:
apiserver_request_duration_seconds_bucket
45524rest_client_rate_limiter_duration_seconds_bucket
36971rest_client_request_duration_seconds_bucket
10032
- 内存使用量最多的 Label:
url
依据 Label 键值对匹配, series 最多的键值对有: (这一项目前对我来说用途不大)
endpoint=metrics
105406service=pushprox-k3s-server-client
101548job=k3s-server
101543namespace=cattle-monitoring-system
101120metrics_path=/metrics
91761
应用 Prometheus PromQL 剖析
如果 Prometheus 版本低于 v2.14.0, 那就须要通过:
- Prometheus PromQL
- Prometheus API
来进行剖析.
以下提供一些实用的 PromQL:
topk(10, count by (__name__)({__name__=~".+"}))
对应的查问后果就是上文的 series 指标最多的 Top10
晓得了 Top10, 接下来能够进一步查问细节, 因为基数微小, 如果查问 range 可能会始终失败, 所以举荐应用 instant
的形式查问细节.
如果要查问标签的维度, 能够执行如下 PromQL:
count(count by (label_name) (metric_name))
如:
count(count by (url) (apiserver_request_duration_seconds_bucket))
另外还有一些其余的 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 ClusterIPkubectl get svc -n cattle-monitoring-systemexport url=http://10.43.85.24:9090export 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
我本人的试验集群剖析后果 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_bucketcurl -s \ --data-urlencode "query=$metric" \ --data-urlencode "time=$now" \ $url/api/v1/query \| jq -c ".data.result[].metric"
后果如下: (次要起因就是 url 的 value 太多)
获取所有指标的列表
curl -s $url/api/v1/label/__name__/values | jq -r ".data[]" | sort
获取标签及其基数的列表
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
后果如下: (还是因为 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
解决高基数问题
对于高基数问题, 有几种状况:
- 某些 label 不合理, 值很多甚至无穷;
- 某些 指标 不合理, 值很多;
- Prometheus 整体的全副 series 量太大
对于第三个问题, 以下 2 个方法能够解决:
对于高可用 Prometheus的高基数问题
有一种高基数的状况, 是 Prometheus 以 HA 模式部署, 并且通过 remote_write
形式将数据发送到 VM、Mimir 或 Thanos.导致数据冗余。
针对这种状况,能够依据 VM、Mimir 或 Thanos 官网文档的领导,增加 external_labels
供这些软件主动解决高基数问题.
示例配置如下:
减少external_labels
cluster
__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"
Warning:
以上配置可能依据不同的版本, 会有不同的变动, 请酌情参考应用.
或者依据上文提到的mimirtool
自行剖析生成适宜本人的配置.
通过 Relabel 缩小 Prometheus 指标的使用量
举一个简略例子如下:
write_relabel_configs: - source_labels: [__name__] regex: "apiserver_request_duration_seconds_bucket" action: drop
通过 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
之后能够再在 remote_wirte
等阶段删掉原始指标:
write_relabel_configs: - source_labels: [__name__] regex: "apiserver_request_duration_seconds_bucket" action: drop
️参考文档
- How to manage high cardinality metrics in a Prometheus environment (grafana.com)
- What are cardinality spikes and why do they matter? | Grafana Labs
- Containing your Cardinality
- Control Prometheus metrics usage | Grafana Cloud documentation
- Bomb Squad: Automatic Detection and Suppression of Prometheus Cardinality Explosions | by Cody Boggs | FreshTracks.io
- Feature: Expose a request path label in the
http_request_*
metric by default · Issue #491 · prometheus/client_golang · GitHub
三人行, 必有我师; 常识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.